fuzzyworld

shadow-zone / files / index.html

index.html

11.1 KB · history · blame

1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <title>Seismic Shadow Zone</title>
7 <link rel="icon" type="image/png" href="earth.png" />
8 <style>
9 *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
10
11 body {
12 background: #0a0a14;
13 color: #d0d0e0;
14 font-family: system-ui, sans-serif;
15 font-size: 14px;
16 min-height: 100vh;
17 display: flex;
18 flex-direction: column;
19 }
20
21 h1 {
22 padding: 10px 16px;
23 font-size: 18px;
24 font-weight: 600;
25 border-bottom: 1px solid #2a2a40;
26 color: #e8e8ff;
27 display: flex;
28 align-items: center;
29 gap: 8px;
30 }
31
32 .site-logo {
33 width: 26px;
34 height: 26px;
35 flex-shrink: 0;
36 filter: invert(1) brightness(0.82);
37 }
38
39 #app {
40 flex: 1;
41 display: flex;
42 flex-direction: row;
43 }
44
45 #canvas-wrap {
46 flex: 1;
47 display: flex;
48 align-items: center;
49 justify-content: center;
50 padding: 12px;
51 }
52
53 canvas {
54 display: block;
55 max-width: 100%;
56 max-height: 100%;
57 }
58
59 #sidebar {
60 width: 280px;
61 flex-shrink: 0;
62 padding: 16px;
63 border-left: 1px solid #2a2a40;
64 overflow-y: auto;
65 display: flex;
66 flex-direction: column;
67 gap: 14px;
68 }
69
70 .control-group {
71 display: flex;
72 flex-direction: column;
73 gap: 4px;
74 }
75
76 label {
77 font-size: 12px;
78 color: #a0a0c0;
79 display: flex;
80 justify-content: space-between;
81 }
82
83 label span {
84 color: #e0e0ff;
85 font-variant-numeric: tabular-nums;
86 }
87
88 input[type=range] {
89 width: 100%;
90 accent-color: #6496ff;
91 }
92
93 select {
94 width: 100%;
95 background: #16162a;
96 color: #d0d0e0;
97 border: 1px solid #3a3a58;
98 border-radius: 4px;
99 padding: 4px 6px;
100 font-size: 13px;
101 }
102
103 hr {
104 border: none;
105 border-top: 1px solid #2a2a40;
106 }
107
108 #shadow-zone-info, #validation-info {
109 background: #16162a;
110 border: 1px solid #3a3a58;
111 border-radius: 4px;
112 padding: 8px 10px;
113 font-size: 13px;
114 line-height: 1.6;
115 color: #c0c0e0;
116 }
117
118 #validation-info code {
119 background: #0e0e20;
120 border: 1px solid #3a3a58;
121 border-radius: 3px;
122 padding: 2px 5px;
123 font-family: monospace;
124 font-size: 12px;
125 color: #80c0ff;
126 }
127
128 #shadow-tooltip {
129 position: fixed;
130 display: none;
131 background: #16162a;
132 border: 1px solid #3a3a58;
133 border-radius: 4px;
134 padding: 5px 10px;
135 font-size: 12px;
136 color: #d0d0e0;
137 pointer-events: none;
138 z-index: 100;
139 white-space: nowrap;
140 }
141
142 #description-details > summary {
143 cursor: pointer;
144 list-style: none;
145 font-size: 12px;
146 color: #a0a0c0;
147 user-select: none;
148 display: flex;
149 align-items: center;
150 gap: 6px;
151 }
152
153 #description-details > summary::-webkit-details-marker { display: none; }
154
155 #description-details > summary::before {
156 content: '▶';
157 font-size: 8px;
158 color: #5858a0;
159 display: inline-block;
160 transition: transform 0.15s;
161 flex-shrink: 0;
162 }
163
164 #description-details[open] > summary::before {
165 transform: rotate(90deg);
166 }
167
168 #description {
169 margin-top: 8px;
170 font-size: 12px;
171 color: #7878a0;
172 line-height: 1.65;
173 }
174
175 #description a {
176 color: #6496ff;
177 text-decoration: none;
178 }
179
180 #description a:hover {
181 text-decoration: underline;
182 }
183
184 .preview-badge {
185 background: #1e1e36;
186 border: 1px solid #4a4a68;
187 border-radius: 3px;
188 color: #8888aa;
189 font-size: 10px;
190 padding: 1px 5px;
191 vertical-align: middle;
192 letter-spacing: 0.02em;
193 }
194
195 @media (max-width: 700px) {
196 #app { flex-direction: column; }
197 #sidebar { width: 100%; border-left: none; border-top: 1px solid #2a2a40; }
198 }
199
200 /* ── Preset controls ──────────────────────────────────────────── */
201 .sidebar-btn {
202 background: #16162a;
203 color: #a0a0c0;
204 border: 1px solid #3a3a58;
205 border-radius: 3px;
206 padding: 2px 7px;
207 font-size: 11px;
208 cursor: pointer;
209 white-space: nowrap;
210 }
211 .sidebar-btn:hover:not(:disabled) {
212 background: #1e1e36;
213 color: #d0d0ff;
214 border-color: #5050a0;
215 }
216 .sidebar-btn:disabled {
217 opacity: 0.35;
218 cursor: default;
219 }
220 .sidebar-btn--danger:hover:not(:disabled) {
221 background: #2a1a1a;
222 border-color: #584040;
223 color: #e08080;
224 }
225 .preset-actions {
226 display: flex;
227 gap: 5px;
228 margin-top: 5px;
229 }
230 #save-preset-form {
231 background: #0e0e20;
232 border: 1px solid #3a3a58;
233 border-radius: 4px;
234 padding: 10px;
235 display: flex;
236 flex-direction: column;
237 gap: 8px;
238 }
239 .emoji-grid {
240 display: flex;
241 flex-wrap: wrap;
242 gap: 3px;
243 }
244 .emoji-btn {
245 width: 26px;
246 height: 26px;
247 background: #16162a;
248 border: 1px solid #2a2a40;
249 border-radius: 3px;
250 cursor: pointer;
251 font-size: 15px;
252 line-height: 1;
253 padding: 0;
254 }
255 .emoji-btn:hover { background: #1e1e36; border-color: #4040a0; }
256 .emoji-btn.selected { border-color: #6496ff; background: #1a1a3a; }
257 #preset-name-input {
258 background: #16162a;
259 color: #d0d0e0;
260 border: 1px solid #3a3a58;
261 border-radius: 3px;
262 padding: 4px 7px;
263 font-size: 12px;
264 width: 100%;
265 }
266 #preset-name-input::placeholder { color: #505070; }
267 #preset-name-input:focus { outline: none; border-color: #6496ff; }
268 .preset-form-btns { display: flex; gap: 5px; }
269 </style>
270</head>
271<body>
272 <h1><img src="earth.png" alt="" class="site-logo">Seismic Shadow Zone</h1>
273 <div id="app">
274 <div id="canvas-wrap">
275 <canvas id="canvas" width="600" height="600" style="display:block;"></canvas>
276 </div>
277 <div id="sidebar">
278
279 <div class="control-group">
280 <label>Preset <button id="reset-btn" class="sidebar-btn" type="button" disabled>↺ Reset</button></label>
281 <select id="preset">
282 <option value="earth">🌍 Earth (PREM-like)</option>
283 <option value="solid">🪨 Solid Earth</option>
284 <option value="water-core">💧 Water Core</option>
285 <option value="giant-core">🔴 Giant Core</option>
286 <option value="tiny-core">🔵 Tiny Core</option>
287 <option value="fast-core">⚡ Fast Core</option>
288 <option id="custom-section-marker" disabled style="display:none"></option>
289 <option disabled>──────────────</option>
290 <option value="neg-refraction">⚠️ Negative Refraction</option>
291 <option value="zero-index">⚠️ Zero-Index Core</option>
292 <option value="spiral-trap">⚠️ Spiral Trap</option>
293 <option value="retrograde">⚠️ Retrograde Core</option>
294 <option value="absorber">⚠️ Perfect Absorber</option>
295 </select>
296 <div class="preset-actions">
297 <button id="save-preset-btn" class="sidebar-btn" type="button">+ Save as…</button>
298 <button id="delete-preset-btn" class="sidebar-btn sidebar-btn--danger" type="button" style="display:none">✕ Delete</button>
299 </div>
300 </div>
301
302 <div id="save-preset-form" style="display:none">
303 <div id="emoji-grid" class="emoji-grid"></div>
304 <input id="preset-name-input" type="text" maxlength="40" placeholder="Preset name…" autocomplete="off" />
305 <div class="preset-form-btns">
306 <button id="save-confirm-btn" class="sidebar-btn" type="button" disabled>Save</button>
307 <button id="save-cancel-btn" class="sidebar-btn" type="button">Cancel</button>
308 </div>
309 </div>
310
311 <details id="description-details">
312 <summary>About</summary>
313 <div id="description">
314 <p>When an earthquake occurs, seismic waves travel through Earth's interior. The liquid outer core refracts P-waves away from their straight-line path, leaving a shadow zone — a band where no direct P arrivals are detected. S-waves cannot propagate through liquid at all, so they are blocked entirely beyond ~105°. The exact boundaries shift with core size and composition; try the presets to explore how structure shapes the shadow.</p>
315 <p style="margin-top:6px">
316 <a href="https://en.wikipedia.org/wiki/Shadow_zone" target="_blank" rel="noopener">Wikipedia: Shadow zone ↗</a>
317 &nbsp;·&nbsp;
318 <a href="https://code.fuzzyworld.net/shadow-zone/" target="_blank" rel="noopener">Source code ↗</a>
319 </p>
320 <p style="margin-top:4px">Icon by <a href="https://www.flaticon.com/authors/icongeek26" target="_blank" rel="noopener">Icongeek26</a> on Flaticon.</p>
321 </div>
322 </details>
323
324 <hr />
325
326 <div class="control-group">
327 <label>Outer core radius (km) <span id="outer-core-radius-val">3480</span></label>
328 <input type="range" id="outer-core-radius" min="100" max="6000" step="10" value="3480" />
329 </div>
330
331 <div class="control-group">
332 <label>Core P-vel top (km/s) <span id="outer-core-v-top-val">8.0</span></label>
333 <input type="range" id="outer-core-v-top" min="1.0" max="15.0" step="0.1" value="8.0" />
334 </div>
335
336 <div class="control-group">
337 <label>Core P-vel bottom (km/s) <span id="outer-core-v-bottom-val">10.4</span></label>
338 <input type="range" id="outer-core-v-bottom" min="1.0" max="15.0" step="0.1" value="10.4" />
339 </div>
340
341 <div class="control-group">
342 <label>Inner core radius (km) <span id="inner-core-radius-val">1220</span></label>
343 <input type="range" id="inner-core-radius" min="0" max="3000" step="10" value="1220" />
344 </div>
345
346 <div class="control-group">
347 <label>Inner core P-vel (km/s) <span id="inner-core-v-val">11.3</span></label>
348 <input type="range" id="inner-core-v" min="1.0" max="15.0" step="0.1" value="11.3" />
349 </div>
350
351 <hr />
352
353 <div class="control-group">
354 <label>Wave type</label>
355 <select id="wave-type">
356 <option value="P">P-waves</option>
357 <option value="S">S-waves</option>
358 <option value="both">Both</option>
359 </select>
360 </div>
361
362 <div class="control-group">
363 <label>Number of rays <span id="num-rays-val">60</span></label>
364 <input type="range" id="num-rays" min="10" max="1000" step="5" value="60" />
365 </div>
366
367 <hr />
368
369 <div id="shadow-zone-info">Shadow zone: computing…</div>
370
371 <hr style="display:none" />
372
373 <div class="control-group" style="display:none">
374 <label>Real-world validation <span class="preview-badge">Preview</span></label>
375 <select id="earthquake-select"></select>
376 </div>
377
378 <div id="validation-info" style="display:none"></div>
379
380 </div>
381 </div>
382 <input type="hidden" id="core-modifier-hidden" value="">
383 <script type="module" src="/src/main.ts"></script>
384</body>
385</html>