@jjlmoya/utils-science 1.36.0 → 1.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +4 -1
  3. package/src/entries.ts +7 -1
  4. package/src/index.ts +3 -0
  5. package/src/tests/locale_completeness.test.ts +2 -2
  6. package/src/tests/tool_validation.test.ts +2 -2
  7. package/src/tool/double-slit-decoherence/bibliography.astro +14 -0
  8. package/src/tool/double-slit-decoherence/bibliography.ts +12 -0
  9. package/src/tool/double-slit-decoherence/component.astro +235 -0
  10. package/src/tool/double-slit-decoherence/double-slit-decoherence-simulator.css +344 -0
  11. package/src/tool/double-slit-decoherence/entry.ts +26 -0
  12. package/src/tool/double-slit-decoherence/i18n/de.ts +181 -0
  13. package/src/tool/double-slit-decoherence/i18n/en.ts +181 -0
  14. package/src/tool/double-slit-decoherence/i18n/es.ts +181 -0
  15. package/src/tool/double-slit-decoherence/i18n/fr.ts +181 -0
  16. package/src/tool/double-slit-decoherence/i18n/id.ts +181 -0
  17. package/src/tool/double-slit-decoherence/i18n/it.ts +181 -0
  18. package/src/tool/double-slit-decoherence/i18n/ja.ts +181 -0
  19. package/src/tool/double-slit-decoherence/i18n/ko.ts +181 -0
  20. package/src/tool/double-slit-decoherence/i18n/nl.ts +181 -0
  21. package/src/tool/double-slit-decoherence/i18n/pl.ts +181 -0
  22. package/src/tool/double-slit-decoherence/i18n/pt.ts +181 -0
  23. package/src/tool/double-slit-decoherence/i18n/ru.ts +181 -0
  24. package/src/tool/double-slit-decoherence/i18n/sv.ts +181 -0
  25. package/src/tool/double-slit-decoherence/i18n/tr.ts +181 -0
  26. package/src/tool/double-slit-decoherence/i18n/zh.ts +181 -0
  27. package/src/tool/double-slit-decoherence/index.ts +11 -0
  28. package/src/tool/double-slit-decoherence/logic.ts +77 -0
  29. package/src/tool/double-slit-decoherence/seo.astro +15 -0
  30. package/src/tool/dyson-sphere-energy-capture/bibliography.astro +14 -0
  31. package/src/tool/dyson-sphere-energy-capture/bibliography.ts +16 -0
  32. package/src/tool/dyson-sphere-energy-capture/component.astro +253 -0
  33. package/src/tool/dyson-sphere-energy-capture/dyson-sphere-energy-capture.css +502 -0
  34. package/src/tool/dyson-sphere-energy-capture/entry.ts +26 -0
  35. package/src/tool/dyson-sphere-energy-capture/i18n/de.ts +195 -0
  36. package/src/tool/dyson-sphere-energy-capture/i18n/en.ts +195 -0
  37. package/src/tool/dyson-sphere-energy-capture/i18n/es.ts +195 -0
  38. package/src/tool/dyson-sphere-energy-capture/i18n/fr.ts +195 -0
  39. package/src/tool/dyson-sphere-energy-capture/i18n/id.ts +195 -0
  40. package/src/tool/dyson-sphere-energy-capture/i18n/it.ts +195 -0
  41. package/src/tool/dyson-sphere-energy-capture/i18n/ja.ts +71 -0
  42. package/src/tool/dyson-sphere-energy-capture/i18n/ko.ts +71 -0
  43. package/src/tool/dyson-sphere-energy-capture/i18n/nl.ts +197 -0
  44. package/src/tool/dyson-sphere-energy-capture/i18n/pl.ts +197 -0
  45. package/src/tool/dyson-sphere-energy-capture/i18n/pt.ts +195 -0
  46. package/src/tool/dyson-sphere-energy-capture/i18n/ru.ts +195 -0
  47. package/src/tool/dyson-sphere-energy-capture/i18n/sv.ts +195 -0
  48. package/src/tool/dyson-sphere-energy-capture/i18n/tr.ts +195 -0
  49. package/src/tool/dyson-sphere-energy-capture/i18n/zh.ts +71 -0
  50. package/src/tool/dyson-sphere-energy-capture/index.ts +11 -0
  51. package/src/tool/dyson-sphere-energy-capture/logic.ts +120 -0
  52. package/src/tool/dyson-sphere-energy-capture/seo.astro +15 -0
  53. package/src/tool/global-albedo-snowball-simulator/bibliography.astro +14 -0
  54. package/src/tool/global-albedo-snowball-simulator/bibliography.ts +16 -0
  55. package/src/tool/global-albedo-snowball-simulator/component.astro +278 -0
  56. package/src/tool/global-albedo-snowball-simulator/entry.ts +26 -0
  57. package/src/tool/global-albedo-snowball-simulator/global-albedo-snowball-simulator.css +530 -0
  58. package/src/tool/global-albedo-snowball-simulator/i18n/de.ts +169 -0
  59. package/src/tool/global-albedo-snowball-simulator/i18n/en.ts +169 -0
  60. package/src/tool/global-albedo-snowball-simulator/i18n/es.ts +169 -0
  61. package/src/tool/global-albedo-snowball-simulator/i18n/fr.ts +169 -0
  62. package/src/tool/global-albedo-snowball-simulator/i18n/id.ts +169 -0
  63. package/src/tool/global-albedo-snowball-simulator/i18n/it.ts +87 -0
  64. package/src/tool/global-albedo-snowball-simulator/i18n/ja.ts +87 -0
  65. package/src/tool/global-albedo-snowball-simulator/i18n/ko.ts +169 -0
  66. package/src/tool/global-albedo-snowball-simulator/i18n/nl.ts +169 -0
  67. package/src/tool/global-albedo-snowball-simulator/i18n/pl.ts +169 -0
  68. package/src/tool/global-albedo-snowball-simulator/i18n/pt.ts +169 -0
  69. package/src/tool/global-albedo-snowball-simulator/i18n/ru.ts +169 -0
  70. package/src/tool/global-albedo-snowball-simulator/i18n/sv.ts +169 -0
  71. package/src/tool/global-albedo-snowball-simulator/i18n/tr.ts +169 -0
  72. package/src/tool/global-albedo-snowball-simulator/i18n/zh.ts +169 -0
  73. package/src/tool/global-albedo-snowball-simulator/index.ts +11 -0
  74. package/src/tool/global-albedo-snowball-simulator/logic.ts +88 -0
  75. package/src/tool/global-albedo-snowball-simulator/seo.astro +15 -0
  76. package/src/tools.ts +6 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-science",
