AshkanTaghipour commited on
Commit
cd9e28f
·
verified ·
1 Parent(s): a124091

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -1,10 +1,9 @@
1
  ---
2
  title: Noddyverse Explorer
3
- emoji:
4
  colorFrom: indigo
5
  colorTo: purple
6
  sdk: static
7
  pinned: false
 
8
  ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: Noddyverse Explorer
3
+ emoji: 🌍
4
  colorFrom: indigo
5
  colorTo: purple
6
  sdk: static
7
  pinned: false
8
+ license: cc-by-4.0
9
  ---
 
 
data/DYKE_DYKE_DYKE_20-09-04-15-13-14-866983543.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1215f1671c79daed89572026684478ad66643cfdabd58dacd5ced72b1820295a
3
+ size 191228
data/DYKE_DYKE_DYKE_20-09-04-15-23-15-571651575.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2e335c56932875fec55e217f0ebc944b062c1ab6051edf6aea668a91ed618a90
3
+ size 226916
data/DYKE_FAULT_FOLD_20-09-04-15-10-34-746860823.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c7ea625855c7681ac89e87f6cbe330adc722e3fd61d4ce14e765dd02f0949dd8
3
+ size 155568
data/DYKE_FAULT_FOLD_20-09-04-15-27-15-271172160.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4530bd3891cc3ab09fcb4c2abeda30a36daf7d70be0bf02a5434e80f302d3859
3
+ size 134164
data/FAULT_FAULT_FAULT_20-09-04-15-10-55-650240871.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:741111b45dc60cb13d3f51a5aa29ab28e42d23621e49a7dbab2b9736d4a44542
3
+ size 98656
data/FAULT_FAULT_FAULT_20-09-04-15-11-35-249388807.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a6a765c66858a3ab937bb62f0857d34ce73e8c5d35d32e97cba683b5a3c1c384
3
+ size 166864
data/FOLD_FAULT_UNCONFORMITY_20-09-04-15-22-57-108968161.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0450a70a95af152ab9f99e62aa37d4ff3f51b639c6c1e731f72d043ade007d82
3
+ size 218080
data/FOLD_FAULT_UNCONFORMITY_20-09-04-15-30-27-806964861.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:58731e5604c8d53d36818b5362ae81c0bca098272583990f09a056542f08b269
3
+ size 163704
data/FOLD_FOLD_FOLD_20-09-04-15-10-30-738239236.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:46ae7a106e81bafa2603cb0032aca17ebe8692cd6e14eae727739067adbec43c
3
+ size 195044
data/FOLD_FOLD_FOLD_20-09-04-15-12-01-552434953.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e4023c6525926410353a891996a4ffc36280b2ac692c71052f74fa9f8b9a54fa
3
+ size 213584
data/PLUG_PLUG_PLUG_20-09-04-15-22-31-290339215.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:19c185592ea6c0ef28ddfc9744c5ad11860ff8e26d7b1a2c6635ee54ce665d46
3
+ size 142584
data/PLUG_PLUG_PLUG_20-09-04-15-34-27-811124008.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b4707c4cc5b6c462478c56aae405c70f9c64834661eba11600330b410e07020f
3
+ size 168924
data/PLUG_SHEAR-ZONE_TILT_20-09-04-15-13-16-695630284.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8986ee495e0598b5f05d3e100f8e47777b9b56a11d40329383350af835de9341
3
+ size 160436
data/PLUG_SHEAR-ZONE_TILT_20-09-04-15-20-46-049109221.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2a3e20aaa5284c9cef7b4a5a1c88aef3a9d3dbb68ba7859ef717afc50d7016e9
3
+ size 130188
data/SHEAR-ZONE_SHEAR-ZONE_SHEAR-ZONE_20-09-04-15-11-17-868781593.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9cd03e94ecc4b065321005cef3cb481436ee4b382fcab3259e163e42fc3fd81d
3
+ size 95184
data/SHEAR-ZONE_SHEAR-ZONE_SHEAR-ZONE_20-09-04-15-12-07-100352181.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:04b109a4d55e095ca49918095ac5dfc11a28785a23544548f625a76be7d707fd
3
+ size 100024
data/TILT_TILT_TILT_20-09-04-15-54-46-708920477.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9b041e761750f6d63541995137676fc587d4e419e123ac8436d41213b21d2b12
3
+ size 100544
data/TILT_TILT_TILT_20-09-04-16-40-18-405539396.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b6b23e2ea0239c1f354093b2935d8ee8e7a6d465d1c21a8ea8f69828de7359e2
3
+ size 140024
data/UNCONFORMITY_UNCONFORMITY_UNCONFORMITY_20-09-04-15-16-52-886739346.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a6d6c23ba5abd1e5477c9909499e3bf8c66ca92169e41366a23f2b23cb4dab0a
3
+ size 125824
data/UNCONFORMITY_UNCONFORMITY_UNCONFORMITY_20-09-04-15-41-38-711387686.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:52966bdf684d2867aeaec50f294c475b83e799f16770758b2bd4281a467d0e81
3
+ size 139676
data/manifest.json ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "grid_size": 50,
3
+ "field_size": 100,
4
+ "types": {
5
+ "DYKE_DYKE_DYKE": [
6
+ "DYKE_DYKE_DYKE_20-09-04-15-13-14-866983543.bin",
7
+ "DYKE_DYKE_DYKE_20-09-04-15-23-15-571651575.bin"
8
+ ],
9
+ "DYKE_FAULT_FOLD": [
10
+ "DYKE_FAULT_FOLD_20-09-04-15-10-34-746860823.bin",
11
+ "DYKE_FAULT_FOLD_20-09-04-15-27-15-271172160.bin"
12
+ ],
13
+ "FAULT_FAULT_FAULT": [
14
+ "FAULT_FAULT_FAULT_20-09-04-15-10-55-650240871.bin",
15
+ "FAULT_FAULT_FAULT_20-09-04-15-11-35-249388807.bin"
16
+ ],
17
+ "FOLD_FAULT_UNCONFORMITY": [
18
+ "FOLD_FAULT_UNCONFORMITY_20-09-04-15-22-57-108968161.bin",
19
+ "FOLD_FAULT_UNCONFORMITY_20-09-04-15-30-27-806964861.bin"
20
+ ],
21
+ "FOLD_FOLD_FOLD": [
22
+ "FOLD_FOLD_FOLD_20-09-04-15-10-30-738239236.bin",
23
+ "FOLD_FOLD_FOLD_20-09-04-15-12-01-552434953.bin"
24
+ ],
25
+ "PLUG_PLUG_PLUG": [
26
+ "PLUG_PLUG_PLUG_20-09-04-15-22-31-290339215.bin",
27
+ "PLUG_PLUG_PLUG_20-09-04-15-34-27-811124008.bin"
28
+ ],
29
+ "PLUG_SHEAR-ZONE_TILT": [
30
+ "PLUG_SHEAR-ZONE_TILT_20-09-04-15-13-16-695630284.bin",
31
+ "PLUG_SHEAR-ZONE_TILT_20-09-04-15-20-46-049109221.bin"
32
+ ],
33
+ "SHEAR-ZONE_SHEAR-ZONE_SHEAR-ZONE": [
34
+ "SHEAR-ZONE_SHEAR-ZONE_SHEAR-ZONE_20-09-04-15-11-17-868781593.bin",
35
+ "SHEAR-ZONE_SHEAR-ZONE_SHEAR-ZONE_20-09-04-15-12-07-100352181.bin"
36
+ ],
37
+ "TILT_TILT_TILT": [
38
+ "TILT_TILT_TILT_20-09-04-15-54-46-708920477.bin",
39
+ "TILT_TILT_TILT_20-09-04-16-40-18-405539396.bin"
40
+ ],
41
+ "UNCONFORMITY_UNCONFORMITY_UNCONFORMITY": [
42
+ "UNCONFORMITY_UNCONFORMITY_UNCONFORMITY_20-09-04-15-16-52-886739346.bin",
43
+ "UNCONFORMITY_UNCONFORMITY_UNCONFORMITY_20-09-04-15-41-38-711387686.bin"
44
+ ]
45
+ }
46
+ }
index.html CHANGED
@@ -1,19 +1,579 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </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>Noddyverse Explorer</title>
7
+ <style>
8
+ *,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
9
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
10
+
11
+ /* ── Splash screen (dark, with fluid) ── */
12
+ #splash{position:fixed;top:0;left:0;width:100%;height:100%;z-index:200;background:#070410;
13
+ display:flex;align-items:center;justify-content:center;flex-direction:column;cursor:pointer;
14
+ transition:opacity 0.8s ease;font-family:'Inter',system-ui,sans-serif}
15
+ #splash.hidden{opacity:0;pointer-events:none}
16
+ #fluid-canvas{position:fixed;top:0;left:0;width:100%;height:100%;z-index:201;pointer-events:none}
17
+ #splash-content{position:relative;z-index:202;text-align:center;color:#e0dce8;pointer-events:none;max-width:620px;padding:0 24px}
18
+ #splash-content h1{font-size:3rem;font-weight:700;
19
+ background:linear-gradient(135deg,#7c3aed,#a855f7,#6366f1);
20
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:16px;letter-spacing:-0.03em}
21
+ #splash-content p{font-size:1.05rem;color:#9a92ad;line-height:1.6;margin-bottom:32px}
22
+ #splash-content .cta{font-size:0.85rem;color:#6366f1;letter-spacing:0.1em;text-transform:uppercase;
23
+ animation:pulse 2s ease-in-out infinite}
24
+ @keyframes pulse{0%,100%{opacity:0.5}50%{opacity:1}}
25
+
26
+ /* ── App (light theme) ── */
27
+ :root{--bg:#f8f9fc;--surface:#ffffff;--surface2:#f0f1f5;--border:#e2e4ea;
28
+ --text:#1a1a2e;--text2:#6b7084;--accent:#7c3aed;--accent2:#a855f7}
29
+ html,body{height:100%;overflow:hidden;font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text)}
30
+ #app{position:relative;z-index:1;display:flex;height:100vh;opacity:0;transition:opacity 0.6s ease 0.4s}
31
+ #app.visible{opacity:1}
32
+
33
+ /* Sidebar */
34
+ #sidebar{width:270px;min-width:270px;background:var(--surface);
35
+ border-right:1px solid var(--border);padding:20px;display:flex;flex-direction:column;gap:14px;overflow-y:auto}
36
+ #sidebar h1{font-size:1.2rem;font-weight:700;background:linear-gradient(135deg,var(--accent),var(--accent2));
37
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:-0.02em}
38
+ #sidebar p.sub{font-size:0.72rem;color:var(--text2);line-height:1.5;margin-top:-6px}
39
+ .control-group{display:flex;flex-direction:column;gap:5px}
40
+ .control-group label{font-size:0.65rem;text-transform:uppercase;letter-spacing:0.08em;color:var(--text2);font-weight:600}
41
+ select{background:var(--surface2);color:var(--text);border:1px solid var(--border);border-radius:8px;
42
+ padding:9px 32px 9px 10px;font-size:0.82rem;cursor:pointer;outline:none;transition:border-color 0.2s;
43
+ appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%236b7084'%3E%3Cpath d='M6 8L1 3h10z'/%3E%3C/svg%3E");
44
+ background-repeat:no-repeat;background-position:right 10px center}
45
+ select:hover,select:focus{border-color:var(--accent)}
46
+
47
+ .info-card{background:var(--surface2);border:1px solid var(--border);border-radius:10px;padding:12px;font-size:0.72rem;line-height:1.6}
48
+ .info-card .label{color:var(--text2);font-size:0.62rem;text-transform:uppercase;letter-spacing:0.06em}
49
+ .info-card .value{color:var(--text);font-weight:600}
50
+ .legend{display:flex;flex-wrap:wrap;gap:5px;margin-top:5px}
51
+ .legend-item{display:flex;align-items:center;gap:3px;font-size:0.62rem;color:var(--text2)}
52
+ .legend-swatch{width:10px;height:10px;border-radius:2px;border:1px solid rgba(0,0,0,0.1)}
53
+
54
+ .credits{font-size:0.58rem;color:var(--text2);line-height:1.5;margin-top:auto;padding-top:8px;border-top:1px solid var(--border)}
55
+
56
+ /* Loading */
57
+ #loading-bar{position:absolute;top:0;left:0;width:0%;height:3px;background:linear-gradient(90deg,var(--accent),var(--accent2));
58
+ z-index:50;transition:width 0.3s;border-radius:0 2px 2px 0}
59
+ #loading-bar.hidden{opacity:0}
60
+
61
+ /* Main content */
62
+ #main{flex:1;display:flex;flex-direction:column;min-width:0;position:relative}
63
+ #viewer-container{flex:1;position:relative;min-height:0;background:var(--surface)}
64
+ #three-canvas{width:100%;height:100%;display:block}
65
+ #viewer-label{position:absolute;top:10px;left:14px;font-size:0.68rem;color:var(--text2);
66
+ background:rgba(255,255,255,0.85);padding:3px 10px;border-radius:6px;backdrop-filter:blur(8px);pointer-events:none;
67
+ border:1px solid var(--border)}
68
+
69
+ #fields-panel{height:210px;min-height:210px;display:flex;border-top:1px solid var(--border);background:var(--surface)}
70
+ .field-container{flex:1;padding:10px 14px;display:flex;flex-direction:column;position:relative}
71
+ .field-container+.field-container{border-left:1px solid var(--border)}
72
+ .field-title{font-size:0.68rem;color:var(--text2);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:5px;font-weight:600}
73
+ .field-canvas-wrap{flex:1;position:relative;display:flex;align-items:center;justify-content:center}
74
+ .field-canvas-wrap canvas{max-width:100%;max-height:100%;border-radius:4px;border:1px solid var(--border)}
75
+ .colorbar{position:absolute;right:2px;top:0;bottom:0;width:16px;display:flex;flex-direction:column;
76
+ align-items:center;justify-content:space-between;font-size:0.52rem;color:var(--text2)}
77
+ .colorbar-gradient{width:8px;flex:1;border-radius:3px;margin:2px 0;border:1px solid var(--border)}
78
+ </style>
79
+ </head>
80
+ <body>
81
+
82
+ <!-- ============ SPLASH SCREEN ============ -->
83
+ <div id="splash">
84
+ <canvas id="fluid-canvas"></canvas>
85
+ <div id="splash-content">
86
+ <h1>Noddyverse Explorer</h1>
87
+ <p>Explore 3D synthetic geological models with interactive gravity and magnetic field visualizations.
88
+ Built from the Noddyverse dataset — one million models generated with the Noddy modelling engine.</p>
89
+ <div class="cta">Click anywhere to explore</div>
90
+ </div>
91
+ </div>
92
+
93
+ <!-- ============ MAIN APP ============ -->
94
+ <div id="app">
95
+ <div id="loading-bar"></div>
96
+ <div id="sidebar">
97
+ <div>
98
+ <h1>Noddyverse Explorer</h1>
99
+ <p class="sub">Drag to rotate the 3D model. Scroll to zoom. Select different geology types below.</p>
100
+ </div>
101
+ <div class="control-group">
102
+ <label>Geology Type</label>
103
+ <select id="type-select"></select>
104
+ </div>
105
+ <div class="control-group">
106
+ <label>Sample</label>
107
+ <select id="sample-select"></select>
108
+ </div>
109
+ <div class="info-card" id="info-card">
110
+ <div><span class="label">Surface voxels: </span><span class="value" id="info-voxels">—</span></div>
111
+ <div><span class="label">Grid: </span><span class="value" id="info-grid">50 x 50 x 50</span></div>
112
+ <div><span class="label">Lithology classes: </span><span class="value" id="info-classes">—</span></div>
113
+ <div style="margin-top:6px"><span class="label">Legend</span>
114
+ <div class="legend" id="legend"></div>
115
+ </div>
116
+ </div>
117
+ <div class="credits">
118
+ Data: Noddyverse (Jessell et al., 2022)<br>
119
+ DOI: 10.25914/p91d-x082 &middot; License: CC-BY 4.0
120
+ </div>
121
+ </div>
122
+
123
+ <div id="main">
124
+ <div id="viewer-container">
125
+ <canvas id="three-canvas"></canvas>
126
+ <div id="viewer-label">3D Geology Model — surface voxels colored by lithology</div>
127
+ </div>
128
+ <div id="fields-panel">
129
+ <div class="field-container">
130
+ <div class="field-title">Gravity Field (mGal)</div>
131
+ <div class="field-canvas-wrap">
132
+ <canvas id="grv-canvas"></canvas>
133
+ <div class="colorbar"><span id="grv-max"></span><div class="colorbar-gradient" id="grv-bar"></div><span id="grv-min"></span></div>
134
+ </div>
135
+ </div>
136
+ <div class="field-container">
137
+ <div class="field-title">Magnetic Field (nT)</div>
138
+ <div class="field-canvas-wrap">
139
+ <canvas id="mag-canvas"></canvas>
140
+ <div class="colorbar"><span id="mag-max"></span><div class="colorbar-gradient" id="mag-bar"></div><span id="mag-min"></span></div>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+
147
+ <script type="importmap">
148
+ {"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.163.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.163.0/examples/jsm/"}}
149
+ </script>
150
+
151
+ <script type="module">
152
+ import * as THREE from 'three';
153
+ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
154
+
155
+ // ============================================================
156
+ // SPLASH → APP TRANSITION
157
+ // ============================================================
158
+ const splash = document.getElementById('splash');
159
+ const app = document.getElementById('app');
160
+ let appStarted = false;
161
+
162
+ splash.addEventListener('click', () => {
163
+ if (appStarted) return;
164
+ appStarted = true;
165
+ splash.classList.add('hidden');
166
+ app.classList.add('visible');
167
+ // Stop fluid rendering after transition
168
+ setTimeout(() => { fluidRunning = false; }, 1000);
169
+ loadManifest();
170
+ });
171
+
172
+ // ============================================================
173
+ // FLUID SIMULATION (WebGL, runs only on splash)
174
+ // ============================================================
175
+ let fluidRunning = true;
176
+ const FluidSim = (() => {
177
+ const canvas = document.getElementById('fluid-canvas');
178
+ const gl = canvas.getContext('webgl', { alpha: true, premultipliedAlpha: false });
179
+ if (!gl) return { init(){}, loop(){} };
180
+
181
+ const ext = gl.getExtension('OES_texture_half_float');
182
+ gl.getExtension('OES_texture_half_float_linear');
183
+ const halfFloat = ext ? ext.HALF_FLOAT_OES : gl.UNSIGNED_BYTE;
184
+
185
+ let simW = 128, simH = 128, dyeW = 512, dyeH = 512;
186
+ let pointer = { x: 0.5, y: 0.5, dx: 0, dy: 0, moved: false };
187
+
188
+ function resize() {
189
+ canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight;
190
+ const a = canvas.width / canvas.height;
191
+ simH = Math.round(128 / a); dyeH = Math.round(512 / a);
192
+ }
193
+
194
+ function compileShader(type, src) {
195
+ const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s); return s;
196
+ }
197
+ function createProgram(vs, fs) {
198
+ const p = gl.createProgram();
199
+ gl.attachShader(p, compileShader(gl.VERTEX_SHADER, vs));
200
+ gl.attachShader(p, compileShader(gl.FRAGMENT_SHADER, fs));
201
+ gl.linkProgram(p);
202
+ const u = {}, n = gl.getProgramParameter(p, gl.ACTIVE_UNIFORMS);
203
+ for (let i = 0; i < n; i++) { const info = gl.getActiveUniform(p, i); u[info.name] = gl.getUniformLocation(p, info.name); }
204
+ return { program: p, uniforms: u };
205
+ }
206
+
207
+ const baseVS = `attribute vec2 aPosition;varying vec2 vUv;varying vec2 vL;varying vec2 vR;varying vec2 vT;varying vec2 vB;uniform vec2 texelSize;
208
+ void main(){vUv=aPosition*0.5+0.5;vL=vUv-vec2(texelSize.x,0);vR=vUv+vec2(texelSize.x,0);
209
+ vT=vUv+vec2(0,texelSize.y);vB=vUv-vec2(0,texelSize.y);gl_Position=vec4(aPosition,0,1);}`;
210
+ const splatFS = `precision highp float;varying vec2 vUv;uniform sampler2D uTarget;uniform float aspectRatio;
211
+ uniform vec3 color;uniform vec2 point;uniform float radius;
212
+ void main(){vec2 p=vUv-point;p.x*=aspectRatio;vec3 s=exp(-dot(p,p)/radius)*color;
213
+ gl_FragColor=vec4(texture2D(uTarget,vUv).xyz+s,1.0);}`;
214
+ const advFS = `precision highp float;varying vec2 vUv;uniform sampler2D uVelocity;uniform sampler2D uSource;
215
+ uniform vec2 texelSize;uniform float dt;uniform float dissipation;
216
+ void main(){vec2 c=vUv-dt*texture2D(uVelocity,vUv).xy*texelSize;gl_FragColor=dissipation*texture2D(uSource,c);gl_FragColor.a=1.0;}`;
217
+ const divFS = `precision highp float;varying vec2 vL;varying vec2 vR;varying vec2 vT;varying vec2 vB;uniform sampler2D uVelocity;
218
+ void main(){float L=texture2D(uVelocity,vL).x;float R=texture2D(uVelocity,vR).x;
219
+ float T=texture2D(uVelocity,vT).y;float B=texture2D(uVelocity,vB).y;gl_FragColor=vec4(0.5*(R-L+T-B),0,0,1);}`;
220
+ const pressFS = `precision highp float;varying vec2 vUv;varying vec2 vL;varying vec2 vR;varying vec2 vT;varying vec2 vB;
221
+ uniform sampler2D uPressure;uniform sampler2D uDivergence;
222
+ void main(){float L=texture2D(uPressure,vL).x;float R=texture2D(uPressure,vR).x;
223
+ float T=texture2D(uPressure,vT).x;float B=texture2D(uPressure,vB).x;
224
+ float d=texture2D(uDivergence,vUv).x;gl_FragColor=vec4((L+R+B+T-d)*0.25,0,0,1);}`;
225
+ const gradFS = `precision highp float;varying vec2 vUv;varying vec2 vL;varying vec2 vR;varying vec2 vT;varying vec2 vB;
226
+ uniform sampler2D uPressure;uniform sampler2D uVelocity;
227
+ void main(){float L=texture2D(uPressure,vL).x;float R=texture2D(uPressure,vR).x;
228
+ float T=texture2D(uPressure,vT).x;float B=texture2D(uPressure,vB).x;
229
+ vec2 v=texture2D(uVelocity,vUv).xy-vec2(R-L,T-B);gl_FragColor=vec4(v,0,1);}`;
230
+ const curlFS = `precision highp float;varying vec2 vL;varying vec2 vR;varying vec2 vT;varying vec2 vB;uniform sampler2D uVelocity;
231
+ void main(){float L=texture2D(uVelocity,vL).y;float R=texture2D(uVelocity,vR).y;
232
+ float T=texture2D(uVelocity,vT).x;float B=texture2D(uVelocity,vB).x;gl_FragColor=vec4(0.5*((R-L)-(T-B)),0,0,1);}`;
233
+ const vortFS = `precision highp float;varying vec2 vUv;varying vec2 vL;varying vec2 vR;varying vec2 vT;varying vec2 vB;
234
+ uniform sampler2D uVelocity;uniform sampler2D uCurl;uniform float curl;uniform vec2 texelSize;
235
+ void main(){float L=texture2D(uCurl,vL).x;float R=texture2D(uCurl,vR).x;
236
+ float T=texture2D(uCurl,vT).x;float B=texture2D(uCurl,vB).x;float C=texture2D(uCurl,vUv).x;
237
+ vec2 f=0.5*vec2(abs(T)-abs(B),abs(R)-abs(L));f/=length(f)+1e-5;f*=curl*C;f.y*=-1.0;
238
+ gl_FragColor=vec4(texture2D(uVelocity,vUv).xy+f*texelSize,0,1);}`;
239
+ const dispFS = `precision highp float;varying vec2 vUv;uniform sampler2D uTexture;
240
+ void main(){vec3 c=texture2D(uTexture,vUv).rgb;gl_FragColor=vec4(c,length(c)*0.7);}`;
241
+
242
+ let splatP, advP, divP, pressP, gradP, dispP, curlP, vortP;
243
+ let velocity, pressure, divergence, dye, curlFBO;
244
+
245
+ function createFBO(w, h) {
246
+ const t = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, t);
247
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
248
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
249
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
250
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
251
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, halfFloat, null);
252
+ const fb = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
253
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, t, 0);
254
+ gl.viewport(0, 0, w, h); gl.clear(gl.COLOR_BUFFER_BIT);
255
+ return { texture: t, fb, w, h };
256
+ }
257
+ function createDoubleFBO(w, h) {
258
+ let a = createFBO(w, h), b = createFBO(w, h);
259
+ return { get read(){ return a; }, get write(){ return b; }, swap(){ const t=a;a=b;b=t; } };
260
+ }
261
+ function blit(tgt) {
262
+ if (tgt) { gl.bindFramebuffer(gl.FRAMEBUFFER, tgt.fb); gl.viewport(0,0,tgt.w,tgt.h); }
263
+ else { gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.viewport(0,0,canvas.width,canvas.height); }
264
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
265
+ }
266
+
267
+ function init() {
268
+ resize();
269
+ const buf = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf);
270
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1,1,-1,-1,1,1,1]), gl.STATIC_DRAW);
271
+ gl.enableVertexAttribArray(0); gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
272
+ splatP=createProgram(baseVS,splatFS); advP=createProgram(baseVS,advFS);
273
+ divP=createProgram(baseVS,divFS); pressP=createProgram(baseVS,pressFS);
274
+ gradP=createProgram(baseVS,gradFS); dispP=createProgram(baseVS,dispFS);
275
+ curlP=createProgram(baseVS,curlFS); vortP=createProgram(baseVS,vortFS);
276
+ velocity=createDoubleFBO(simW,simH); pressure=createDoubleFBO(simW,simH);
277
+ divergence=createFBO(simW,simH); curlFBO=createFBO(simW,simH); dye=createDoubleFBO(dyeW,dyeH);
278
+ }
279
+
280
+ function doSplat(x, y, dx, dy, color) {
281
+ gl.useProgram(splatP.program);
282
+ gl.uniform1i(splatP.uniforms.uTarget,0); gl.uniform1f(splatP.uniforms.aspectRatio,canvas.width/canvas.height);
283
+ gl.uniform2f(splatP.uniforms.point,x,y); gl.uniform3f(splatP.uniforms.color,dx,dy,0);
284
+ gl.uniform1f(splatP.uniforms.radius,0.0003);
285
+ gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,velocity.read.texture);
286
+ blit(velocity.write); velocity.swap();
287
+ gl.uniform3fv(splatP.uniforms.color,color); gl.uniform1f(splatP.uniforms.radius,0.005);
288
+ gl.bindTexture(gl.TEXTURE_2D,dye.read.texture); blit(dye.write); dye.swap();
289
+ }
290
+
291
+ function step(dt) {
292
+ gl.disable(gl.BLEND);
293
+ const sTS=[1/simW,1/simH], dTS=[1/dyeW,1/dyeH];
294
+ // Curl
295
+ gl.useProgram(curlP.program); gl.uniform2fv(curlP.uniforms.texelSize,sTS);
296
+ gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,velocity.read.texture);
297
+ gl.uniform1i(curlP.uniforms.uVelocity,0); blit(curlFBO);
298
+ // Vorticity
299
+ gl.useProgram(vortP.program); gl.uniform2fv(vortP.uniforms.texelSize,sTS); gl.uniform1f(vortP.uniforms.curl,20);
300
+ gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,velocity.read.texture); gl.uniform1i(vortP.uniforms.uVelocity,0);
301
+ gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D,curlFBO.texture); gl.uniform1i(vortP.uniforms.uCurl,1);
302
+ blit(velocity.write); velocity.swap();
303
+ // Advect velocity
304
+ gl.useProgram(advP.program); gl.uniform2fv(advP.uniforms.texelSize,sTS);
305
+ gl.uniform1f(advP.uniforms.dt,dt); gl.uniform1f(advP.uniforms.dissipation,1.0);
306
+ gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,velocity.read.texture); gl.uniform1i(advP.uniforms.uVelocity,0);
307
+ gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D,velocity.read.texture); gl.uniform1i(advP.uniforms.uSource,1);
308
+ blit(velocity.write); velocity.swap();
309
+ // Advect dye
310
+ gl.uniform2fv(advP.uniforms.texelSize,dTS); gl.uniform1f(advP.uniforms.dissipation,0.97);
311
+ gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,velocity.read.texture);
312
+ gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D,dye.read.texture); gl.uniform1i(advP.uniforms.uSource,1);
313
+ blit(dye.write); dye.swap();
314
+ // Divergence
315
+ gl.useProgram(divP.program); gl.uniform2fv(divP.uniforms.texelSize,sTS);
316
+ gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,velocity.read.texture); gl.uniform1i(divP.uniforms.uVelocity,0);
317
+ blit(divergence);
318
+ // Pressure
319
+ gl.useProgram(pressP.program); gl.uniform2fv(pressP.uniforms.texelSize,sTS);
320
+ gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D,divergence.texture); gl.uniform1i(pressP.uniforms.uDivergence,1);
321
+ for(let i=0;i<6;i++){gl.activeTexture(gl.TEXTURE0);gl.bindTexture(gl.TEXTURE_2D,pressure.read.texture);
322
+ gl.uniform1i(pressP.uniforms.uPressure,0);blit(pressure.write);pressure.swap();}
323
+ // Gradient subtract
324
+ gl.useProgram(gradP.program); gl.uniform2fv(gradP.uniforms.texelSize,sTS);
325
+ gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,pressure.read.texture); gl.uniform1i(gradP.uniforms.uPressure,0);
326
+ gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D,velocity.read.texture); gl.uniform1i(gradP.uniforms.uVelocity,1);
327
+ blit(velocity.write); velocity.swap();
328
+ // Display
329
+ gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);
330
+ gl.useProgram(dispP.program); gl.activeTexture(gl.TEXTURE0);
331
+ gl.bindTexture(gl.TEXTURE_2D,dye.read.texture); gl.uniform1i(dispP.uniforms.uTexture,0); blit(null);
332
+ }
333
+
334
+ let lastT = 0;
335
+ function loop(time) {
336
+ if (!fluidRunning) return;
337
+ const dt = Math.min((time - lastT) / 1000, 0.016); lastT = time;
338
+ if (pointer.moved) {
339
+ doSplat(pointer.x, pointer.y, pointer.dx*8, pointer.dy*8, [0.15, 0.0, 0.6]);
340
+ pointer.moved = false;
341
+ }
342
+ step(dt || 0.016);
343
+ requestAnimationFrame(loop);
344
+ }
345
+
346
+ window.addEventListener('resize', () => {
347
+ if (!fluidRunning) return;
348
+ resize(); velocity=createDoubleFBO(simW,simH); pressure=createDoubleFBO(simW,simH);
349
+ divergence=createFBO(simW,simH); curlFBO=createFBO(simW,simH); dye=createDoubleFBO(dyeW,dyeH);
350
+ });
351
+ document.addEventListener('mousemove', e => {
352
+ const x=e.clientX/canvas.width, y=1-e.clientY/canvas.height;
353
+ pointer.dx=(x-pointer.x)*10; pointer.dy=(y-pointer.y)*10;
354
+ pointer.x=x; pointer.y=y; pointer.moved=true;
355
+ });
356
+ document.addEventListener('touchmove', e => {
357
+ e.preventDefault(); const t=e.touches[0];
358
+ const x=t.clientX/canvas.width, y=1-t.clientY/canvas.height;
359
+ pointer.dx=(x-pointer.x)*10; pointer.dy=(y-pointer.y)*10;
360
+ pointer.x=x; pointer.y=y; pointer.moved=true;
361
+ }, { passive: false });
362
+
363
+ return { init, loop };
364
+ })();
365
+
366
+ FluidSim.init();
367
+ requestAnimationFrame(FluidSim.loop);
368
+
369
+ // ============================================================
370
+ // COLORS
371
+ // ============================================================
372
+ const LITH_COLORS = [
373
+ null,
374
+ [0.26,0.52,0.96],[0.94,0.40,0.27],[0.30,0.78,0.47],[0.90,0.70,0.20],
375
+ [0.70,0.35,0.85],[0.20,0.82,0.82],[0.92,0.50,0.70],[0.55,0.75,0.30],
376
+ [0.98,0.60,0.40],[0.45,0.55,0.90],[0.80,0.80,0.30],[0.40,0.70,0.70],
377
+ [0.85,0.45,0.55],[0.60,0.60,0.40],[0.55,0.40,0.70],[0.75,0.55,0.35],
378
+ [0.35,0.65,0.55],[0.80,0.35,0.35],[0.50,0.80,0.60],[0.70,0.50,0.80],
379
+ ];
380
+
381
+ // ============================================================
382
+ // THREE.JS 3D VIEWER
383
+ // ============================================================
384
+ const GRID = 50;
385
+ const threeCanvas = document.getElementById('three-canvas');
386
+ const renderer = new THREE.WebGLRenderer({ canvas: threeCanvas, antialias: true });
387
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
388
+ renderer.setClearColor(0xf8f9fc, 1);
389
+
390
+ const scene = new THREE.Scene();
391
+ const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 300);
392
+ camera.position.set(70, 55, 70);
393
+ const controls = new OrbitControls(camera, threeCanvas);
394
+ controls.enableDamping = true; controls.dampingFactor = 0.08;
395
+ controls.target.set(GRID/2, GRID/2, GRID/2); controls.update();
396
+
397
+ scene.add(new THREE.AmbientLight(0xffffff, 0.6));
398
+ const d1 = new THREE.DirectionalLight(0xffffff, 0.8); d1.position.set(60, 90, 70); scene.add(d1);
399
+ const d2 = new THREE.DirectionalLight(0x8888cc, 0.3); d2.position.set(-40, -20, -40); scene.add(d2);
400
+
401
+ // Wireframe bounding box
402
+ const boxGeo = new THREE.BoxGeometry(GRID, GRID, GRID);
403
+ const boxEdge = new THREE.LineSegments(
404
+ new THREE.EdgesGeometry(boxGeo),
405
+ new THREE.LineBasicMaterial({ color: 0xccccdd })
406
+ );
407
+ boxEdge.position.set(GRID/2, GRID/2, GRID/2);
408
+ scene.add(boxEdge);
409
+
410
+ // Axis labels
411
+ function makeLabel(text, pos) {
412
+ const c = document.createElement('canvas'); c.width=64; c.height=32;
413
+ const ctx = c.getContext('2d'); ctx.fillStyle='#6b7084'; ctx.font='bold 20px Inter,sans-serif';
414
+ ctx.textAlign='center'; ctx.textBaseline='middle'; ctx.fillText(text,32,16);
415
+ const tex = new THREE.CanvasTexture(c);
416
+ const sp = new THREE.Sprite(new THREE.SpriteMaterial({map:tex,transparent:true}));
417
+ sp.position.copy(pos); sp.scale.set(6,3,1); scene.add(sp);
418
+ }
419
+ makeLabel('X', new THREE.Vector3(GRID/2, -4, -4));
420
+ makeLabel('Y', new THREE.Vector3(-4, -4, GRID/2));
421
+ makeLabel('Depth', new THREE.Vector3(-6, GRID/2, -4));
422
+
423
+ let currentMesh = null;
424
+
425
+ function render3D() {
426
+ requestAnimationFrame(render3D); controls.update();
427
+ const rect = threeCanvas.parentElement.getBoundingClientRect();
428
+ const w = rect.width, h = rect.height;
429
+ if (threeCanvas.width !== w*renderer.getPixelRatio() || threeCanvas.height !== h*renderer.getPixelRatio()) {
430
+ renderer.setSize(w, h, false); camera.aspect = w/h; camera.updateProjectionMatrix();
431
+ }
432
+ renderer.render(scene, camera);
433
+ }
434
+ render3D();
435
+
436
+ function loadVoxels(data) {
437
+ if (currentMesh) { scene.remove(currentMesh); currentMesh.geometry.dispose(); currentMesh.material.dispose(); }
438
+ const { surfaceVoxels, nClasses } = data;
439
+ const n = surfaceVoxels.length / 4;
440
+ const geo = new THREE.BoxGeometry(1, 1, 1);
441
+ const mat = new THREE.MeshPhongMaterial({ vertexColors: true, shininess: 40 });
442
+ const mesh = new THREE.InstancedMesh(geo, mat, n);
443
+ const dummy = new THREE.Object3D();
444
+ const color = new THREE.Color();
445
+
446
+ for (let i = 0; i < n; i++) {
447
+ // Binary stores: [dim0, dim1, dim2, class]
448
+ // dim0 = depth layers (0=top), dim1 = northing(Y), dim2 = easting(X)
449
+ // Three.js: X=easting, Y=up(inverted depth), Z=northing
450
+ const dim0 = surfaceVoxels[i * 4]; // depth
451
+ const dim1 = surfaceVoxels[i * 4 + 1]; // northing
452
+ const dim2 = surfaceVoxels[i * 4 + 2]; // easting
453
+ const cls = surfaceVoxels[i * 4 + 3];
454
+
455
+ dummy.position.set(dim2 + 0.5, (GRID - 1 - dim0) + 0.5, dim1 + 0.5);
456
+ dummy.updateMatrix();
457
+ mesh.setMatrixAt(i, dummy.matrix);
458
+ const c = LITH_COLORS[cls] || [0.5, 0.5, 0.5];
459
+ color.setRGB(c[0], c[1], c[2]);
460
+ mesh.setColorAt(i, color);
461
+ }
462
+ mesh.instanceMatrix.needsUpdate = true;
463
+ mesh.instanceColor.needsUpdate = true;
464
+ scene.add(mesh);
465
+ currentMesh = mesh;
466
+
467
+ document.getElementById('info-voxels').textContent = n.toLocaleString();
468
+ document.getElementById('info-classes').textContent = nClasses;
469
+ const legend = document.getElementById('legend'); legend.innerHTML = '';
470
+ for (let i = 1; i <= nClasses; i++) {
471
+ const c = LITH_COLORS[i] || [0.5,0.5,0.5];
472
+ const div = document.createElement('div'); div.className = 'legend-item';
473
+ div.innerHTML = `<span class="legend-swatch" style="background:rgb(${c[0]*255|0},${c[1]*255|0},${c[2]*255|0})"></span>${i}`;
474
+ legend.appendChild(div);
475
+ }
476
+ }
477
+
478
+ // ============================================================
479
+ // HEATMAPS
480
+ // ============================================================
481
+ function viridis(t) {
482
+ t = Math.max(0, Math.min(1, t));
483
+ return [Math.max(0,Math.min(255,(68+t*(1-t)*600-t*t*350)|0)),
484
+ Math.max(0,Math.min(255,(2+t*330-t*t*130)|0)),
485
+ Math.max(0,Math.min(255,(85+t*180-t*t*350+t*t*t*200)|0))];
486
+ }
487
+ function magma(t) {
488
+ t = Math.max(0, Math.min(1, t));
489
+ return [Math.max(0,Math.min(255,(1+t*780-t*t*500+t*t*t*200)|0)),
490
+ Math.max(0,Math.min(255,(0+t*50+t*t*500-t*t*t*350)|0)),
491
+ Math.max(0,Math.min(255,(40+t*560-t*t*900+t*t*t*550)|0))];
492
+ }
493
+
494
+ function renderHeatmap(canvasId, data, rows, cols, cmap, barId, minId, maxId) {
495
+ const canvas = document.getElementById(canvasId);
496
+ canvas.width = cols; canvas.height = rows;
497
+ const ctx = canvas.getContext('2d');
498
+ const img = ctx.createImageData(cols, rows);
499
+ let min = Infinity, max = -Infinity;
500
+ for (let i = 0; i < data.length; i++) { if(data[i]<min)min=data[i]; if(data[i]>max)max=data[i]; }
501
+ const range = max - min || 1;
502
+ for (let i = 0; i < data.length; i++) {
503
+ const [r,g,b] = cmap((data[i]-min)/range);
504
+ img.data[i*4]=r; img.data[i*4+1]=g; img.data[i*4+2]=b; img.data[i*4+3]=255;
505
+ }
506
+ ctx.putImageData(img, 0, 0);
507
+ document.getElementById(minId).textContent = min.toFixed(1);
508
+ document.getElementById(maxId).textContent = max.toFixed(1);
509
+ const bar = document.getElementById(barId);
510
+ let grad = 'linear-gradient(to bottom,';
511
+ for (let i = 0; i <= 20; i++) {
512
+ const [r,g,b] = cmap(1-i/20);
513
+ grad += `rgb(${r},${g},${b})${i<20?',':''}`;
514
+ }
515
+ bar.style.background = grad + ')';
516
+ }
517
+
518
+ // ============================================================
519
+ // DATA LOADING
520
+ // ============================================================
521
+ let manifest = null;
522
+
523
+ async function loadManifest() {
524
+ const resp = await fetch('data/manifest.json');
525
+ manifest = await resp.json();
526
+ const typeSelect = document.getElementById('type-select');
527
+ Object.keys(manifest.types).forEach(type => {
528
+ const opt = document.createElement('option'); opt.value = type;
529
+ opt.textContent = type.replace(/_/g, ' \u2192 ').replace(/SHEAR-ZONE/g,'SHEAR ZONE');
530
+ typeSelect.appendChild(opt);
531
+ });
532
+ typeSelect.addEventListener('change', populateSamples);
533
+ document.getElementById('sample-select').addEventListener('change', loadSample);
534
+ populateSamples();
535
+ }
536
+
537
+ function populateSamples() {
538
+ const type = document.getElementById('type-select').value;
539
+ const sel = document.getElementById('sample-select'); sel.innerHTML = '';
540
+ manifest.types[type].forEach((name, i) => {
541
+ const opt = document.createElement('option'); opt.value = name;
542
+ opt.textContent = `Sample ${i+1}`; sel.appendChild(opt);
543
+ });
544
+ loadSample();
545
+ }
546
+
547
+ async function loadSample() {
548
+ const fileName = document.getElementById('sample-select').value;
549
+ if (!fileName) return;
550
+ const bar = document.getElementById('loading-bar');
551
+ bar.classList.remove('hidden'); bar.style.width = '30%';
552
+
553
+ try {
554
+ const resp = await fetch(`data/${fileName}`);
555
+ bar.style.width = '70%';
556
+ const buf = await resp.arrayBuffer();
557
+ bar.style.width = '90%';
558
+ const view = new DataView(buf);
559
+ let off = 0;
560
+ const nSurface = view.getUint32(off,true); off+=4;
561
+ const grvR = view.getUint32(off,true); off+=4;
562
+ const grvC = view.getUint32(off,true); off+=4;
563
+ const magR = view.getUint32(off,true); off+=4;
564
+ const magC = view.getUint32(off,true); off+=4;
565
+ const nCls = view.getUint32(off,true); off+=4;
566
+ const sv = new Uint8Array(buf, off, nSurface*4); off += nSurface*4;
567
+ const grv = new Float32Array(buf, off, grvR*grvC); off += grvR*grvC*4;
568
+ const mag = new Float32Array(buf, off, magR*magC);
569
+
570
+ loadVoxels({ surfaceVoxels: sv, nClasses: nCls });
571
+ renderHeatmap('grv-canvas', grv, grvR, grvC, viridis, 'grv-bar', 'grv-min', 'grv-max');
572
+ renderHeatmap('mag-canvas', mag, magR, magC, magma, 'mag-bar', 'mag-min', 'mag-max');
573
+ bar.style.width = '100%';
574
+ } catch(e) { console.error('Load failed:', e); }
575
+ setTimeout(() => { bar.style.width = '0%'; bar.classList.add('hidden'); }, 400);
576
+ }
577
+ </script>
578
+ </body>
579
  </html>