AK51 commited on
Commit
ab59545
·
verified ·
1 Parent(s): d66aa0c

Upload 18 files

Browse files
.gitignore ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # IDE
2
+ .vscode/
3
+ .idea/
4
+ *.swp
5
+ *.swo
6
+ *~
7
+
8
+ # OS
9
+ .DS_Store
10
+ Thumbs.db
11
+
12
+ # Kiro
13
+ .kiro/
14
+
15
+ # Logs
16
+ *.log
17
+ npm-debug.log*
18
+
19
+ # Temporary files
20
+ *.tmp
21
+ *.temp
DEPLOYMENT.md ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deploying to Hugging Face Spaces
2
+
3
+ ## Quick Start
4
+
5
+ 1. **Create a Hugging Face Account**
6
+ - Go to https://huggingface.co/join
7
+ - Sign up for a free account
8
+
9
+ 2. **Create a New Space**
10
+ - Go to https://huggingface.co/spaces
11
+ - Click "Create new Space"
12
+ - Choose a name for your space (e.g., "periodic-table-viewer")
13
+ - Select SDK: **Static**
14
+ - Choose visibility: Public or Private
15
+ - Click "Create Space"
16
+
17
+ 3. **Upload Files**
18
+
19
+ **Option A: Using Git (Recommended)**
20
+ ```bash
21
+ # Clone your space repository
22
+ git clone https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
23
+ cd YOUR_SPACE_NAME
24
+
25
+ # Copy all files from this project
26
+ cp /path/to/this/project/index.html .
27
+ cp /path/to/this/project/README.md .
28
+
29
+ # Commit and push
30
+ git add .
31
+ git commit -m "Initial commit: Periodic Table Spectrum Viewer"
32
+ git push
33
+ ```
34
+
35
+ **Option B: Using Web Interface**
36
+ - In your Space, click "Files" tab
37
+ - Click "Add file" → "Upload files"
38
+ - Upload `index.html` and `README.md`
39
+ - Click "Commit changes to main"
40
+
41
+ 4. **Wait for Build**
42
+ - Hugging Face will automatically build and deploy your space
43
+ - This usually takes 10-30 seconds
44
+ - Your space will be available at: `https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME`
45
+
46
+ ## Files Needed for Deployment
47
+
48
+ Only these files are required:
49
+ - ✅ `index.html` - Main application (self-contained)
50
+ - ✅ `README.md` - Space description and metadata
51
+
52
+ **NOT needed** (already embedded in index.html):
53
+ - ❌ JavaScript files (all code is in index.html)
54
+ - ❌ CSS files (all styles are in index.html)
55
+ - ❌ Data files (all data is in index.html)
56
+
57
+ ## Customization
58
+
59
+ ### Update Space Metadata
60
+ Edit the frontmatter in `README.md`:
61
+ ```yaml
62
+ ---
63
+ title: Your Custom Title
64
+ emoji: 🔬 # Choose any emoji
65
+ colorFrom: blue # Start color for gradient
66
+ colorTo: purple # End color for gradient
67
+ sdk: static # Keep as "static"
68
+ pinned: false # Pin to your profile
69
+ license: mit # Choose your license
70
+ ---
71
+ ```
72
+
73
+ ### Update Credits
74
+ In `index.html`, find the footer section and update:
75
+ ```html
76
+ <footer>
77
+ <p>Created by Your Name | Data from <a href="..." target="_blank">Source</a></p>
78
+ </footer>
79
+ ```
80
+
81
+ ## Troubleshooting
82
+
83
+ ### Space not loading?
84
+ - Check the "Logs" tab in your Space for errors
85
+ - Ensure `index.html` is in the root directory
86
+ - Verify the README.md has correct frontmatter
87
+
88
+ ### Three.js not loading?
89
+ - The app uses CDN links for Three.js
90
+ - Ensure you have internet connection
91
+ - Check browser console for errors
92
+
93
+ ### Need to update?
94
+ ```bash
95
+ # Make changes to index.html locally
96
+ # Then push updates
97
+ git add index.html
98
+ git commit -m "Update: description of changes"
99
+ git push
100
+ ```
101
+
102
+ ## Support
103
+
104
+ For issues with:
105
+ - **Hugging Face Spaces**: https://huggingface.co/docs/hub/spaces
106
+ - **This Project**: Open an issue in the repository
107
+
108
+ ## Example Spaces
109
+
110
+ See other static spaces for inspiration:
111
+ - https://huggingface.co/spaces (search for "static" SDK)
DataManager.js ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export class DataManager {
2
+ constructor() {
3
+ this.elementsData = new Map();
4
+ }
5
+
6
+ async loadSpectralData(dataUrl) {
7
+ try {
8
+ const response = await fetch(dataUrl);
9
+ if (!response.ok) {
10
+ throw new Error(`Failed to load spectral data: ${response.statusText}`);
11
+ }
12
+ const data = await response.json();
13
+
14
+ if (!this.validateData(data)) {
15
+ throw new Error('Invalid spectral data format');
16
+ }
17
+
18
+ // Store elements in a Map for quick lookup
19
+ data.elements.forEach(element => {
20
+ this.elementsData.set(element.symbol, element);
21
+ });
22
+
23
+ return true;
24
+ } catch (error) {
25
+ console.error('Error loading spectral data:', error);
26
+ throw error;
27
+ }
28
+ }
29
+
30
+ getElementData(symbol) {
31
+ return this.elementsData.get(symbol) || null;
32
+ }
33
+
34
+ getAllElements() {
35
+ return Array.from(this.elementsData.keys());
36
+ }
37
+
38
+ validateData(data) {
39
+ if (!data || !Array.isArray(data.elements)) {
40
+ return false;
41
+ }
42
+
43
+ for (const element of data.elements) {
44
+ if (!element.symbol || !element.name || !element.atomicNumber) {
45
+ return false;
46
+ }
47
+ if (!Array.isArray(element.spectralLines)) {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ return true;
53
+ }
54
+
55
+ validateWavelength(wavelength) {
56
+ return wavelength >= 10 && wavelength <= 10000;
57
+ }
58
+ }
HUGGINGFACE_SETUP.md ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face Spaces Setup - Quick Guide
2
+
3
+ ## ✅ Your Project is Ready!
4
+
5
+ Your Periodic Table Spectrum Viewer is now configured for Hugging Face Spaces deployment.
6
+
7
+ ## 📦 What's Been Prepared
8
+
9
+ 1. **README.md** - Contains Hugging Face Space metadata and project description
10
+ 2. **.gitignore** - Excludes unnecessary files from version control
11
+ 3. **DEPLOYMENT.md** - Detailed deployment instructions
12
+ 4. **index.html** - Self-contained application (no external dependencies needed)
13
+
14
+ ## 🚀 Deploy in 3 Steps
15
+
16
+ ### Step 1: Create Space
17
+ 1. Go to https://huggingface.co/spaces
18
+ 2. Click "Create new Space"
19
+ 3. Name: `periodic-table-viewer` (or your choice)
20
+ 4. SDK: Select **"Static"**
21
+ 5. Click "Create Space"
22
+
23
+ ### Step 2: Upload Files
24
+ Upload only these 2 files:
25
+ - `index.html`
26
+ - `README.md`
27
+
28
+ ### Step 3: Done!
29
+ Your space will be live at:
30
+ `https://huggingface.co/spaces/YOUR_USERNAME/periodic-table-viewer`
31
+
32
+ ## 📝 Important Notes
33
+
34
+ ### ✅ What's Included
35
+ - All JavaScript code (embedded in index.html)
36
+ - All CSS styles (embedded in index.html)
37
+ - All element data (embedded in index.html)
38
+ - Three.js library (loaded from CDN)
39
+
40
+ ### ❌ What's NOT Needed
41
+ - No separate .js files
42
+ - No separate .css files
43
+ - No data files
44
+ - No build process
45
+ - No dependencies to install
46
+
47
+ ### 🌐 External Dependencies
48
+ The app loads Three.js from CDN:
49
+ - `https://cdn.jsdelivr.net/npm/three@0.160.0/`
50
+
51
+ This is standard and works perfectly on Hugging Face Spaces.
52
+
53
+ ## 🎨 Customization
54
+
55
+ ### Change Space Appearance
56
+ Edit `README.md` frontmatter:
57
+ ```yaml
58
+ ---
59
+ title: Your Title Here
60
+ emoji: 🔬
61
+ colorFrom: blue
62
+ colorTo: purple
63
+ ---
64
+ ```
65
+
66
+ ### Change Credits
67
+ Edit the footer in `index.html`:
68
+ ```html
69
+ <footer>
70
+ <p>Created by Your Name</p>
71
+ </footer>
72
+ ```
73
+
74
+ ## 🔧 Testing Locally
75
+
76
+ Before deploying, test locally:
77
+ 1. Open `index.html` in a web browser
78
+ 2. Check that all features work:
79
+ - 3D periodic table loads
80
+ - Elements are clickable
81
+ - Spectrum popup works
82
+ - All 118 elements have data
83
+
84
+ ## 📚 Resources
85
+
86
+ - **Hugging Face Spaces Docs**: https://huggingface.co/docs/hub/spaces
87
+ - **Static Spaces Guide**: https://huggingface.co/docs/hub/spaces-sdks-static
88
+ - **Example Spaces**: Search "static" on https://huggingface.co/spaces
89
+
90
+ ## 🆘 Troubleshooting
91
+
92
+ **Space shows blank page?**
93
+ - Check browser console for errors
94
+ - Verify index.html is in root directory
95
+ - Ensure README.md has correct frontmatter
96
+
97
+ **Three.js not loading?**
98
+ - CDN might be temporarily down
99
+ - Check internet connection
100
+ - Try refreshing the page
101
+
102
+ **Need help?**
103
+ - Check DEPLOYMENT.md for detailed instructions
104
+ - Visit Hugging Face Discord: https://hf.co/join/discord
105
+
106
+ ## 🎉 Next Steps
107
+
108
+ 1. Deploy to Hugging Face Spaces
109
+ 2. Share your space URL
110
+ 3. Get feedback from users
111
+ 4. Consider adding more features:
112
+ - Search functionality
113
+ - Favorite elements
114
+ - Compare elements side-by-side
115
+ - Export spectrum images
116
+
117
+ Good luck with your deployment! 🚀
InteractionController.js ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
2
+
3
+ export class InteractionController {
4
+ constructor(sceneManager, periodicTable, spectrumDisplay) {
5
+ this.sceneManager = sceneManager;
6
+ this.periodicTable = periodicTable;
7
+ this.spectrumDisplay = spectrumDisplay;
8
+ this.controls = null;
9
+ this.renderer = sceneManager.getRenderer();
10
+ this.camera = sceneManager.getCamera();
11
+ }
12
+
13
+ initialize() {
14
+ // Set up OrbitControls
15
+ this.controls = new OrbitControls(this.camera, this.renderer.domElement);
16
+ this.controls.enableDamping = true;
17
+ this.controls.dampingFactor = 0.05;
18
+ this.controls.minDistance = 10;
19
+ this.controls.maxDistance = 50;
20
+ this.controls.enablePan = true;
21
+
22
+ // Mouse move for hover
23
+ this.renderer.domElement.addEventListener('mousemove', (e) => this.onMouseMove(e));
24
+
25
+ // Click for selection
26
+ this.renderer.domElement.addEventListener('click', (e) => this.onClick(e));
27
+
28
+ // ESC key for deselection
29
+ document.addEventListener('keydown', (e) => {
30
+ if (e.key === 'Escape') {
31
+ this.deselectElement();
32
+ }
33
+ });
34
+
35
+ // Update controls in animation loop
36
+ this.updateControls();
37
+ }
38
+
39
+ onMouseMove(event) {
40
+ const rect = this.renderer.domElement.getBoundingClientRect();
41
+ const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
42
+ const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
43
+
44
+ const element = this.periodicTable.getElementAtPosition(x, y, this.camera);
45
+
46
+ if (element) {
47
+ this.renderer.domElement.style.cursor = 'pointer';
48
+ this.periodicTable.highlightElement(element);
49
+ } else {
50
+ this.renderer.domElement.style.cursor = 'default';
51
+ this.periodicTable.highlightElement(null);
52
+ }
53
+ }
54
+
55
+ onClick(event) {
56
+ const rect = this.renderer.domElement.getBoundingClientRect();
57
+ const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
58
+ const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
59
+
60
+ const element = this.periodicTable.getElementAtPosition(x, y, this.camera);
61
+
62
+ if (element) {
63
+ this.periodicTable.selectElement(element);
64
+ this.spectrumDisplay.showSpectrum(element);
65
+ } else {
66
+ this.deselectElement();
67
+ }
68
+ }
69
+
70
+ deselectElement() {
71
+ this.periodicTable.selectElement(null);
72
+ this.spectrumDisplay.hideSpectrum();
73
+ }
74
+
75
+ updateControls() {
76
+ const animate = () => {
77
+ requestAnimationFrame(animate);
78
+ if (this.controls) {
79
+ this.controls.update();
80
+ }
81
+ };
82
+ animate();
83
+ }
84
+
85
+ dispose() {
86
+ if (this.controls) {
87
+ this.controls.dispose();
88
+ }
89
+ }
90
+ }
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Andy Kong
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
PeriodicTableComponent.js ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as THREE from 'three';
2
+ import { getElementPosition, ELEMENT_COLORS } from './elementLayout.js';
3
+
4
+ export class PeriodicTableComponent {
5
+ constructor(scene, dataManager) {
6
+ this.scene = scene;
7
+ this.dataManager = dataManager;
8
+ this.elementMeshes = new Map();
9
+ this.highlightedElement = null;
10
+ this.selectedElement = null;
11
+ }
12
+
13
+ createPeriodicTable() {
14
+ const elements = this.dataManager.getAllElements();
15
+
16
+ elements.forEach(symbol => {
17
+ const elementData = this.dataManager.getElementData(symbol);
18
+ const position = getElementPosition(symbol);
19
+
20
+ if (!position) return;
21
+
22
+ // Create element tile
23
+ const geometry = new THREE.BoxGeometry(0.9, 0.9, 0.1);
24
+ const color = ELEMENT_COLORS[elementData.category] || ELEMENT_COLORS.unknown;
25
+ const material = new THREE.MeshStandardMaterial({
26
+ color: color,
27
+ emissive: 0x000000,
28
+ metalness: 0.3,
29
+ roughness: 0.7
30
+ });
31
+
32
+ const mesh = new THREE.Mesh(geometry, material);
33
+ mesh.position.set(position.x, position.y, position.z);
34
+ mesh.userData = { symbol, elementData };
35
+
36
+ this.scene.add(mesh);
37
+ this.elementMeshes.set(symbol, mesh);
38
+
39
+ // Add text label
40
+ this.addTextLabel(symbol, position);
41
+ });
42
+ }
43
+
44
+ addTextLabel(symbol, position) {
45
+ const canvas = document.createElement('canvas');
46
+ const context = canvas.getContext('2d');
47
+ canvas.width = 128;
48
+ canvas.height = 128;
49
+
50
+ context.fillStyle = 'white';
51
+ context.font = 'bold 60px Arial';
52
+ context.textAlign = 'center';
53
+ context.textBaseline = 'middle';
54
+ context.fillText(symbol, 64, 64);
55
+
56
+ const texture = new THREE.CanvasTexture(canvas);
57
+ const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
58
+ const sprite = new THREE.Sprite(spriteMaterial);
59
+ sprite.scale.set(0.6, 0.6, 1);
60
+ sprite.position.set(position.x, position.y, position.z + 0.1);
61
+
62
+ this.scene.add(sprite);
63
+ }
64
+
65
+ highlightElement(symbol) {
66
+ // Reset previous highlight
67
+ if (this.highlightedElement && this.highlightedElement !== this.selectedElement) {
68
+ const prevMesh = this.elementMeshes.get(this.highlightedElement);
69
+ if (prevMesh) {
70
+ prevMesh.material.emissive.setHex(0x000000);
71
+ }
72
+ }
73
+
74
+ // Apply new highlight
75
+ if (symbol && symbol !== this.selectedElement) {
76
+ const mesh = this.elementMeshes.get(symbol);
77
+ if (mesh) {
78
+ mesh.material.emissive.setHex(0x444444);
79
+ }
80
+ }
81
+
82
+ this.highlightedElement = symbol;
83
+ }
84
+
85
+ selectElement(symbol) {
86
+ // Reset previous selection
87
+ if (this.selectedElement) {
88
+ const prevMesh = this.elementMeshes.get(this.selectedElement);
89
+ if (prevMesh) {
90
+ prevMesh.material.emissive.setHex(0x000000);
91
+ }
92
+ }
93
+
94
+ // Apply new selection
95
+ if (symbol) {
96
+ const mesh = this.elementMeshes.get(symbol);
97
+ if (mesh) {
98
+ mesh.material.emissive.setHex(0x00ff00);
99
+ }
100
+ }
101
+
102
+ this.selectedElement = symbol;
103
+ }
104
+
105
+ getElementAtPosition(x, y, camera) {
106
+ const raycaster = new THREE.Raycaster();
107
+ const mouse = new THREE.Vector2(x, y);
108
+
109
+ raycaster.setFromCamera(mouse, camera);
110
+
111
+ const meshes = Array.from(this.elementMeshes.values());
112
+ const intersects = raycaster.intersectObjects(meshes);
113
+
114
+ if (intersects.length > 0) {
115
+ return intersects[0].object.userData.symbol;
116
+ }
117
+
118
+ return null;
119
+ }
120
+
121
+ getSelectedElement() {
122
+ return this.selectedElement;
123
+ }
124
+ }
README.md CHANGED
@@ -1,11 +1,59 @@
1
- ---
2
- title: Periodic Table
3
- emoji: 👀
4
- colorFrom: purple
5
- colorTo: gray
6
- sdk: static
7
- pinned: false
8
- short_description: 3D Periodic Table with Emission lines for astronomy
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Periodic Table Spectrum Viewer
3
+ emoji: 🔬
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: static
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ # Periodic Table Spectrum Viewer
12
+
13
+ An interactive 3D periodic table with detailed emission spectrum visualization for all 118 elements.
14
+
15
+ ## Features
16
+
17
+ - **3D Interactive Periodic Table**: Explore all 118 elements in a beautiful 3D layout using Three.js
18
+ - **Emission Spectra**: View accurate emission spectra for each element
19
+ - **Detailed Spectrum Viewer**: Click on any spectrum to see an expanded view with:
20
+ - Visible spectrum (380-750nm) with continuous color gradient
21
+ - UV and IR regions for elements with lines outside visible range
22
+ - Electron transition information
23
+ - Wavelength labels and intensity data
24
+ - **Element Information**:
25
+ - Cosmic origin (how the element was created in the universe)
26
+ - Chemistry properties and uses
27
+ - Physics data (electron configuration, isotopes)
28
+ - **Color-Coded Categories**: Elements colored by type (metals, nonmetals, noble gases, etc.)
29
+ - **Hover Effects**: Elements pop forward when you hover over them
30
+ - **Collapsible Info Panel**: Full-height panel with collapsible sections
31
+
32
+ ## How to Use
33
+
34
+ 1. **Rotate**: Click and drag to rotate the periodic table
35
+ 2. **Zoom**: Scroll to zoom in/out
36
+ 3. **Select Element**: Click on any element to view its details
37
+ 4. **View Detailed Spectrum**: Click on the emission spectrum graph to open a full-width detailed view
38
+ 5. **Deselect**: Press ESC or click away to deselect
39
+
40
+ ## Data Sources
41
+
42
+ - Spectral data from [NIST Atomic Spectra Database](https://www.nist.gov/pml/atomic-spectra-database)
43
+ - Cosmic origin information based on astrophysics research
44
+ - Chemistry and physics data from scientific databases
45
+
46
+ ## Technology
47
+
48
+ - **Three.js**: 3D visualization
49
+ - **Vanilla JavaScript**: No framework dependencies
50
+ - **Canvas API**: Spectrum rendering
51
+ - **CSS3**: Modern styling and animations
52
+
53
+ ## Credits
54
+
55
+ Created by Andy Kong
56
+
57
+ ## License
58
+
59
+ MIT License - Feel free to use and modify for your own projects!
SceneManager.js ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as THREE from 'three';
2
+
3
+ export class SceneManager {
4
+ constructor(container) {
5
+ this.container = container;
6
+ this.scene = null;
7
+ this.camera = null;
8
+ this.renderer = null;
9
+ this.animationId = null;
10
+ }
11
+
12
+ initialize() {
13
+ // Check WebGL support
14
+ if (!this.checkWebGLSupport()) {
15
+ this.showWebGLError();
16
+ return false;
17
+ }
18
+
19
+ // Create scene
20
+ this.scene = new THREE.Scene();
21
+ this.scene.background = new THREE.Color(0x1a1a2e);
22
+
23
+ // Create camera
24
+ const aspect = this.container.clientWidth / this.container.clientHeight;
25
+ this.camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
26
+ this.camera.position.set(0, 0, 30);
27
+
28
+ // Create renderer
29
+ this.renderer = new THREE.WebGLRenderer({ antialias: true });
30
+ this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
31
+ this.renderer.setPixelRatio(window.devicePixelRatio);
32
+ this.container.appendChild(this.renderer.domElement);
33
+
34
+ // Add lights
35
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
36
+ this.scene.add(ambientLight);
37
+
38
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
39
+ directionalLight.position.set(10, 10, 10);
40
+ this.scene.add(directionalLight);
41
+
42
+ // Handle window resize
43
+ window.addEventListener('resize', () => this.onWindowResize());
44
+
45
+ return true;
46
+ }
47
+
48
+ checkWebGLSupport() {
49
+ try {
50
+ const canvas = document.createElement('canvas');
51
+ return !!(window.WebGLRenderingContext &&
52
+ (canvas.getContext('webgl') || canvas.getContext('experimental-webgl')));
53
+ } catch (e) {
54
+ return false;
55
+ }
56
+ }
57
+
58
+ showWebGLError() {
59
+ const errorDiv = document.createElement('div');
60
+ errorDiv.style.cssText = `
61
+ position: fixed;
62
+ top: 50%;
63
+ left: 50%;
64
+ transform: translate(-50%, -50%);
65
+ background: rgba(0, 0, 0, 0.9);
66
+ color: white;
67
+ padding: 30px;
68
+ border-radius: 10px;
69
+ text-align: center;
70
+ z-index: 10000;
71
+ `;
72
+ errorDiv.innerHTML = `
73
+ <h2>WebGL Not Available</h2>
74
+ <p>WebGL is required but not available. Please use a modern browser with WebGL support.</p>
75
+ <p><a href="https://get.webgl.org/" target="_blank" style="color: #4fc3f7;">Learn more</a></p>
76
+ `;
77
+ document.body.appendChild(errorDiv);
78
+ }
79
+
80
+ start() {
81
+ if (!this.renderer) return;
82
+
83
+ const animate = () => {
84
+ this.animationId = requestAnimationFrame(animate);
85
+ this.renderer.render(this.scene, this.camera);
86
+ };
87
+ animate();
88
+ }
89
+
90
+ stop() {
91
+ if (this.animationId) {
92
+ cancelAnimationFrame(this.animationId);
93
+ this.animationId = null;
94
+ }
95
+ }
96
+
97
+ onWindowResize() {
98
+ if (!this.camera || !this.renderer) return;
99
+
100
+ this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
101
+ this.camera.updateProjectionMatrix();
102
+ this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
103
+ }
104
+
105
+ getScene() {
106
+ return this.scene;
107
+ }
108
+
109
+ getCamera() {
110
+ return this.camera;
111
+ }
112
+
113
+ getRenderer() {
114
+ return this.renderer;
115
+ }
116
+ }
SpectrumDisplayComponent.js ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getSpectrumColor } from './wavelengthUtils.js';
2
+
3
+ export class SpectrumDisplayComponent {
4
+ constructor(dataManager) {
5
+ this.dataManager = dataManager;
6
+ this.infoPanel = document.getElementById('info-panel');
7
+ this.elementName = document.getElementById('element-name');
8
+ this.elementDetails = document.getElementById('element-details');
9
+ this.spectrumContainer = document.getElementById('spectrum-container');
10
+ }
11
+
12
+ showSpectrum(symbol) {
13
+ const elementData = this.dataManager.getElementData(symbol);
14
+
15
+ if (!elementData) {
16
+ this.showNoData(symbol);
17
+ return;
18
+ }
19
+
20
+ // Update element info
21
+ this.elementName.textContent = elementData.name;
22
+ this.elementDetails.textContent = `Symbol: ${elementData.symbol} | Atomic Number: ${elementData.atomicNumber}`;
23
+
24
+ // Create spectrum visualization
25
+ this.createSpectralLines(elementData.spectralLines);
26
+
27
+ // Show panel
28
+ this.infoPanel.classList.remove('hidden');
29
+ }
30
+
31
+ hideSpectrum() {
32
+ this.infoPanel.classList.add('hidden');
33
+ this.spectrumContainer.innerHTML = '';
34
+ }
35
+
36
+ createSpectralLines(lines) {
37
+ this.spectrumContainer.innerHTML = '';
38
+
39
+ if (!lines || lines.length === 0) {
40
+ this.spectrumContainer.innerHTML = '<p>No spectral data available</p>';
41
+ return;
42
+ }
43
+
44
+ // Sort by intensity and limit to top 20
45
+ const sortedLines = [...lines].sort((a, b) => (b.intensity || 0) - (a.intensity || 0));
46
+ const displayLines = sortedLines.slice(0, 20);
47
+
48
+ // Create canvas for spectrum
49
+ const canvas = document.createElement('canvas');
50
+ canvas.width = 360;
51
+ canvas.height = 200;
52
+ canvas.style.width = '100%';
53
+ canvas.style.border = '1px solid rgba(255, 255, 255, 0.2)';
54
+ canvas.style.borderRadius = '5px';
55
+
56
+ const ctx = canvas.getContext('2d');
57
+
58
+ // Draw background
59
+ ctx.fillStyle = '#000';
60
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
61
+
62
+ // Find wavelength range
63
+ const wavelengths = displayLines.map(l => l.wavelength);
64
+ const minWave = Math.min(...wavelengths, 380);
65
+ const maxWave = Math.max(...wavelengths, 750);
66
+ const range = maxWave - minWave;
67
+
68
+ // Draw wavelength scale
69
+ ctx.fillStyle = '#666';
70
+ ctx.font = '10px Arial';
71
+ ctx.textAlign = 'center';
72
+ for (let w = Math.ceil(minWave / 50) * 50; w <= maxWave; w += 50) {
73
+ const x = ((w - minWave) / range) * (canvas.width - 40) + 20;
74
+ ctx.fillRect(x, canvas.height - 20, 1, 10);
75
+ ctx.fillText(w + 'nm', x, canvas.height - 5);
76
+ }
77
+
78
+ // Draw spectral lines
79
+ displayLines.forEach(line => {
80
+ const { color, opacity } = getSpectrumColor(line.wavelength);
81
+ const x = ((line.wavelength - minWave) / range) * (canvas.width - 40) + 20;
82
+ const height = (line.intensity || 0.5) * 140;
83
+
84
+ ctx.fillStyle = `rgba(${Math.floor(color.r * 255)}, ${Math.floor(color.g * 255)}, ${Math.floor(color.b * 255)}, ${opacity})`;
85
+ ctx.fillRect(x - 2, canvas.height - 30 - height, 4, height);
86
+
87
+ // Draw wavelength label for prominent lines
88
+ if (line.intensity > 0.5) {
89
+ ctx.fillStyle = '#fff';
90
+ ctx.font = '9px Arial';
91
+ ctx.save();
92
+ ctx.translate(x, canvas.height - 35 - height);
93
+ ctx.rotate(-Math.PI / 2);
94
+ ctx.fillText(line.wavelength.toFixed(1), 0, 0);
95
+ ctx.restore();
96
+ }
97
+ });
98
+
99
+ this.spectrumContainer.appendChild(canvas);
100
+
101
+ // Add line details
102
+ const detailsDiv = document.createElement('div');
103
+ detailsDiv.style.marginTop = '10px';
104
+ detailsDiv.style.fontSize = '12px';
105
+ detailsDiv.style.maxHeight = '200px';
106
+ detailsDiv.style.overflowY = 'auto';
107
+
108
+ detailsDiv.innerHTML = '<h3 style="margin-bottom: 10px;">Spectral Lines:</h3>';
109
+
110
+ displayLines.forEach(line => {
111
+ const lineDiv = document.createElement('div');
112
+ lineDiv.style.marginBottom = '5px';
113
+ lineDiv.style.padding = '5px';
114
+ lineDiv.style.background = 'rgba(255, 255, 255, 0.05)';
115
+ lineDiv.style.borderRadius = '3px';
116
+
117
+ const { color } = getSpectrumColor(line.wavelength);
118
+ const colorBox = `<span style="display: inline-block; width: 12px; height: 12px; background: rgb(${Math.floor(color.r * 255)}, ${Math.floor(color.g * 255)}, ${Math.floor(color.b * 255)}); border: 1px solid #fff; margin-right: 5px;"></span>`;
119
+
120
+ lineDiv.innerHTML = `${colorBox}${line.wavelength.toFixed(1)} nm (Intensity: ${(line.intensity || 0).toFixed(2)})${line.transition ? ' - ' + line.transition : ''}`;
121
+ detailsDiv.appendChild(lineDiv);
122
+ });
123
+
124
+ this.spectrumContainer.appendChild(detailsDiv);
125
+ }
126
+
127
+ showNoData(symbol) {
128
+ this.elementName.textContent = symbol;
129
+ this.elementDetails.textContent = 'Element information';
130
+ this.spectrumContainer.innerHTML = '<p>Spectral data not available for this element</p>';
131
+ this.infoPanel.classList.remove('hidden');
132
+ }
133
+ }
all-elements-data.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Complete periodic table data with cosmic origins and spectral lines
2
+ // This will be integrated into index.html
3
+
4
+ const ALL_ELEMENTS = [
5
+ // Period 1
6
+ { symbol: "H", name: "Hydrogen", atomicNumber: 1, category: "nonmetal", row: 1, col: 1,
7
+ cosmicOrigin: "Big Bang", originColor: "#FFD700",
8
+ astronomyInfo: "The most abundant element in the universe, created during the Big Bang 13.8 billion years ago. Hydrogen makes up about 75% of all normal matter and is the fuel that powers stars through nuclear fusion.",
9
+ spectralLines: [{ wavelength: 656.3, intensity: 1.0 }, { wavelength: 486.1, intensity: 0.5 }, { wavelength: 434.0, intensity: 0.3 }, { wavelength: 410.2, intensity: 0.2 }]},
10
+
11
+ { symbol: "He", name: "Helium", atomicNumber: 2, category: "noble-gas", row: 1, col: 18,
12
+ cosmicOrigin: "Big Bang", originColor: "#FFD700",
13
+ astronomyInfo: "The second most abundant element in the universe, primarily created during the Big Bang. Also produced by nuclear fusion in stars.",
14
+ spectralLines: [{ wavelength: 587.6, intensity: 1.0 }, { wavelength: 667.8, intensity: 0.3 }, { wavelength: 501.6, intensity: 0.4 }]},
15
+
16
+ // Period 2
17
+ { symbol: "Li", name: "Lithium", atomicNumber: 3, category: "alkali-metal", row: 2, col: 1,
18
+ cosmicOrigin: "Cosmic Ray & Dying Stars", originColor: "#9370DB",
19
+ astronomyInfo: "Created through multiple processes: some from the Big Bang, most from dying low-mass stars, and some isotopes from cosmic ray collisions.",
20
+ spectralLines: [{ wavelength: 670.8, intensity: 1.0 }, { wavelength: 610.4, intensity: 0.3 }]},
21
+
22
+ { symbol: "Be", name: "Beryllium", atomicNumber: 4, category: "alkaline-earth", row: 2, col: 2,
23
+ cosmicOrigin: "Cosmic Ray Fission", originColor: "#9370DB",
24
+ astronomyInfo: "Created by cosmic ray spallation - when high-energy cosmic rays collide with heavier elements in space, breaking them apart.",
25
+ spectralLines: [{ wavelength: 234.9, intensity: 1.0 }, { wavelength: 313.0, intensity: 0.4 }]},
element-science-data.txt ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Chemistry and Physics data for all elements to be added:
2
+
3
+ H: chemistryInfo: "The simplest element with one proton and one electron. Forms covalent bonds in H₂ molecules. Essential for water (H₂O), acids, and all organic compounds. Has three isotopes: protium, deuterium, and tritium."
4
+ physicsInfo: "Atomic mass: 1.008 u. Ionization energy: 13.6 eV. The only atom with exact analytical solution to Schrödinger equation. Melting point: -259°C, Boiling point: -253°C."
5
+
6
+ He: chemistryInfo: "Noble gas with complete electron shell, chemically inert. Does not form compounds under normal conditions. Lowest boiling point (-269°C). Used in balloons, diving, and cryogenics."
7
+ physicsInfo: "Atomic mass: 4.003 u. Ionization energy: 24.6 eV (highest). Exhibits superfluidity below 2.17 K. Two stable isotopes: ³He and ⁴He."
8
+
9
+ Li: chemistryInfo: "Highly reactive alkali metal, stored in oil. Forms ionic compounds with halogens. Lightest metal and least dense solid. Used in rechargeable batteries and psychiatric medication."
10
+ physicsInfo: "Atomic mass: 6.94 u. Melting point: 180.5°C. Highest specific heat of any solid. Two stable isotopes: ⁶Li and ⁷Li. Excellent conductor."
11
+
12
+ Be: chemistryInfo: "Alkaline earth metal, forms strong covalent bonds. Toxic and carcinogenic. Used in aerospace alloys and X-ray windows. Forms Be²⁺ ions."
13
+ physicsInfo: "Atomic mass: 9.012 u. Melting point: 1287°C. High stiffness-to-weight ratio. Transparent to X-rays. One stable isotope: ⁹Be."
14
+
15
+ C: chemistryInfo: "Basis of organic chemistry, forms 4 covalent bonds. Exists as diamond, graphite, and fullerenes. Essential for all known life. Forms millions of compounds."
16
+ physicsInfo: "Atomic mass: 12.011 u. Sublimes at 3642°C. Diamond is hardest natural material. Graphene has highest strength. Isotopes: ¹²C, ¹³C, ¹⁴C (radioactive)."
17
+
18
+ N: chemistryInfo: "Diatomic gas (N₂) with triple bond, very stable. Essential for proteins and DNA. Forms nitrates and ammonia. Makes up 78% of Earth's atmosphere."
19
+ physicsInfo: "Atomic mass: 14.007 u. Boiling point: -196°C. Triple bond energy: 945 kJ/mol. Two stable isotopes: ¹⁴N, ¹⁵N."
20
+
21
+ O: chemistryInfo: "Highly reactive, forms oxides with most elements. Essential for respiration. Exists as O₂ and O₃ (ozone). Second most electronegative element."
22
+ physicsInfo: "Atomic mass: 15.999 u. Boiling point: -183°C. Paramagnetic in liquid form. Three stable isotopes: ¹⁶O, ¹⁷O, ¹⁸O."
23
+
24
+ F: chemistryInfo: "Most electronegative and reactive element. Forms fluorides with all elements except He, Ne, Ar. Used in toothpaste and Teflon. Highly toxic."
25
+ physicsInfo: "Atomic mass: 18.998 u. Boiling point: -188°C. Electronegativity: 4.0 (highest). One stable isotope: ¹⁹F."
26
+
27
+ Ne: chemistryInfo: "Noble gas, chemically inert. No known compounds. Used in neon signs (red-orange glow), lasers, and cryogenic refrigeration."
28
+ physicsInfo: "Atomic mass: 20.180 u. Boiling point: -246°C. Three stable isotopes: ²⁰Ne, ²¹Ne, ²²Ne. Ionization energy: 21.6 eV."
29
+
30
+ Na: chemistryInfo: "Highly reactive alkali metal, reacts violently with water. Forms Na⁺ ions. Essential for nerve function. Found in table salt (NaCl)."
31
+ physicsInfo: "Atomic mass: 22.990 u. Melting point: 98°C. Soft enough to cut with knife. One stable isotope: ²³Na. Excellent conductor."
elementLayout.js ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const ELEMENT_LAYOUT = {
2
+ tileSize: 1.0,
3
+ spacing: 0.1,
4
+
5
+ // Simplified layout for the 10 elements we have data for
6
+ positions: {
7
+ "H": { row: 1, column: 1 },
8
+ "He": { row: 1, column: 18 },
9
+ "Li": { row: 2, column: 1 },
10
+ "C": { row: 2, column: 14 },
11
+ "N": { row: 2, column: 15 },
12
+ "O": { row: 2, column: 16 },
13
+ "Na": { row: 3, column: 1 },
14
+ "Fe": { row: 4, column: 8 },
15
+ "Cu": { row: 4, column: 11 },
16
+ "Au": { row: 6, column: 11 }
17
+ }
18
+ };
19
+
20
+ export const ELEMENT_COLORS = {
21
+ "metal": 0x4A90E2,
22
+ "nonmetal": 0x7ED321,
23
+ "metalloid": 0xF5A623,
24
+ "noble-gas": 0xBD10E0,
25
+ "alkali-metal": 0xFF6B6B,
26
+ "alkaline-earth": 0xFFA07A,
27
+ "transition-metal": 0x87CEEB,
28
+ "lanthanide": 0xFFD700,
29
+ "actinide": 0xFF69B4,
30
+ "halogen": 0x50E3C2,
31
+ "unknown": 0xCCCCCC
32
+ };
33
+
34
+ export function getElementPosition(symbol) {
35
+ const pos = ELEMENT_LAYOUT.positions[symbol];
36
+ if (!pos) return null;
37
+
38
+ const tileSize = ELEMENT_LAYOUT.tileSize;
39
+ const spacing = ELEMENT_LAYOUT.spacing;
40
+ const unit = tileSize + spacing;
41
+
42
+ // Center the periodic table
43
+ const x = (pos.column - 9.5) * unit;
44
+ const y = -(pos.row - 3.5) * unit;
45
+ const z = 0;
46
+
47
+ return { x, y, z, row: pos.row, column: pos.column };
48
+ }
index.html CHANGED
The diff for this file is too large to render. See raw diff
 
main.js ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { SceneManager } from './SceneManager.js';
2
+ import { DataManager } from './DataManager.js';
3
+ import { PeriodicTableComponent } from './PeriodicTableComponent.js';
4
+ import { SpectrumDisplayComponent } from './SpectrumDisplayComponent.js';
5
+ import { InteractionController } from './InteractionController.js';
6
+
7
+ async function init() {
8
+ const loadingDiv = document.getElementById('loading');
9
+ const appContainer = document.getElementById('app-container');
10
+
11
+ try {
12
+ // Initialize DataManager and load spectral data
13
+ const dataManager = new DataManager();
14
+ await dataManager.loadSpectralData('spectral-data.json');
15
+
16
+ // Initialize SceneManager
17
+ const container = document.getElementById('canvas-container');
18
+ const sceneManager = new SceneManager(container);
19
+
20
+ if (!sceneManager.initialize()) {
21
+ throw new Error('Failed to initialize WebGL');
22
+ }
23
+
24
+ // Create PeriodicTableComponent
25
+ const periodicTable = new PeriodicTableComponent(
26
+ sceneManager.getScene(),
27
+ dataManager
28
+ );
29
+ periodicTable.createPeriodicTable();
30
+
31
+ // Create SpectrumDisplayComponent
32
+ const spectrumDisplay = new SpectrumDisplayComponent(dataManager);
33
+
34
+ // Create InteractionController
35
+ const interactionController = new InteractionController(
36
+ sceneManager,
37
+ periodicTable,
38
+ spectrumDisplay
39
+ );
40
+ interactionController.initialize();
41
+
42
+ // Start animation loop
43
+ sceneManager.start();
44
+
45
+ // Hide loading indicator
46
+ loadingDiv.style.display = 'none';
47
+ appContainer.style.display = 'block';
48
+
49
+ } catch (error) {
50
+ console.error('Initialization error:', error);
51
+ loadingDiv.innerHTML = `
52
+ <div style="color: #ff6b6b;">
53
+ <h2>Failed to Load Application</h2>
54
+ <p>${error.message}</p>
55
+ <p>Please refresh the page to try again.</p>
56
+ </div>
57
+ `;
58
+ }
59
+ }
60
+
61
+ // Start the application when DOM is ready
62
+ if (document.readyState === 'loading') {
63
+ document.addEventListener('DOMContentLoaded', init);
64
+ } else {
65
+ init();
66
+ }
spectral-data.json ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "elements": [
3
+ {
4
+ "symbol": "H",
5
+ "name": "Hydrogen",
6
+ "atomicNumber": 1,
7
+ "category": "nonmetal",
8
+ "spectralLines": [
9
+ { "wavelength": 656.3, "intensity": 1.0, "transition": "3→2 (Hα)" },
10
+ { "wavelength": 486.1, "intensity": 0.5, "transition": "4→2 (Hβ)" },
11
+ { "wavelength": 434.0, "intensity": 0.3, "transition": "5→2 (Hγ)" },
12
+ { "wavelength": 410.2, "intensity": 0.2, "transition": "6→2 (Hδ)" }
13
+ ]
14
+ },
15
+ {
16
+ "symbol": "He",
17
+ "name": "Helium",
18
+ "atomicNumber": 2,
19
+ "category": "noble-gas",
20
+ "spectralLines": [
21
+ { "wavelength": 587.6, "intensity": 1.0 },
22
+ { "wavelength": 667.8, "intensity": 0.3 },
23
+ { "wavelength": 501.6, "intensity": 0.4 },
24
+ { "wavelength": 447.1, "intensity": 0.2 }
25
+ ]
26
+ },
27
+ {
28
+ "symbol": "Li",
29
+ "name": "Lithium",
30
+ "atomicNumber": 3,
31
+ "category": "alkali-metal",
32
+ "spectralLines": [
33
+ { "wavelength": 670.8, "intensity": 1.0 },
34
+ { "wavelength": 610.4, "intensity": 0.3 }
35
+ ]
36
+ },
37
+ {
38
+ "symbol": "C",
39
+ "name": "Carbon",
40
+ "atomicNumber": 6,
41
+ "category": "nonmetal",
42
+ "spectralLines": [
43
+ { "wavelength": 247.9, "intensity": 1.0 },
44
+ { "wavelength": 426.7, "intensity": 0.4 },
45
+ { "wavelength": 538.0, "intensity": 0.3 }
46
+ ]
47
+ },
48
+ {
49
+ "symbol": "N",
50
+ "name": "Nitrogen",
51
+ "atomicNumber": 7,
52
+ "category": "nonmetal",
53
+ "spectralLines": [
54
+ { "wavelength": 399.5, "intensity": 0.8 },
55
+ { "wavelength": 500.5, "intensity": 0.5 },
56
+ { "wavelength": 567.6, "intensity": 0.3 }
57
+ ]
58
+ },
59
+ {
60
+ "symbol": "O",
61
+ "name": "Oxygen",
62
+ "atomicNumber": 8,
63
+ "category": "nonmetal",
64
+ "spectralLines": [
65
+ { "wavelength": 777.4, "intensity": 1.0 },
66
+ { "wavelength": 844.6, "intensity": 0.7 },
67
+ { "wavelength": 615.8, "intensity": 0.4 }
68
+ ]
69
+ },
70
+ {
71
+ "symbol": "Na",
72
+ "name": "Sodium",
73
+ "atomicNumber": 11,
74
+ "category": "alkali-metal",
75
+ "spectralLines": [
76
+ { "wavelength": 589.0, "intensity": 1.0 },
77
+ { "wavelength": 589.6, "intensity": 0.9 },
78
+ { "wavelength": 568.3, "intensity": 0.2 }
79
+ ]
80
+ },
81
+ {
82
+ "symbol": "Fe",
83
+ "name": "Iron",
84
+ "atomicNumber": 26,
85
+ "category": "transition-metal",
86
+ "spectralLines": [
87
+ { "wavelength": 438.4, "intensity": 0.9 },
88
+ { "wavelength": 440.5, "intensity": 0.8 },
89
+ { "wavelength": 495.8, "intensity": 0.6 },
90
+ { "wavelength": 526.9, "intensity": 0.7 },
91
+ { "wavelength": 532.8, "intensity": 0.5 }
92
+ ]
93
+ },
94
+ {
95
+ "symbol": "Cu",
96
+ "name": "Copper",
97
+ "atomicNumber": 29,
98
+ "category": "transition-metal",
99
+ "spectralLines": [
100
+ { "wavelength": 324.8, "intensity": 1.0 },
101
+ { "wavelength": 327.4, "intensity": 0.9 },
102
+ { "wavelength": 510.6, "intensity": 0.4 },
103
+ { "wavelength": 521.8, "intensity": 0.5 }
104
+ ]
105
+ },
106
+ {
107
+ "symbol": "Au",
108
+ "name": "Gold",
109
+ "atomicNumber": 79,
110
+ "category": "transition-metal",
111
+ "spectralLines": [
112
+ { "wavelength": 267.6, "intensity": 1.0 },
113
+ { "wavelength": 312.3, "intensity": 0.7 },
114
+ { "wavelength": 479.3, "intensity": 0.3 }
115
+ ]
116
+ }
117
+ ]
118
+ }
styles.css ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
9
+ background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
10
+ color: #fff;
11
+ overflow: hidden;
12
+ }
13
+
14
+ #loading {
15
+ position: fixed;
16
+ top: 0;
17
+ left: 0;
18
+ width: 100%;
19
+ height: 100%;
20
+ background: rgba(0, 0, 0, 0.9);
21
+ display: flex;
22
+ flex-direction: column;
23
+ justify-content: center;
24
+ align-items: center;
25
+ z-index: 1000;
26
+ }
27
+
28
+ .spinner {
29
+ width: 50px;
30
+ height: 50px;
31
+ border: 5px solid rgba(255, 255, 255, 0.3);
32
+ border-top-color: #fff;
33
+ border-radius: 50%;
34
+ animation: spin 1s linear infinite;
35
+ }
36
+
37
+ @keyframes spin {
38
+ to { transform: rotate(360deg); }
39
+ }
40
+
41
+ #loading p {
42
+ margin-top: 20px;
43
+ font-size: 18px;
44
+ }
45
+
46
+ #app-container {
47
+ width: 100vw;
48
+ height: 100vh;
49
+ position: relative;
50
+ }
51
+
52
+ #canvas-container {
53
+ width: 100%;
54
+ height: 100%;
55
+ }
56
+
57
+ #info-panel {
58
+ position: absolute;
59
+ top: 20px;
60
+ right: 20px;
61
+ background: rgba(0, 0, 0, 0.8);
62
+ padding: 20px;
63
+ border-radius: 10px;
64
+ max-width: 400px;
65
+ max-height: 80vh;
66
+ overflow-y: auto;
67
+ backdrop-filter: blur(10px);
68
+ border: 1px solid rgba(255, 255, 255, 0.2);
69
+ }
70
+
71
+ #info-panel.hidden {
72
+ display: none;
73
+ }
74
+
75
+ #info-panel h2 {
76
+ margin-bottom: 10px;
77
+ color: #4fc3f7;
78
+ }
79
+
80
+ #element-details {
81
+ margin-bottom: 20px;
82
+ font-size: 14px;
83
+ color: #ccc;
84
+ }
85
+
86
+ #spectrum-container {
87
+ margin-top: 20px;
88
+ }
89
+
90
+ #controls-info {
91
+ position: absolute;
92
+ bottom: 60px;
93
+ left: 50%;
94
+ transform: translateX(-50%);
95
+ background: rgba(0, 0, 0, 0.7);
96
+ padding: 10px 20px;
97
+ border-radius: 5px;
98
+ font-size: 14px;
99
+ text-align: center;
100
+ }
101
+
102
+ footer {
103
+ position: absolute;
104
+ bottom: 10px;
105
+ left: 50%;
106
+ transform: translateX(-50%);
107
+ font-size: 12px;
108
+ text-align: center;
109
+ }
110
+
111
+ footer a {
112
+ color: #4fc3f7;
113
+ text-decoration: none;
114
+ }
115
+
116
+ footer a:hover {
117
+ text-decoration: underline;
118
+ }
119
+
120
+ .hidden {
121
+ display: none;
122
+ }
wavelengthUtils.js ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as THREE from 'three';
2
+
3
+ export function wavelengthToRGB(wavelength) {
4
+ let r, g, b;
5
+
6
+ if (wavelength >= 380 && wavelength < 440) {
7
+ r = -(wavelength - 440) / (440 - 380);
8
+ g = 0.0;
9
+ b = 1.0;
10
+ } else if (wavelength >= 440 && wavelength < 490) {
11
+ r = 0.0;
12
+ g = (wavelength - 440) / (490 - 440);
13
+ b = 1.0;
14
+ } else if (wavelength >= 490 && wavelength < 510) {
15
+ r = 0.0;
16
+ g = 1.0;
17
+ b = -(wavelength - 510) / (510 - 490);
18
+ } else if (wavelength >= 510 && wavelength < 580) {
19
+ r = (wavelength - 510) / (580 - 510);
20
+ g = 1.0;
21
+ b = 0.0;
22
+ } else if (wavelength >= 580 && wavelength < 645) {
23
+ r = 1.0;
24
+ g = -(wavelength - 645) / (645 - 580);
25
+ b = 0.0;
26
+ } else if (wavelength >= 645 && wavelength <= 750) {
27
+ r = 1.0;
28
+ g = 0.0;
29
+ b = 0.0;
30
+ } else {
31
+ r = 0.0;
32
+ g = 0.0;
33
+ b = 0.0;
34
+ }
35
+
36
+ // Apply intensity correction for edge wavelengths
37
+ let factor;
38
+ if (wavelength >= 380 && wavelength < 420) {
39
+ factor = 0.3 + 0.7 * (wavelength - 380) / (420 - 380);
40
+ } else if (wavelength >= 420 && wavelength <= 700) {
41
+ factor = 1.0;
42
+ } else if (wavelength > 700 && wavelength <= 750) {
43
+ factor = 0.3 + 0.7 * (750 - wavelength) / (750 - 700);
44
+ } else {
45
+ factor = 0.0;
46
+ }
47
+
48
+ // Apply gamma correction
49
+ const gamma = 0.80;
50
+ r = Math.pow(r * factor, gamma);
51
+ g = Math.pow(g * factor, gamma);
52
+ b = Math.pow(b * factor, gamma);
53
+
54
+ return new THREE.Color(r, g, b);
55
+ }
56
+
57
+ export function getSpectrumColor(wavelength) {
58
+ if (wavelength < 380) {
59
+ // UV - use violet with reduced opacity
60
+ return { color: new THREE.Color(0x9400D3), opacity: 0.5 };
61
+ } else if (wavelength > 750) {
62
+ // IR - use dark red with reduced opacity
63
+ return { color: new THREE.Color(0x8B0000), opacity: 0.5 };
64
+ } else {
65
+ // Visible spectrum
66
+ return { color: wavelengthToRGB(wavelength), opacity: 1.0 };
67
+ }
68
+ }