nrs / index.html
dxlorhuggingface's picture
Update index.html
785383f verified
raw
history blame
55 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nuclear Reactor Simulator</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Roboto+Mono:wght@400;500;700&display=swap');
:root {
--bg: #0a0a0f;
--primary: #00b4ff;
--caution: #ffab00;
--alarm: #ff3b30;
--coolant: #4df0ff;
--graphite: #444;
--glow: #00d1ff33;
--success: #32d74b;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: var(--bg);
color: var(--primary);
font-family: 'Roboto Mono', monospace;
overflow: hidden;
height: 100vh;
position: relative;
}
.container {
display: flex;
height: 100vh;
}
.control-panel {
width: 35%;
background: linear-gradient(135deg, #0d0d1a, #1a1a2e);
border-right: 2px solid var(--primary);
padding: 20px;
overflow-y: auto;
position: relative;
}
.reactor-cavity {
width: 65%;
background: radial-gradient(ellipse at center, #0f0f1f, var(--bg));
position: relative;
overflow: hidden;
}
.panel-header {
font-family: 'Orbitron', sans-serif;
font-weight: 700;
font-size: 1.2em;
color: var(--primary);
margin-bottom: 20px;
text-align: center;
text-shadow: 0 0 10px var(--glow);
}
.control-group {
margin-bottom: 20px;
background: rgba(0, 180, 255, 0.05);
border: 1px solid rgba(0, 180, 255, 0.3);
border-radius: 8px;
padding: 15px;
}
.control-label {
font-size: 0.9em;
margin-bottom: 8px;
color: var(--coolant);
text-transform: uppercase;
letter-spacing: 1px;
}
.slider-container {
position: relative;
height: 30px;
background: var(--graphite);
border-radius: 15px;
margin: 10px 0;
}
.slider {
width: 100%;
height: 100%;
background: transparent;
outline: none;
border: none;
border-radius: 15px;
-webkit-appearance: none;
appearance: none;
cursor: pointer;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 25px;
height: 25px;
background: var(--primary);
border-radius: 50%;
cursor: pointer;
box-shadow: 0 0 15px var(--glow);
border: 2px solid var(--coolant);
}
.slider::-moz-range-thumb {
width: 25px;
height: 25px;
background: var(--primary);
border-radius: 50%;
cursor: pointer;
box-shadow: 0 0 15px var(--glow);
border: 2px solid var(--coolant);
}
.power-bar {
height: 200px;
width: 40px;
background: var(--graphite);
border-radius: 20px;
position: relative;
margin: 0 auto;
border: 2px solid var(--primary);
}
.power-fill {
position: absolute;
bottom: 2px;
left: 2px;
right: 2px;
background: linear-gradient(0deg, var(--primary), var(--coolant));
border-radius: 18px;
transition: height 0.3s ease;
box-shadow: 0 0 20px var(--glow);
height: 0%;
}
.gauge {
width: 100px;
height: 100px;
margin: 10px auto;
position: relative;
background: radial-gradient(circle, var(--graphite), #222);
border-radius: 50%;
border: 3px solid var(--primary);
}
.gauge-needle {
position: absolute;
top: 50%;
left: 50%;
width: 2px;
height: 40px;
background: var(--alarm);
transform-origin: bottom center;
transform: translate(-50%, -100%) rotate(-90deg);
transition: transform 0.3s ease;
box-shadow: 0 0 10px var(--alarm);
}
.action-button {
background: var(--primary);
color: var(--bg);
border: none;
padding: 12px 20px;
border-radius: 8px;
font-family: 'Orbitron', sans-serif;
font-weight: 700;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
margin: 5px;
font-size: 12px;
display: inline-block;
min-width: 120px;
}
.action-button:hover {
background: var(--coolant);
box-shadow: 0 0 20px var(--glow);
transform: scale(1.02);
}
.action-button.caution {
background: var(--caution);
}
.action-button.caution:hover {
background: #ff8f00;
}
.action-button.success {
background: var(--success);
}
.action-button.success:hover {
background: #28a745;
}
.scram-button {
background: var(--alarm);
color: white;
border: none;
padding: 15px 25px;
border-radius: 10px;
font-family: 'Orbitron', sans-serif;
font-weight: 700;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 2px;
margin: 20px auto;
display: block;
width: 100%;
font-size: 16px;
}
.scram-button:hover {
background: #ff1a1a;
box-shadow: 0 0 30px var(--alarm);
transform: scale(1.05);
}
.emergency-controls {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-top: 15px;
}
.annunciator-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 5px;
margin: 15px 0;
}
.annunciator {
width: 25px;
height: 25px;
background: var(--graphite);
border-radius: 3px;
border: 1px solid var(--primary);
transition: all 0.3s ease;
position: relative;
}
.annunciator.active {
background: var(--alarm);
box-shadow: 0 0 10px var(--alarm);
animation: blink 1s infinite;
}
.annunciator.caution {
background: var(--caution);
box-shadow: 0 0 10px var(--caution);
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0.3; }
}
.reactor-3d {
width: 100%;
height: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.reactor-core {
width: 300px;
height: 400px;
background: linear-gradient(45deg, var(--graphite), #666);
border-radius: 20px;
border: 2px solid var(--primary);
overflow: hidden;
box-shadow: 0 0 50px var(--glow);
position: relative;
}
.fuel-assembly-grid {
display: grid;
grid-template-columns: repeat(17, 1fr);
grid-template-rows: repeat(17, 1fr);
height: 100%;
padding: 10px;
gap: 1px;
}
.fuel-pin {
background: var(--primary);
border-radius: 1px;
transition: all 0.3s ease;
opacity: 0.3;
}
.fuel-pin.hot {
background: var(--coolant);
box-shadow: 0 0 3px var(--coolant);
opacity: 1;
}
.control-rod {
position: absolute;
width: 8px;
height: 300px;
background: linear-gradient(0deg, var(--graphite), #666);
border: 1px solid var(--primary);
left: 50%;
top: 10px;
transform: translateX(-50%);
transition: top 0.5s ease;
border-radius: 4px;
}
.coolant-loops {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.coolant-loop {
position: absolute;
width: 60px;
height: 180px;
border: 4px solid var(--coolant);
border-radius: 30px;
background: transparent;
}
.loop-1 { top: 50px; left: -40px; }
.loop-2 { top: 50px; right: -40px; }
.loop-3 { bottom: 50px; left: -40px; }
.loop-4 { bottom: 50px; right: -40px; }
.coolant-particle {
position: absolute;
width: 4px;
height: 8px;
background: var(--coolant);
border-radius: 2px;
opacity: 0.8;
}
.readout {
font-family: 'Roboto Mono', monospace;
font-size: 0.9em;
color: var(--coolant);
text-align: center;
background: rgba(0, 0, 0, 0.8);
padding: 5px 8px;
border-radius: 5px;
border: 1px solid var(--primary);
margin-top: 10px;
}
.status-indicator {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
animation: pulse 2s infinite;
}
.status-indicator.critical {
background: var(--alarm);
}
.status-indicator.subcritical {
background: var(--caution);
}
.status-indicator.shutdown {
background: var(--success);
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.tooltip {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.95);
border: 2px solid var(--primary);
border-radius: 15px;
padding: 30px;
max-width: 500px;
text-align: center;
font-family: 'Orbitron', sans-serif;
z-index: 1000;
box-shadow: 0 0 30px var(--glow);
}
.close-tooltip {
margin-top: 20px;
padding: 10px 20px;
background: var(--primary);
color: var(--bg);
border: none;
border-radius: 5px;
cursor: pointer;
font-family: 'Orbitron', sans-serif;
font-weight: 700;
}
.particles {
position: absolute;
width: 100%;
height: 100%;
pointer-events: none;
overflow: hidden;
}
.neutron {
position: absolute;
width: 3px;
height: 3px;
background: #00ffff;
border-radius: 50%;
box-shadow: 0 0 6px #00ffff;
}
.audio-controls {
display: flex;
justify-content: space-between;
align-items: center;
margin: 10px 0;
}
.volume-control {
display: flex;
align-items: center;
gap: 10px;
}
.mute-button {
background: var(--graphite);
color: var(--primary);
border: 1px solid var(--primary);
padding: 8px;
border-radius: 5px;
cursor: pointer;
font-family: 'Roboto Mono', monospace;
font-size: 12px;
}
.mute-button.muted {
background: var(--alarm);
color: white;
}
.scanlines {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 180, 255, 0.02) 2px,
rgba(0, 180, 255, 0.02) 4px
);
pointer-events: none;
z-index: 10;
}
@media (max-width: 900px) {
.container {
flex-direction: column;
}
.control-panel {
width: 100%;
height: 40%;
}
.reactor-cavity {
width: 100%;
height: 60%;
}
}
</style>
</head>
<body>
<div class="scanlines"></div>
<div class="tooltip" id="welcomeTooltip">
<h2>Welcome, Dr. Voss</h2>
<p>Reactor is cold and shutdown. Bring power to 100% without exceeding DNBR < 1.3.</p>
<p style="color: var(--caution); margin-top: 15px;">Good hunting.</p>
<button class="close-tooltip" onclick="closeTooltip()">Initialize Reactor</button>
</div>
<div class="container">
<div class="control-panel">
<div class="panel-header">CONTROL ROD DRIVE PANEL</div>
<div class="control-group">
<div class="control-label">Reactor Power</div>
<div class="power-bar">
<div class="power-fill" id="powerFill"></div>
</div>
<div class="readout" id="powerReadout">
<span class="status-indicator shutdown" id="statusIndicator"></span>
0 MWth (0%)
</div>
</div>
<div class="control-group">
<div class="control-label">Core ΔT</div>
<div class="gauge">
<div class="gauge-needle" id="tempNeedle"></div>
</div>
<div class="readout" id="tempReadout">0°C</div>
</div>
<div class="control-group">
<div class="control-label">Control Rod Position</div>
<div class="slider-container">
<input type="range" class="slider" id="rodPosition" min="0" max="100" value="0">
</div>
<div class="readout" id="rodReadout">0% Withdrawn</div>
</div>
<div class="control-group">
<div class="control-label">Boron Concentration</div>
<div class="slider-container">
<input type="range" class="slider" id="boronLevel" min="0" max="2000" value="2000" step="50">
</div>
<div class="readout" id="boronReadout">2000 ppm</div>
</div>
<div class="control-group">
<div class="control-label">RCS Pressure</div>
<div class="gauge">
<div class="gauge-needle" id="pressureNeedle"></div>
</div>
<div class="readout" id="pressureReadout">15.5 MPa</div>
</div>
<div class="control-group">
<div class="control-label">Audio System</div>
<div class="audio-controls">
<button class="mute-button" id="muteButton">🔊 Audio On</button>
<div class="volume-control">
<span style="font-size: 12px;">Vol:</span>
<input type="range" class="slider" id="volumeControl" min="0" max="100" value="50" style="width: 80px; height: 20px;">
</div>
</div>
</div>
<div class="control-group">
<div class="control-label">Emergency Procedures</div>
<div class="emergency-controls">
<button class="action-button success" id="startupBtn">Startup</button>
<button class="action-button caution" id="cooldownBtn">Cooldown</button>
<button class="action-button" id="criticalityBtn">To Critical</button>
<button class="action-button caution" id="resetBtn">Reset All</button>
</div>
</div>
<div class="control-group">
<div class="control-label">Annunciators</div>
<div class="annunciator-grid" id="annunciatorGrid"></div>
</div>
<button class="scram-button" id="scramButton">SCRAM</button>
<div class="control-group">
<div class="control-label">Time Scale</div>
<div class="slider-container">
<input type="range" class="slider" id="timeScale" min="0.1" max="5" value="1" step="0.1">
</div>
<div class="readout" id="timeReadout">1.0x Real Time</div>
</div>
</div>
<div class="reactor-cavity">
<div class="reactor-3d">
<div class="reactor-core" id="reactorCore">
<div class="fuel-assembly-grid" id="fuelGrid"></div>
<div class="control-rod" id="controlRod"></div>
<div class="coolant-loops">
<div class="coolant-loop loop-1"></div>
<div class="coolant-loop loop-2"></div>
<div class="coolant-loop loop-3"></div>
<div class="coolant-loop loop-4"></div>
</div>
</div>
<div class="particles" id="particles"></div>
</div>
</div>
</div>
<script>
// Global variables
let reactor;
// Utility functions (defined first to avoid reference errors)
function closeTooltip() {
const tooltip = document.getElementById('welcomeTooltip');
if (tooltip) {
tooltip.style.display = 'none';
}
// Start the reactor simulation
if (reactor) {
reactor.start();
}
}
// Enhanced Audio System
class ReactorAudio {
constructor() {
this.audioContext = null;
this.enabled = false;
this.muted = false;
this.volume = 0.5;
this.alarmInterval = null;
this.backgroundHum = null;
this.init();
}
init() {
try {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
this.enabled = true;
console.log('Audio system initialized successfully');
} catch (e) {
console.log('Audio not available:', e);
this.enabled = false;
}
}
async ensureAudioContext() {
if (this.audioContext && this.audioContext.state === 'suspended') {
try {
await this.audioContext.resume();
} catch (e) {
console.log('Failed to resume audio context:', e);
}
}
}
playTone(frequency, duration = 0.1, type = 'sine', volume = null) {
if (!this.enabled || !this.audioContext || this.muted) return;
this.ensureAudioContext();
try {
const oscillator = this.audioContext.createOscillator();
const gainNode = this.audioContext.createGain();
oscillator.type = type;
oscillator.frequency.setValueAtTime(frequency, this.audioContext.currentTime);
const vol = (volume !== null ? volume : this.volume) * 0.3;
gainNode.gain.setValueAtTime(vol, this.audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + duration);
oscillator.connect(gainNode);
gainNode.connect(this.audioContext.destination);
oscillator.start();
oscillator.stop(this.audioContext.currentTime + duration);
} catch (e) {
console.log('Audio playback failed:', e);
}
}
playClick() {
this.playTone(800, 0.05, 'square', 0.2);
}
playAlarm() {
if (this.alarmInterval) return; // Already playing
this.playTone(1000, 0.8, 'sine', 0.8);
// Continue alarm pattern
this.alarmInterval = setInterval(() => {
this.playTone(1000, 0.4, 'sine', 0.6);
setTimeout(() => {
this.playTone(1200, 0.4, 'sine', 0.6);
}, 500);
}, 1500);
// Stop after 10 seconds
setTimeout(() => {
this.stopAlarm();
}, 10000);
}
stopAlarm() {
if (this.alarmInterval) {
clearInterval(this.alarmInterval);
this.alarmInterval = null;
}
}
playCriticalityAlarm() {
this.stopAlarm();
this.playTone(1500, 0.2, 'sawtooth', 0.7);
setTimeout(() => {
this.playTone(1200, 0.2, 'sawtooth', 0.7);
}, 300);
setTimeout(() => {
this.playTone(1800, 0.3, 'sawtooth', 0.7);
}, 600);
}
playSubcriticalBeep() {
this.playTone(600, 0.15, 'triangle', 0.4);
}
startBackgroundHum(powerLevel = 0) {
if (!this.enabled || this.muted) return;
this.ensureAudioContext();
try {
if (this.backgroundHum) {
this.backgroundHum.frequency.setValueAtTime(
50 + (powerLevel * 100),
this.audioContext.currentTime
);
this.backgroundHum.gain.gain.setValueAtTime(
Math.min(0.1, powerLevel * 0.05) * this.volume,
this.audioContext.currentTime
);
return;
}
const oscillator = this.audioContext.createOscillator();
const gainNode = this.audioContext.createGain();
const filterNode = this.audioContext.createBiquadFilter();
oscillator.type = 'sawtooth';
oscillator.frequency.setValueAtTime(50 + (powerLevel * 100), this.audioContext.currentTime);
filterNode.type = 'lowpass';
filterNode.frequency.setValueAtTime(200, this.audioContext.currentTime);
gainNode.gain.setValueAtTime(Math.min(0.1, powerLevel * 0.05) * this.volume, this.audioContext.currentTime);
oscillator.connect(filterNode);
filterNode.connect(gainNode);
gainNode.connect(this.audioContext.destination);
oscillator.start();
this.backgroundHum = {
oscillator: oscillator,
gain: gainNode,
frequency: oscillator.frequency
};
} catch (e) {
console.log('Background hum failed:', e);
}
}
stopBackgroundHum() {
if (this.backgroundHum) {
try {
this.backgroundHum.oscillator.stop();
} catch (e) {}
this.backgroundHum = null;
}
}
setMute(muted) {
this.muted = muted;
if (muted) {
this.stopAlarm();
this.stopBackgroundHum();
}
}
setVolume(volume) {
this.volume = Math.max(0, Math.min(1, volume));
}
}
// Reactor Physics Engine
class ReactorPhysics {
constructor() {
this.power = 0; // MWth
this.rodPosition = 0; // % withdrawn
this.boronConc = 2000; // ppm
this.pressure = 15.5; // MPa
this.temperature = 291; // °C
this.timeScale = 1.0;
this.scrammed = false;
this.running = false;
this.lastState = 'shutdown';
// Physics constants
this.maxPower = 3000; // MWth
this.criticalBoron = 1000; // ppm for criticality
this.lastUpdate = Date.now();
}
update() {
if (!this.running) return;
const now = Date.now();
const dt = Math.min((now - this.lastUpdate) / 1000 * this.timeScale, 0.1);
this.lastUpdate = now;
if (this.scrammed) {
this.power = Math.max(0, this.power - (this.power * 2 * dt));
return;
}
// Simple reactivity calculation
const reactivity = this.calculateReactivity();
// Power change based on reactivity
if (reactivity > 0) {
this.power = Math.min(this.maxPower, this.power + (reactivity * 1000 * dt));
} else {
this.power = Math.max(0, this.power + (reactivity * 1000 * dt));
}
// Temperature response
if (this.power > 0) {
const targetTemp = 291 + (this.power / this.maxPower) * 50;
this.temperature += (targetTemp - this.temperature) * 2 * dt;
} else {
this.temperature += (291 - this.temperature) * 1 * dt;
}
// Pressure response
const tempRatio = this.temperature / 291;
this.pressure = 15.5 * tempRatio;
}
calculateReactivity() {
// Rod reactivity
const rodReactivity = (this.rodPosition / 100) * 0.2;
// Boron reactivity
const boronReactivity = -(this.boronConc - this.criticalBoron) * 0.0002;
// Temperature feedback
const tempReactivity = -(this.temperature - 291) * 0.0001;
// Base subcritical
return rodReactivity + boronReactivity + tempReactivity - 0.05;
}
getState() {
if (this.scrammed) return 'scrammed';
if (this.power > 1000) return 'critical';
if (this.power > 10) return 'subcritical';
return 'shutdown';
}
scram() {
this.scrammed = true;
this.rodPosition = 0;
}
reset() {
this.scrammed = false;
this.power = 0;
this.temperature = 291;
this.pressure = 15.5;
this.rodPosition = 0;
this.boronConc = 2000;
this.lastState = 'shutdown';
}
start() {
this.running = true;
this.lastUpdate = Date.now();
}
}
// Main Reactor Simulator
class ReactorSimulator {
constructor() {
this.physics = new ReactorPhysics();
this.audio = new ReactorAudio();
this.keyBuffer = '';
this.cherenkovMode = false;
this.animationId = null;
this.lastReactorState = 'shutdown';
this.initializeElements();
this.createFuelGrid();
this.createAnnunciators();
this.bindEvents();
}
initializeElements() {
this.elements = {
powerFill: document.getElementById('powerFill'),
powerReadout: document.getElementById('powerReadout'),
statusIndicator: document.getElementById('statusIndicator'),
tempNeedle: document.getElementById('tempNeedle'),
tempReadout: document.getElementById('tempReadout'),
pressureNeedle: document.getElementById('pressureNeedle'),
pressureReadout: document.getElementById('pressureReadout'),
rodPosition: document.getElementById('rodPosition'),
rodReadout: document.getElementById('rodReadout'),
boronLevel: document.getElementById('boronLevel'),
boronReadout: document.getElementById('boronReadout'),
timeScale: document.getElementById('timeScale'),
timeReadout: document.getElementById('timeReadout'),
scramButton: document.getElementById('scramButton'),
controlRod: document.getElementById('controlRod'),
reactorCore: document.getElementById('reactorCore'),
particles: document.getElementById('particles'),
fuelGrid: document.getElementById('fuelGrid'),
muteButton: document.getElementById('muteButton'),
volumeControl: document.getElementById('volumeControl'),
startupBtn: document.getElementById('startupBtn'),
cooldownBtn: document.getElementById('cooldownBtn'),
criticalityBtn: document.getElementById('criticalityBtn'),
resetBtn: document.getElementById('resetBtn')
};
}
createFuelGrid() {
const grid = this.elements.fuelGrid;
grid.innerHTML = '';
for (let i = 0; i < 289; i++) { // 17x17 grid
const pin = document.createElement('div');
pin.className = 'fuel-pin';
grid.appendChild(pin);
}
}
createAnnunciators() {
const grid = document.getElementById('annunciatorGrid');
grid.innerHTML = '';
const labels = [
'PWR HIGH', 'TEMP HIGH', 'PRESS HIGH', 'FLOW LOW',
'ROD DROP', 'SCRAM', 'DNBR LOW', 'CRITICAL',
'COOLING', 'CONTAIN', 'RADIAT', 'TURBINE',
'BACKUP', 'DIESEL', 'BATTERY', 'ALARM'
];
for (let i = 0; i < 16; i++) {
const annunciator = document.createElement('div');
annunciator.className = 'annunciator';
annunciator.title = labels[i] || `ANN ${i+1}`;
grid.appendChild(annunciator);
}
}
bindEvents() {
// Control sliders
this.elements.rodPosition.addEventListener('input', (e) => {
if (!this.physics.scrammed) {
this.physics.rodPosition = parseFloat(e.target.value);
this.audio.playClick();
}
});
this.elements.boronLevel.addEventListener('input', (e) => {
this.physics.boronConc = parseFloat(e.target.value);
this.audio.playClick();
});
this.elements.timeScale.addEventListener('input', (e) => {
this.physics.timeScale = parseFloat(e.target.value);
});
// SCRAM button
this.elements.scramButton.addEventListener('click', () => {
if (this.physics.scrammed) {
this.resetReactor();
} else {
this.physics.scram();
this.elements.scramButton.textContent = 'RESET';
this.elements.scramButton.style.background = 'var(--caution)';
this.audio.playAlarm();
}
});
// Audio controls
this.elements.muteButton.addEventListener('click', () => {
this.audio.setMute(!this.audio.muted);
this.elements.muteButton.textContent = this.audio.muted ? '🔇 Audio Off' : '🔊 Audio On';
this.elements.muteButton.classList.toggle('muted', this.audio.muted);
});
this.elements.volumeControl.addEventListener('input', (e) => {
this.audio.setVolume(parseFloat(e.target.value) / 100);
});
// Emergency procedure buttons
this.elements.startupBtn.addEventListener('click', () => {
this.startupProcedure();
this.audio.playClick();
});
this.elements.cooldownBtn.addEventListener('click', () => {
this.cooldownProcedure();
this.audio.playClick();
});
this.elements.criticalityBtn.addEventListener('click', () => {
this.toCriticality();
this.audio.playClick();
});
this.elements.resetBtn.addEventListener('click', () => {
this.resetReactor();
this.audio.playClick();
});
// Cherenkov easter egg
document.addEventListener('keydown', (e) => {
if (e.shiftKey) {
this.keyBuffer += e.key.toLowerCase();
if (this.keyBuffer.includes('cherenkov')) {
this.toggleCherenkovMode();
this.keyBuffer = '';
}
}
});
document.addEventListener('keyup', (e) => {
if (!e.shiftKey) {
this.keyBuffer = '';
}
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT') return;
switch(e.key.toLowerCase()) {
case ' ':
e.preventDefault();
this.elements.scramButton.click();
break;
case 'r':
if (e.ctrlKey) {
e.preventDefault();
this.resetReactor();
}
break;
case 's':
if (e.ctrlKey) {
e.preventDefault();
this.startupProcedure();
}
break;
case 'c':
if (e.ctrlKey) {
e.preventDefault();
this.cooldownProcedure();
}
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (!this.physics.scrammed) {
const position = parseInt(e.key) * 10;
this.physics.rodPosition = position;
this.elements.rodPosition.value = position;
this.audio.playClick();
}
break;
}
});
}
toggleCherenkovMode() {
this.cherenkovMode = !this.cherenkovMode;
if (this.cherenkovMode) {
this.elements.reactorCore.style.filter = 'hue-rotate(200deg) brightness(1.5) saturate(2)';
console.log('Cherenkov debug mode activated');
} else {
this.elements.reactorCore.style.filter = '';
console.log('Cherenkov debug mode deactivated');
}
}
startupProcedure() {
if (this.physics.scrammed) {
console.log('Cannot start: Reactor is scrammed');
return;
}
console.log('PROCEDURE: Reactor startup sequence initiated');
// Step 1: Ensure high boron concentration
this.physics.boronConc = 1800;
this.elements.boronLevel.value = 1800;
// Step 2: Gradual rod withdrawal
let step = 0;
const withdrawStep = () => {
if (step < 40 && this.physics.power < 100 && !this.physics.scrammed) {
this.physics.rodPosition = Math.min(80, this.physics.rodPosition + 2);
this.elements.rodPosition.value = this.physics.rodPosition;
step++;
setTimeout(withdrawStep, 800);
}
};
setTimeout(withdrawStep, 1000);
}
cooldownProcedure() {
console.log('PROCEDURE: Initiating controlled cooldown');
this.physics.rodPosition = Math.max(0, this.physics.rodPosition - 30);
this.physics.boronConc = Math.min(2000, this.physics.boronConc + 300);
this.updateControls();
}
toCriticality() {
if (this.physics.scrammed) {
console.log('Cannot achieve criticality: Reactor is scrammed');
return;
}
console.log('PROCEDURE: Approaching criticality');
// Reduce boron to critical level
this.physics.boronConc = 1100;
this.elements.boronLevel.value = 1100;
// Set rods to critical position
let targetRods = 60;
const adjustRods = () => {
if (Math.abs(this.physics.rodPosition - targetRods) > 2 && !this.physics.scrammed) {
if (this.physics.rodPosition < targetRods) {
this.physics.rodPosition = Math.min(targetRods, this.physics.rodPosition + 3);
} else {
this.physics.rodPosition = Math.max(targetRods, this.physics.rodPosition - 3);
}
this.elements.rodPosition.value = this.physics.rodPosition;
setTimeout(adjustRods, 500);
}
};
adjustRods();
}
resetReactor() {
this.physics.reset();
this.elements.scramButton.textContent = 'SCRAM';
this.elements.scramButton.style.background = 'var(--alarm)';
this.updateControls();
this.audio.stopAlarm();
this.audio.stopBackgroundHum();
console.log('Reactor reset to cold shutdown');
}
updateControls() {
this.elements.rodPosition.value = this.physics.rodPosition;
this.elements.boronLevel.value = this.physics.boronConc;
this.elements.timeScale.value = this.physics.timeScale;
}
updateDisplay() {
// Power display
const powerPercent = Math.min((this.physics.power / 3000) * 100, 120);
this.elements.powerFill.style.height = powerPercent + '%';
// Status indicator and reactor state monitoring
const currentState = this.physics.getState();
if (currentState !== this.lastReactorState) {
this.onStateChange(this.lastReactorState, currentState);
this.lastReactorState = currentState;
}
// Update status indicator
this.elements.statusIndicator.className = `status-indicator ${currentState}`;
this.elements.powerReadout.innerHTML =
`<span class="status-indicator ${currentState}"></span>${this.physics.power.toFixed(0)} MWth (${(this.physics.power/30).toFixed(1)}%)`;
if (powerPercent > 100) {
this.elements.powerFill.style.background = 'linear-gradient(0deg, var(--alarm), var(--caution))';
} else {
this.elements.powerFill.style.background = 'linear-gradient(0deg, var(--primary), var(--coolant))';
}
// Temperature
const tempDelta = this.physics.temperature - 291;
const tempAngle = Math.min((tempDelta / 50) * 180, 180);
this.elements.tempNeedle.style.transform =
`translate(-50%, -100%) rotate(${tempAngle - 90}deg)`;
this.elements.tempReadout.textContent = `+${tempDelta.toFixed(1)}°C`;
// Pressure
const pressureAngle = Math.min(((this.physics.pressure - 15.5) / 2) * 180, 180);
this.elements.pressureNeedle.style.transform =
`translate(-50%, -100%) rotate(${pressureAngle - 90}deg)`;
this.elements.pressureReadout.textContent = `${this.physics.pressure.toFixed(1)} MPa`;
// Control displays
this.elements.rodReadout.textContent = `${this.physics.rodPosition.toFixed(0)}% Withdrawn`;
this.elements.boronReadout.textContent = `${this.physics.boronConc.toFixed(0)} ppm`;
this.elements.timeReadout.textContent = `${this.physics.timeScale.toFixed(1)}x Real Time`;
// Control rod visual position
const rodTop = 10 + (100 - this.physics.rodPosition) * 2.9;
this.elements.controlRod.style.top = rodTop + 'px';
// Update fuel pins
this.updateFuelPins();
// Update annunciators
this.updateAnnunciators();
// Update background hum based on power level
if (this.physics.power > 50) {
this.audio.startBackgroundHum(this.physics.power / 3000);
} else {
this.audio.stopBackgroundHum();
}
// Create neutron particles in Cherenkov mode
if (this.cherenkovMode && this.physics.power > 50 && Math.random() < 0.1) {
this.createNeutronParticle();
}
}
onStateChange(oldState, newState) {
console.log(`Reactor state changed: ${oldState}${newState}`);
switch(newState) {
case 'critical':
if (oldState === 'subcritical') {
console.log('🔥 REACTOR IS NOW CRITICAL');
this.audio.playCriticalityAlarm();
}
break;
case 'subcritical':
if (oldState === 'shutdown') {
console.log('⚡ Reactor is now subcritical');
this.audio.playSubcriticalBeep();
}
break;
case 'scrammed':
console.log('🚨 REACTOR SCRAMMED - EMERGENCY SHUTDOWN');
this.audio.playAlarm();
break;
case 'shutdown':
console.log('✅ Reactor shutdown complete');
this.audio.stopAlarm();
break;
}
}
updateFuelPins() {
const pins = this.elements.fuelGrid.querySelectorAll('.fuel-pin');
const powerLevel = this.physics.power / 3000;
pins.forEach((pin, index) => {
const row = Math.floor(index / 17);
const col = index % 17;
const centerDistance = Math.sqrt((row - 8) ** 2 + (col - 8) ** 2);
const peaking = Math.max(0, 1 - centerDistance / 12);
const localPower = powerLevel * peaking;
if (localPower > 0.1) {
pin.classList.add('hot');
pin.style.opacity = Math.min(1, localPower * 2);
} else {
pin.classList.remove('hot');
pin.style.opacity = 0.3;
}
});
}
updateAnnunciators() {
const annunciators = document.querySelectorAll('.annunciator');
// Clear all
annunciators.forEach(ann => {
ann.classList.remove('active', 'caution');
});
// Power high
if (this.physics.power > 2500) {
annunciators[0].classList.add('active');
}
// Temperature high
if (this.physics.temperature > 330) {
annunciators[1].classList.add('caution');
}
// Pressure high
if (this.physics.pressure > 17.0) {
annunciators[2].classList.add('caution');
}
// SCRAM
if (this.physics.scrammed) {
annunciators[5].classList.add('active');
}
// Critical
if (this.physics.power > 1000) {
annunciators[7].classList.add('caution');
}
// DNBR low
if (this.physics.power > 2800 && this.physics.temperature > 335) {
annunciators[6].classList.add('active');
}
}
createNeutronParticle() {
const neutron = document.createElement('div');
neutron.className = 'neutron';
neutron.style.left = (40 + Math.random() * 20) + '%';
neutron.style.top = (30 + Math.random() * 40) + '%';
this.elements.particles.appendChild(neutron);
// Animate neutron
let y = 0;
const animate = () => {
y += 2;
neutron.style.transform = `translateY(${y}px)`;
neutron.style.opacity = Math.max(0, 1 - y / 200);
if (y < 200 && neutron.parentNode) {
requestAnimationFrame(animate);
} else if (neutron.parentNode) {
neutron.parentNode.removeChild(neutron);
}
};
animate();
}
start() {
this.physics.start();
const animate = () => {
this.physics.update();
this.updateDisplay();
this.animationId = requestAnimationFrame(animate);
};
animate();
}
stop() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
this.audio.stopAlarm();
this.audio.stopBackgroundHum();
}
}
// Initialize everything when page loads
window.addEventListener('load', () => {
console.log('Initializing Nuclear Reactor Simulator...');
try {
// Initialize reactor simulator
reactor = new ReactorSimulator();
console.log('Reactor simulator initialized successfully');
console.log('Controls:');
console.log('- Numbers 0-9: Set rod position (0%, 10%, 20%, etc.)');
console.log('- Space: Emergency SCRAM');
console.log('- Ctrl+R: Reset reactor');
console.log('- Ctrl+S: Startup procedure');
console.log('- Ctrl+C: Cooldown procedure');
console.log('- Shift+CHERENKOV: Debug mode');
} catch (error) {
console.error('Failed to initialize reactor simulator:', error);
}
});
// Handle page visibility changes
document.addEventListener('visibilitychange', () => {
if (reactor) {
if (document.hidden) {
reactor.stop();
} else {
reactor.start();
}
}
});
// Prevent accidental page reload
window.addEventListener('beforeunload', (e) => {
if (reactor && reactor.physics.power > 500) {
e.preventDefault();
e.returnValue = 'Reactor is at power. Are you sure you want to leave?';
return e.returnValue;
}
});
// Add status monitoring
setInterval(() => {
if (reactor && reactor.physics.running) {
const status = {
power: reactor.physics.power.toFixed(1) + ' MWth',
temperature: reactor.physics.temperature.toFixed(1) + '°C',
pressure: reactor.physics.pressure.toFixed(1) + ' MPa',
rods: reactor.physics.rodPosition.toFixed(1) + '% withdrawn',
boron: reactor.physics.boronConc.toFixed(0) + ' ppm',
status: reactor.physics.scrammed ? 'SCRAMMED' :
reactor.physics.power > 1000 ? 'CRITICAL' :
reactor.physics.power > 10 ? 'SUBCRITICAL' : 'SHUTDOWN'
};
// Update page title with reactor status
document.title = `Reactor: ${status.power} | ${status.status}`;
// Log critical conditions
if (reactor.physics.power > 2500) {
console.warn('WARNING: Reactor power exceeding 2500 MWth');
}
if (reactor.physics.temperature > 340) {
console.warn('WARNING: Core temperature exceeding 340°C');
}
if (reactor.physics.pressure > 17) {
console.warn('WARNING: RCS pressure exceeding 17 MPa');
}
}
}, 5000);
console.log('Dr. Elara Voss Nuclear Reactor Simulator v2.0');
console.log('======================================================');
console.log('Enhanced with improved audio and button controls');
console.log('Reactor systems nominal. Awaiting initialization...');
console.log('======================================================');
</script>
</body>
</html>