shadow-zone / files / index.html
index.html
| 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 | · |
| 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> |