3D_slit_simulation / index.html
AK51's picture
Upload 8 files
1462805 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Slit Experiment Simulator</title>
<style>
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
width: 100%;
height: 100%;
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
background: linear-gradient(135deg, #0f0c29 0%, #1a1a3e 40%, #24243e 100%);
color: #e8e8f0;
overflow: hidden;
-webkit-text-size-adjust: 100%;
touch-action: manipulation;
}
#app {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
overflow: hidden;
}
#viewport-row {
display: flex;
flex-direction: row;
flex: 1;
min-height: 0;
overflow: hidden;
}
#scene-container {
flex: 1;
min-height: 150px;
background: linear-gradient(180deg, #1a1a30 0%, #252540 100%);
position: relative;
overflow: hidden;
touch-action: none;
}
#intensity-container {
width: 200px;
background: linear-gradient(180deg, #12122a 0%, #1a1a30 100%);
border-left: 1px solid rgba(100, 140, 255, 0.2);
position: relative;
overflow: hidden;
flex-shrink: 0;
}
#control-container {
width: 100%;
background: linear-gradient(180deg, #161630 0%, #0e0e24 100%);
border-top: 2px solid rgba(100, 140, 255, 0.2);
padding: 8px 10px;
overflow-x: auto;
overflow-y: auto;
max-height: 40vh;
flex-shrink: 0;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.3);
-webkit-overflow-scrolling: touch;
}
#error-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #0f0c29;
color: #e0e0e0;
z-index: 9999;
justify-content: center;
align-items: center;
text-align: center;
padding: 2rem;
font-size: 1.2rem;
}
#error-overlay p {
max-width: 500px;
line-height: 1.6;
}
@media (min-width: 1024px) {
#intensity-container {
width: 240px;
}
}
/* Mobile: narrower intensity panel, compact controls */
@media (max-width: 768px) {
#intensity-container {
width: 120px;
}
#scene-container {
min-height: 120px;
}
#control-container {
padding: 6px 8px;
max-height: 50vh;
}
}
</style>
</head>
<body>
<div id="app">
<div id="viewport-row">
<div id="scene-container">
<div style="position:absolute;top:6px;left:10px;font-size:clamp(13px,2.5vw,20px);font-weight:700;color:#fff;pointer-events:none;z-index:10;text-shadow:0 2px 8px rgba(80,120,255,0.4),0 1px 3px rgba(0,0,0,0.6);letter-spacing:0.5px;">🌊 3D Slit Simulation</div>
<div id="view-buttons" style="position:absolute;top:30px;left:10px;display:flex;gap:4px;z-index:10;flex-wrap:wrap;">
<button id="btn-top-view" style="padding:3px 8px;border:1px solid rgba(100,140,255,0.3);border-radius:5px;background:rgba(100,140,255,0.2);color:#c0ccff;font-size:clamp(9px,1.5vw,11px);cursor:pointer;font-weight:600;white-space:nowrap;">Top View</button>
<button id="btn-angle-view" style="padding:3px 8px;border:1px solid rgba(100,140,255,0.3);border-radius:5px;background:rgba(255,255,255,0.05);color:#8899bb;font-size:clamp(9px,1.5vw,11px);cursor:pointer;font-weight:600;white-space:nowrap;">45° View</button>
<button id="btn-135-view" style="padding:3px 8px;border:1px solid rgba(100,140,255,0.3);border-radius:5px;background:rgba(255,255,255,0.05);color:#8899bb;font-size:clamp(9px,1.5vw,11px);cursor:pointer;font-weight:600;white-space:nowrap;">135° View</button>
</div>
</div>
<div id="intensity-container"></div>
</div>
<div id="control-container">
<div id="control-buttons" style="display:flex;align-items:center;justify-content:center;gap:6px;padding:6px 0;"></div>
<div id="control-toggle" style="display:none;text-align:center;padding:4px 0;cursor:pointer;color:#8899cc;font-size:12px;font-weight:600;user-select:none;touch-action:manipulation;">▼ Sliders ▼</div>
<div id="control-inner"></div>
</div>
</div>
<div id="error-overlay">
<p>Required libraries could not be loaded. Please check your internet connection and try again.</p>
</div>
<!-- About modal -->
<div id="about-overlay" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);z-index:9998;justify-content:center;align-items:center;">
<div style="background:linear-gradient(135deg, #1a1a3e 0%, #252550 100%);border:1px solid rgba(100,140,255,0.3);border-radius:14px;max-width:560px;max-height:85vh;overflow-y:auto;padding:20px 24px;color:#d0d4e8;font-size:13px;line-height:1.7;position:relative;box-shadow:0 8px 40px rgba(0,0,0,0.5);margin:10px;width:calc(100% - 20px);">
<button id="about-close" style="position:absolute;top:10px;right:14px;background:none;border:none;color:#8899cc;font-size:20px;cursor:pointer;"></button>
<h2 style="margin:0 0 12px;font-size:20px;color:#fff;text-shadow:0 1px 6px rgba(80,120,255,0.3);">🌊 3D Slit Simulation</h2>
<p>This simulator models wave propagation through single and double slits using a 2D FDTD (Finite-Difference Time-Domain) solver. The wave field is rendered as a 3D height-mapped surface with amplitude-based coloring — warm tones for peaks, cool tones for troughs.</p>
<p>A planar wave source on the left emits coherent waves toward a barrier with configurable slit openings. As waves pass through the slits, they diffract and interfere, producing characteristic patterns on the 3D surface and the 2D intensity panel.</p>
<p style="color:#aabbdd;"><span style="color:#ffcc44;"></span> <span style="color:#00cc44;"></span> <b style="color:#c0ccff;">Polarization (Light mode):</b> In double-slit mode with Light selected, you can choose between Same (∥) and Orthogonal (⊥) polarization. With same polarization, waves from both slits interfere normally, producing bright fringes. With orthogonal polarization, each slit emits a different polarization component — these cannot interfere, so the fringe pattern disappears and you see only two overlapping diffraction envelopes.</p>
<p style="color:#aabbdd;"><span style="color:#4488ff;"></span> <b style="color:#c0ccff;">45° Polarizer:</b> When orthogonal polarization is active, enabling the 45° polarizer places a diagonal filter between the slits and the detector. This filter projects both orthogonal components onto the same axis: E<sub>out</sub> = (E₁ + E₂)/√2. Since both components now share the same polarization direction, they can interfere again — and the fringe pattern reappears, just like same-polarization mode. This demonstrates a key principle of quantum optics: erasing "which-path" information restores interference.</p>
<p>The green detector screen samples the wave amplitude along a vertical line. The 2D panel shows the live waveform (green) and the sliding-window averaged intensity (red).</p>
<p style="margin-top:16px;color:#8899aa;font-style:italic;text-align:right;">Created by Andy Kong</p>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var overlay = document.getElementById('about-overlay');
var closeBtn = document.getElementById('about-close');
if (closeBtn) closeBtn.addEventListener('click', function() { overlay.style.display = 'none'; });
overlay.addEventListener('click', function(e) { if (e.target === overlay) overlay.style.display = 'none'; });
// Collapsible sliders on mobile (buttons stay visible)
var toggle = document.getElementById('control-toggle');
var inner = document.getElementById('control-inner');
function checkMobile() {
if (window.innerWidth < 768) {
toggle.style.display = 'block';
inner.style.display = 'none';
toggle.textContent = '▼ Sliders ▼';
} else {
toggle.style.display = 'none';
inner.style.display = '';
}
}
if (toggle && inner) {
checkMobile();
window.addEventListener('resize', checkMobile);
toggle.addEventListener('click', function() {
if (inner.style.display === 'none') {
inner.style.display = '';
toggle.textContent = '▲ Sliders ▲';
} else {
inner.style.display = 'none';
toggle.textContent = '▼ Sliders ▼';
}
});
}
});
</script>
<!-- Three.js via ES Module importmap -->
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
<!-- Bootstrap: import THREE + OrbitControls as ES modules, expose as globals, then load app -->
<script type="module">
import * as THREE_MODULE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// Expose as a mutable global so plain-script app modules can use them
const THREE = Object.assign({}, THREE_MODULE);
THREE.OrbitControls = OrbitControls;
window.THREE = THREE;
// CDN load error detection
if (typeof THREE === 'undefined') {
document.getElementById('error-overlay').style.display = 'flex';
}
// Dynamically load application scripts in order after THREE is available
const scripts = [
'fdtd-solver.js',
'scene-manager.js',
'wave-surface.js',
'intensity-panel.js',
'controls.js',
'main.js'
];
for (const src of scripts) {
await new Promise((resolve, reject) => {
const s = document.createElement('script');
s.src = src;
s.onload = resolve;
s.onerror = reject;
document.body.appendChild(s);
});
}
</script>
</body>
</html>