3
- "version": "1.36.0",
3
+ "version": "1.38.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -11,16 +11,19 @@ import { stellarHabitabilityZone } from '../tool/stellar-habitability-zone/index
11
11
  import { radioactiveDecay } from '../tool/radioactive-decay/index';
12
12
  import { naturalSelectionDrift } from '../tool/natural-selection-drift/index';
13
13
  import { entropySecondLaw } from '../tool/entropy-second-law/index';
14
+ import { doubleSlitDecoherence } from '../tool/double-slit-decoherence/index';
14
15
  import { phaseDiagramCriticalPoints } from '../tool/phase-diagram-critical-points/index';
15
16
  import { twinParadoxVisualizer } from '../tool/twin-paradox-visualizer/index';
16
17
  import { mandelbrotFractal } from '../tool/mandelbrot-fractal/index';
17
18
  import { planetAtmosphereSurvival } from '../tool/planet-atmosphere-survival/index';
18
19
  import { threeBodyProblem } from '../tool/three-body-problem/index';
19
20
  import { rocheLimitSatelliteDisruption } from '../tool/roche-limit-satellite-disruption/index';
21
+ import { dysonSphereEnergyCapture } from '../tool/dyson-sphere-energy-capture/index';
22
+ import { globalAlbedoSnowballSimulator } from '../tool/global-albedo-snowball-simulator/index';
20
23
 
21
24
  export const scienceCategory: ScienceCategoryEntry = {
22
25
  icon: 'mdi:flask',
23
- tools: [colonyCounter, asteroidImpact, microwaveDetector, simulationProbability, cellularRenewal, cosmicInflation, temperatureTimeline, lorenzAttractor, stellarHabitabilityZone, radioactiveDecay, naturalSelectionDrift, entropySecondLaw, phaseDiagramCriticalPoints, twinParadoxVisualizer, mandelbrotFractal, planetAtmosphereSurvival, threeBodyProblem, rocheLimitSatelliteDisruption],
26
+ tools: [colonyCounter, asteroidImpact, microwaveDetector, simulationProbability, cellularRenewal, cosmicInflation, temperatureTimeline, lorenzAttractor, stellarHabitabilityZone, radioactiveDecay, naturalSelectionDrift, entropySecondLaw, doubleSlitDecoherence, phaseDiagramCriticalPoints, twinParadoxVisualizer, mandelbrotFractal, planetAtmosphereSurvival, threeBodyProblem, rocheLimitSatelliteDisruption, dysonSphereEnergyCapture, globalAlbedoSnowballSimulator],
24
27
  i18n: {
25
28
  es: () => import('./i18n/es').then((m) => m.content),
26
29
  en: () => import('./i18n/en').then((m) => m.content),
package/src/entries.ts CHANGED
@@ -11,12 +11,15 @@ export { stellarHabitabilityZone } from './tool/stellar-habitability-zone/entry'
11
11
  export { radioactiveDecay } from './tool/radioactive-decay/entry';
12
12
  export { naturalSelectionDrift } from './tool/natural-selection-drift/entry';
13
13
  export { entropySecondLaw } from './tool/entropy-second-law/entry';
14
+ export { doubleSlitDecoherence } from './tool/double-slit-decoherence/entry';
14
15
  export { phaseDiagramCriticalPoints } from './tool/phase-diagram-critical-points/entry';
15
16
  export { twinParadoxVisualizer } from './tool/twin-paradox-visualizer/entry';
16
17
  export { mandelbrotFractal } from './tool/mandelbrot-fractal/entry';
17
18
  export { planetAtmosphereSurvival } from './tool/planet-atmosphere-survival/entry';
18
19
  export { threeBodyProblem } from './tool/three-body-problem/entry';
19
20
  export { rocheLimitSatelliteDisruption } from './tool/roche-limit-satellite-disruption/entry';
21
+ export { dysonSphereEnergyCapture } from './tool/dyson-sphere-energy-capture/entry';
22
+ export { globalAlbedoSnowballSimulator } from './tool/global-albedo-snowball-simulator/entry';
20
23
  export { scienceCategory } from './category';
21
24
  import { asteroidImpact } from './tool/asteroid-impact/entry';
22
25
  import { cellularRenewal } from './tool/cellular-renewal/entry';
@@ -30,10 +33,13 @@ import { stellarHabitabilityZone } from './tool/stellar-habitability-zone/entry'
30
33
  import { radioactiveDecay } from './tool/radioactive-decay/entry';
31
34
  import { naturalSelectionDrift } from './tool/natural-selection-drift/entry';
32
35
  import { entropySecondLaw } from './tool/entropy-second-law/entry';
36
+ import { doubleSlitDecoherence } from './tool/double-slit-decoherence/entry';
33
37
  import { phaseDiagramCriticalPoints } from './tool/phase-diagram-critical-points/entry';
34
38
  import { twinParadoxVisualizer } from './tool/twin-paradox-visualizer/entry';
35
39
  import { mandelbrotFractal } from './tool/mandelbrot-fractal/entry';
36
40
  import { planetAtmosphereSurvival } from './tool/planet-atmosphere-survival/entry';
37
41
  import { threeBodyProblem } from './tool/three-body-problem/entry';
38
42
  import { rocheLimitSatelliteDisruption } from './tool/roche-limit-satellite-disruption/entry';
39
- export const ALL_ENTRIES = [asteroidImpact, cellularRenewal, colonyCounter, microwaveDetector, simulationProbability, cosmicInflation, temperatureTimeline, lorenzAttractor, stellarHabitabilityZone, radioactiveDecay, naturalSelectionDrift, entropySecondLaw, phaseDiagramCriticalPoints, twinParadoxVisualizer, mandelbrotFractal, planetAtmosphereSurvival, threeBodyProblem, rocheLimitSatelliteDisruption];
43
+ import { dysonSphereEnergyCapture } from './tool/dyson-sphere-energy-capture/entry';
44
+ import { globalAlbedoSnowballSimulator } from './tool/global-albedo-snowball-simulator/entry';
45
+ export const ALL_ENTRIES = [asteroidImpact, cellularRenewal, colonyCounter, microwaveDetector, simulationProbability, cosmicInflation, temperatureTimeline, lorenzAttractor, stellarHabitabilityZone, radioactiveDecay, naturalSelectionDrift, entropySecondLaw, doubleSlitDecoherence, phaseDiagramCriticalPoints, twinParadoxVisualizer, mandelbrotFractal, planetAtmosphereSurvival, threeBodyProblem, rocheLimitSatelliteDisruption, dysonSphereEnergyCapture, globalAlbedoSnowballSimulator];
package/src/index.ts CHANGED
@@ -12,12 +12,15 @@ export { STELLAR_HABITABILITY_ZONE_TOOL } from './tool/stellar-habitability-zone
12
12
  export { RADIOACTIVE_DECAY_TOOL } from './tool/radioactive-decay/index';
13
13
  export { NATURAL_SELECTION_DRIFT_TOOL } from './tool/natural-selection-drift/index';
14
14
  export { ENTROPY_SECOND_LAW_TOOL } from './tool/entropy-second-law/index';
15
+ export { DOUBLE_SLIT_DECOHERENCE_TOOL } from './tool/double-slit-decoherence/index';
15
16
  export { PHASE_DIAGRAM_CRITICAL_POINTS_TOOL } from './tool/phase-diagram-critical-points/index';
16
17
  export { TWIN_PARADOX_VISUALIZER_TOOL } from './tool/twin-paradox-visualizer/index';
17
18
  export { MANDELBROT_FRACTAL_TOOL } from './tool/mandelbrot-fractal/index';
18
19
  export { PLANET_ATMOSPHERE_SURVIVAL_TOOL } from './tool/planet-atmosphere-survival/index';
19
20
  export { THREE_BODY_PROBLEM_TOOL } from './tool/three-body-problem/index';
20
21
  export { ROCHE_LIMIT_SATELLITE_DISRUPTION_TOOL } from './tool/roche-limit-satellite-disruption/index';
22
+ export { DYSON_SPHERE_ENERGY_CAPTURE_TOOL } from './tool/dyson-sphere-energy-capture/index';
23
+ export { GLOBAL_ALBEDO_SNOWBALL_SIMULATOR_TOOL } from './tool/global-albedo-snowball-simulator/index';
21
24
 
22
25
  export type {
23
26
  KnownLocale,
@@ -18,7 +18,7 @@ describe('Locale Completeness Validation', () => {
18
18
  });
19
19
  });
20
20
 
21
- it('all 18 tools registered', () => {
22
- expect(ALL_TOOLS.length).toBe(18);
21
+ it('all 20 tools registered', () => {
22
+ expect(ALL_TOOLS.length).toBe(21);
23
23
  });
24
24
  });
@@ -4,8 +4,8 @@ import { scienceCategory } from '../data';
4
4
 
5
5
  describe('Tool Validation Suite', () => {
6
6
  describe('Library Registration', () => {
7
- it('should have 18 tools in ALL_TOOLS', () => {
8
- expect(ALL_TOOLS.length).toBe(18);
7
+ it('should have 20 tools in ALL_TOOLS', () => {
8
+ expect(ALL_TOOLS.length).toBe(21);
9
9
  });
10
10
 
11
11
  it('scienceCategory should be defined', () => {
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { doubleSlitDecoherence } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props;
11
+ const content = await doubleSlitDecoherence.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && <SharedBibliography links={content.bibliography} />}
@@ -0,0 +1,12 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'Feynman Lectures on Physics, Volume III, Chapter 1: Quantum Behavior',
6
+ url: 'https://www.feynmanlectures.caltech.edu/III_01.html',
7
+ },
8
+ {
9
+ name: 'Decoherence, the measurement problem, and interpretations of quantum mechanics',
10
+ url: 'https://arxiv.org/abs/quant-ph/0312059',
11
+ },
12
+ ];
@@ -0,0 +1,235 @@
1
+ ---
2
+ import './double-slit-decoherence-simulator.css';
3
+
4
+ interface Props {
5
+ ui: Record<string, string>;
6
+ }
7
+
8
+ const { ui } = Astro.props;
9
+ ---
10
+
11
+ <div class="dslit-lab" id="dslit-lab">
12
+ <section class="dslit-stage" aria-label={ui.stage}>
13
+ <div class="dslit-apparatus">
14
+ <canvas id="dslit-canvas" width="860" height="520"></canvas>
15
+ <div class="dslit-source" aria-hidden="true"></div>
16
+ <div class="dslit-barrier" aria-hidden="true">
17
+ <span></span>
18
+ <i></i>
19
+ <span></span>
20
+ <i></i>
21
+ <span></span>
22
+ </div>
23
+ <div class="dslit-screen" aria-hidden="true"></div>
24
+ </div>
25
+
26
+ <div class="dslit-mode-strip">
27
+ <span>{ui.unobserved}</span>
28
+ <strong id="dslit-state-label" data-wave={ui.waveMode} data-particle={ui.particleMode}>{ui.waveMode}</strong>
29
+ <span>{ui.observed}</span>
30
+ </div>
31
+ </section>
32
+
33
+ <section class="dslit-panel" aria-label={ui.controls}>
34
+ <div class="dslit-kicker">{ui.kicker}</div>
35
+ <label class="dslit-toggle">
36
+ <input id="dslit-detector-enabled" type="checkbox" />
37
+ <span>{ui.detectorToggle}</span>
38
+ </label>
39
+
40
+ <label class="dslit-field" for="dslit-detector">
41
+ <span>{ui.detectorStrength}</span>
42
+ <output id="dslit-detector-output">0%</output>
43
+ <input id="dslit-detector" type="range" min="0" max="100" step="1" value="0" />
44
+ </label>
45
+
46
+ <label class="dslit-field" for="dslit-separation">
47
+ <span>{ui.slitSeparation}</span>
48
+ <output id="dslit-separation-output">1.80</output>
49
+ <input id="dslit-separation" type="range" min="80" max="280" step="5" value="180" />
50
+ </label>
51
+
52
+ <label class="dslit-field" for="dslit-width">
53
+ <span>{ui.slitWidth}</span>
54
+ <output id="dslit-width-output">0.42</output>
55
+ <input id="dslit-width" type="range" min="16" max="90" step="2" value="42" />
56
+ </label>
57
+ </section>
58
+
59
+ <section class="dslit-readout" aria-label={ui.results}>
60
+ <article>
61
+ <span>{ui.fringeVisibility}</span>
62
+ <strong id="dslit-visibility">1.00</strong>
63
+ </article>
64
+ <article>
65
+ <span>{ui.whichPath}</span>
66
+ <strong id="dslit-path">0%</strong>
67
+ </article>
68
+ <article>
69
+ <span>{ui.coherence}</span>
70
+ <strong id="dslit-coherence">100%</strong>
71
+ </article>
72
+ <p>{ui.readoutNote}</p>
73
+ </section>
74
+ </div>
75
+
76
+ <script>
77
+ import { calculateDoubleSlitPattern, measurePattern } from './logic';
78
+
79
+ const canvas = document.getElementById('dslit-canvas') as HTMLCanvasElement | null;
80
+ const ctx = canvas?.getContext('2d');
81
+ const detectorToggle = document.getElementById('dslit-detector-enabled') as HTMLInputElement | null;
82
+ const detectorInput = document.getElementById('dslit-detector') as HTMLInputElement | null;
83
+ const separationInput = document.getElementById('dslit-separation') as HTMLInputElement | null;
84
+ const widthInput = document.getElementById('dslit-width') as HTMLInputElement | null;
85
+ const detectorOutput = document.getElementById('dslit-detector-output');
86
+ const separationOutput = document.getElementById('dslit-separation-output');
87
+ const widthOutput = document.getElementById('dslit-width-output');
88
+ const visibilityOutput = document.getElementById('dslit-visibility');
89
+ const pathOutput = document.getElementById('dslit-path');
90
+ const coherenceOutput = document.getElementById('dslit-coherence');
91
+ const stateLabel = document.getElementById('dslit-state-label');
92
+
93
+ const state = {
94
+ detectorStrength: 0,
95
+ slitSeparation: 1.8,
96
+ slitWidth: 0.42,
97
+ wavelength: 0.36,
98
+ screenDistance: 4.2,
99
+ phase: 0,
100
+ raf: 0,
101
+ };
102
+
103
+ function isDarkTheme() {
104
+ return document.documentElement.classList.contains('theme-dark') || document.body.classList.contains('theme-dark');
105
+ }
106
+
107
+ function createBeamGradient(sourceX: number, screenX: number, isDark: boolean) {
108
+ const gradient = ctx!.createLinearGradient(sourceX, 0, screenX, 0);
109
+ gradient.addColorStop(0, isDark ? 'rgba(64, 224, 208, 0.1)' : 'rgba(0, 139, 130, 0.16)');
110
+ gradient.addColorStop(0.48, isDark ? 'rgba(64, 224, 208, 0.3)' : 'rgba(0, 139, 130, 0.42)');
111
+ gradient.addColorStop(1, isDark ? 'rgba(255, 207, 102, 0.2)' : 'rgba(181, 111, 0, 0.32)');
112
+
113
+ return gradient;
114
+ }
115
+
116
+ function drawInterferencePaths(
117
+ samples: ReturnType<typeof calculateDoubleSlitPattern>,
118
+ scene: { sourceX: number; slitX: number; screenX: number; centerY: number; slitGap: number },
119
+ isDark: boolean,
120
+ ) {
121
+ ctx!.strokeStyle = createBeamGradient(scene.sourceX, scene.screenX, isDark);
122
+ ctx!.lineWidth = isDark ? 1.2 : 1.45;
123
+ ctx!.globalCompositeOperation = isDark ? 'lighter' : 'multiply';
124
+
125
+ samples.forEach((sample, index) => {
126
+ if (index % 2 !== 0) return;
127
+ const screenY = scene.centerY + sample.y * 68;
128
+ const glow = Math.max(0.04, sample.observedIntensity);
129
+ ctx!.globalAlpha = Math.min(isDark ? 0.62 : 0.74, glow * (isDark ? 0.85 : 1.05));
130
+ ctx!.beginPath();
131
+ ctx!.moveTo(scene.sourceX, scene.centerY + Math.sin(state.phase + index * 0.05) * 8);
132
+ ctx!.quadraticCurveTo(scene.slitX, scene.centerY - scene.slitGap / 2, scene.screenX, screenY);
133
+ ctx!.stroke();
134
+ ctx!.beginPath();
135
+ ctx!.moveTo(scene.sourceX, scene.centerY + Math.cos(state.phase + index * 0.04) * 8);
136
+ ctx!.quadraticCurveTo(scene.slitX, scene.centerY + scene.slitGap / 2, scene.screenX, screenY);
137
+ ctx!.stroke();
138
+ });
139
+
140
+ ctx!.globalAlpha = 1;
141
+ ctx!.globalCompositeOperation = 'source-over';
142
+ }
143
+
144
+ function drawBarrier(scene: { slitX: number; centerY: number; slitGap: number; slitWidth: number }, isDark: boolean) {
145
+ ctx!.fillStyle = isDark ? 'rgba(249, 245, 234, 0.82)' : 'rgba(9, 18, 32, 0.86)';
146
+ ctx!.fillRect(scene.slitX - 12, 58, 24, scene.centerY - scene.slitGap / 2 - scene.slitWidth / 2 - 58);
147
+ ctx!.fillRect(scene.slitX - 12, scene.centerY - scene.slitGap / 2 + scene.slitWidth / 2, 24, scene.slitGap - scene.slitWidth);
148
+ ctx!.fillRect(scene.slitX - 12, scene.centerY + scene.slitGap / 2 + scene.slitWidth / 2, 24, canvas!.height - (scene.centerY + scene.slitGap / 2 + scene.slitWidth / 2) - 58);
149
+ }
150
+
151
+ function drawScreenPattern(
152
+ samples: ReturnType<typeof calculateDoubleSlitPattern>,
153
+ scene: { screenX: number; centerY: number },
154
+ isDark: boolean,
155
+ ) {
156
+ samples.forEach((sample) => {
157
+ const y = scene.centerY + sample.y * 68;
158
+ const intensity = Math.min(1, sample.observedIntensity);
159
+ ctx!.fillStyle = isDark
160
+ ? `rgba(255, ${Math.round(178 + intensity * 60)}, 92, ${0.12 + intensity * 0.8})`
161
+ : `rgba(${Math.round(170 + intensity * 30)}, ${Math.round(95 + intensity * 60)}, 0, ${0.22 + intensity * 0.72})`;
162
+ ctx!.fillRect(scene.screenX, y - 1.2 - intensity * 3, 34 + intensity * 30, 2.4 + intensity * 6);
163
+ });
164
+ }
165
+
166
+ function drawBeam(samples: ReturnType<typeof calculateDoubleSlitPattern>) {
167
+ if (!ctx || !canvas) return;
168
+
169
+ const isDark = isDarkTheme();
170
+ const scene = {
171
+ sourceX: 86,
172
+ slitX: 330,
173
+ screenX: 742,
174
+ centerY: canvas.height / 2,
175
+ slitGap: state.slitSeparation * 58,
176
+ slitWidth: state.slitWidth * 42,
177
+ };
178
+
179
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
180
+ drawInterferencePaths(samples, scene, isDark);
181
+ drawBarrier(scene, isDark);
182
+ drawScreenPattern(samples, scene, isDark);
183
+ }
184
+
185
+ function update() {
186
+ const samples = calculateDoubleSlitPattern(state, 201);
187
+ const metrics = measurePattern(samples, state.detectorStrength);
188
+
189
+ detectorOutput!.textContent = `${Math.round(state.detectorStrength * 100)}%`;
190
+ separationOutput!.textContent = state.slitSeparation.toFixed(2);
191
+ widthOutput!.textContent = state.slitWidth.toFixed(2);
192
+ visibilityOutput!.textContent = metrics.fringeVisibility.toFixed(2);
193
+ pathOutput!.textContent = `${Math.round(metrics.whichPath * 100)}%`;
194
+ coherenceOutput!.textContent = `${Math.round(metrics.coherence * 100)}%`;
195
+ stateLabel!.textContent = state.detectorStrength > 0.55
196
+ ? stateLabel!.getAttribute('data-particle') ?? 'Particle bands'
197
+ : stateLabel!.getAttribute('data-wave') ?? 'Interference';
198
+
199
+ detectorInput?.style.setProperty('--fill', `${state.detectorStrength * 100}%`);
200
+ separationInput?.style.setProperty('--fill', `${((state.slitSeparation * 100 - 80) / 200) * 100}%`);
201
+ widthInput?.style.setProperty('--fill', `${((state.slitWidth * 100 - 16) / 74) * 100}%`);
202
+
203
+ drawBeam(samples);
204
+ }
205
+
206
+ function animate() {
207
+ state.phase += 0.025 * (1 - state.detectorStrength * 0.55);
208
+ update();
209
+ state.raf = window.requestAnimationFrame(animate);
210
+ }
211
+
212
+ detectorToggle?.addEventListener('change', () => {
213
+ state.detectorStrength = detectorToggle.checked ? Math.max(0.72, Number(detectorInput?.value ?? 0) / 100) : 0;
214
+ if (detectorInput) detectorInput.value = String(Math.round(state.detectorStrength * 100));
215
+ update();
216
+ });
217
+
218
+ detectorInput?.addEventListener('input', () => {
219
+ state.detectorStrength = Number(detectorInput.value) / 100;
220
+ if (detectorToggle) detectorToggle.checked = state.detectorStrength > 0.02;
221
+ update();
222
+ });
223
+
224
+ separationInput?.addEventListener('input', () => {
225
+ state.slitSeparation = Number(separationInput.value) / 100;
226
+ update();
227
+ });
228
+
229
+ widthInput?.addEventListener('input', () => {
230
+ state.slitWidth = Number(widthInput.value) / 100;
231
+ update();
232
+ });
233
+
234
+ animate();
235
+ </script>