concept-collection / turing-sphere
237 lines · 9.9 KBBlameHistoryRaw
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" />
6 <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><circle cx=%2250%22 cy=%2250%22 r=%2245%22 fill=%22%232a7f62%22/><circle cx=%2235%22 cy=%2238%22 r=%2211%22 fill=%22%23f5d547%22/><circle cx=%2265%22 cy=%2258%22 r=%229%22 fill=%22%23f5d547%22/><circle cx=%2248%22 cy=%2274%22 r=%227%22 fill=%22%23f5d547%22/><circle cx=%2268%22 cy=%2230%22 r=%226%22 fill=%22%23f5d547%22/></svg>" />
7 <title>turing-sphere — reaction-diffusion on the sphere, live in the browser</title>
8 <style>
9 :root {
10 --bg: #ffffff;
11 --ink: #1f2328;
12 --ink-2: #57606a;
13 --line: #d0d7de;
14 --accent: #0969da;
15 --sphere-bg: #f4f6f8;
16 --tok-com: #6e7781;
17 --tok-str: #0a3069;
18 --tok-num: #0550ae;
19 --tok-kw: #cf222e;
20 --tok-ext: #8250df;
21 color-scheme: light dark;
22 }
23 @media (prefers-color-scheme: dark) {
24 :root {
25 --bg: #14171a;
26 --ink: #e6e9ec;
27 --ink-2: #9aa4af;
28 --line: #333b44;
29 --accent: #58a6ff;
30 --sphere-bg: #14161c;
31 --tok-com: #8b949e;
32 --tok-str: #a5d6ff;
33 --tok-num: #79c0ff;
34 --tok-kw: #ff7b72;
35 --tok-ext: #d2a8ff;
36 }
37 }
38 body {
39 margin: 0;
40 background: var(--bg);
41 color: var(--ink);
42 font: 15px/1.5 system-ui, -apple-system, sans-serif;
43 }
44 main { max-width: 1100px; margin: 0 auto; padding: 20px 16px 48px; }
45 h1 { font-size: 20px; margin: 0 0 2px; }
46 .sub { color: var(--ink-2); margin: 0 0 12px; font-size: 13px; }
47 .sub a { color: var(--accent); }
48 .controls {
49 display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center;
50 padding: 6px 0;
51 }
52 .controls label { color: var(--ink-2); font-size: 13px; white-space: nowrap; }
53 select, input[type="number"], button {
54 font: inherit; font-size: 13px;
55 color: var(--ink); background: var(--bg);
56 border: 1px solid var(--line); border-radius: 6px;
57 padding: 4px 8px;
58 }
59 input[type="number"] { width: 6em; }
60 button { cursor: pointer; }
61 button:hover { border-color: var(--accent); }
62 button.primary { border-color: var(--accent); color: var(--accent); font-weight: 600; min-width: 5.5em; }
63 #panels {
64 display: flex; flex-wrap: wrap; gap: 14px; margin-top: 12px;
65 }
66 .panel {
67 flex: 1 1 320px; min-width: 280px;
68 border: 1px solid var(--line); border-radius: 8px; overflow: hidden;
69 display: flex;
70 }
71 .sphere-box { flex: 1; aspect-ratio: 1 / 1; max-height: 70vh; position: relative; }
72 .species-tag {
73 position: absolute; top: 8px; left: 10px; z-index: 2;
74 font-size: 15px; font-weight: 600; color: #fff;
75 background: rgba(0, 0, 0, 0.45);
76 padding: 1px 10px; border-radius: 12px;
77 pointer-events: none;
78 }
79 .colorbar {
80 display: flex; flex-direction: column; align-items: center; justify-content: center;
81 gap: 4px; padding: 8px 4px; background: var(--sphere-bg);
82 width: 52px; flex: none; box-sizing: border-box;
83 }
84 .colorbar canvas { border: 1px solid var(--line); border-radius: 2px; }
85 .colorbar-label { font-size: 11px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
86 .stats { margin-top: 10px; font-size: 13px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
87 .stats b { color: var(--ink); font-weight: 600; }
88 .cli {
89 margin-top: 10px; border: 1px solid var(--line); border-radius: 8px;
90 overflow: hidden;
91 }
92 .cli-head {
93 display: flex; gap: 10px; align-items: center; justify-content: space-between;
94 padding: 6px 10px; font-size: 12px; color: var(--ink-2);
95 background: var(--sphere-bg); border-bottom: 1px solid var(--line);
96 }
97 .cli-head button { padding: 2px 10px; font-size: 12px; }
98 #cmd {
99 display: block; padding: 8px 10px; white-space: pre-wrap;
100 font: 12px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
101 color: var(--ink); user-select: all;
102 }
103 #blurb { margin-top: 4px; font-size: 13px; color: var(--ink-2); }
104 #err { color: #b35900; white-space: pre-wrap; font-size: 13px; }
105 .editor {
106 margin-top: 12px; border: 1px solid var(--line); border-radius: 8px;
107 overflow: hidden;
108 }
109 .editor-head {
110 display: flex; gap: 10px; align-items: center; justify-content: space-between;
111 padding: 6px 10px; font-size: 12px; color: var(--ink-2);
112 background: var(--sphere-bg); border-bottom: 1px solid var(--line);
113 }
114 .editor-head button { padding: 2px 10px; font-size: 12px; }
115 /* Source and compiled-op list side by side, so the editor gets the
116 height rather than sharing it with the list below. */
117 .editor-body { display: flex; align-items: stretch; }
118 .editor-code { position: relative; flex: 1 1 62%; min-width: 0; height: 34em; }
119 /* The overlay and the textarea must agree on every metric that affects
120 where a character lands. Keep these two rules together. */
121 .editor-code > pre,
122 .editor-code > textarea {
123 margin: 0; padding: 10px 12px; border: 0;
124 box-sizing: border-box; width: 100%; height: 100%;
125 font: 12.5px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
126 tab-size: 2;
127 white-space: pre; overflow-wrap: normal;
128 }
129 #highlight {
130 position: absolute; inset: 0; overflow: hidden;
131 pointer-events: none; background: var(--bg); color: var(--ink);
132 }
133 #source {
134 position: relative; z-index: 1; display: block;
135 resize: none; overflow: auto;
136 background: transparent; color: transparent; caret-color: var(--ink);
137 }
138 #source:focus { outline: none; }
139 /* Transparent text means the selection must be see-through, or selected
140 code would be invisible. */
141 #source::selection { background: color-mix(in srgb, var(--accent) 28%, transparent); }
142 #compiled {
143 flex: 1 1 38%; min-width: 0; margin: 0; padding: 10px 12px; overflow: auto;
144 border-left: 1px solid var(--line); background: var(--sphere-bg);
145 font: 12px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
146 color: var(--ink-2); white-space: pre;
147 }
148 @media (max-width: 860px) {
149 .editor-body { flex-direction: column; }
150 .editor-code { flex: none; height: 26em; }
151 #compiled { border-left: 0; border-top: 1px solid var(--line); max-height: 12em; }
152 }
153 .tok-com { color: var(--tok-com); }
154 .tok-str { color: var(--tok-str); }
155 .tok-num { color: var(--tok-num); }
156 .tok-kw { color: var(--tok-kw); font-weight: 600; }
157 .tok-ext { color: var(--tok-ext); }
158 </style>
159 </head>
160 <body>
161 <main>
162 <h1>turing-sphere</h1>
163 <p class="sub">
164 Reaction-diffusion on the sphere, solved live with spherical harmonics:
165 implicit spectral diffusion + explicit reaction (IMEX Euler), transforms on WebGPU via
166 <a href="https://github.com/concept-collection/shtns-webgpu">shtns-webgpu</a>.
167 The solver itself is the MATLAB below — <a href="https://numbl.org">numbl</a>
168 parses and lowers it in your browser, and each line becomes a WebGPU
169 compute kernel. Edit it and the pattern changes. Drag to rotate.
170 </p>
171 <div class="controls">
172 <label>preset
173 <select id="model"></select>
174 </label>
175 <label>lmax
176 <select id="lmax">
177 <option value="31">31</option>
178 <option value="63" selected>63</option>
179 <option value="127">127</option>
180 <option value="255">255</option>
181 </select>
182 </label>
183 <label title="Display only — the solution is evaluated on a finer grid for rendering; the solver and its grid are unchanged. Auto oversamples coarse grids and leaves fine ones alone.">display oversampling
184 <select id="oversample">
185 <option value="auto" selected>auto</option>
186 <option value="1">off</option>
187 <option value="2"></option>
188 <option value="4"></option>
189 </select>
190 </label>
191 <label>colormap
192 <select id="colormap"></select>
193 </label>
194 <button id="runpause" class="primary">Run</button>
195 <button id="benchmark">Benchmark</button>
196 <button id="reseed">Re-seed</button>
197 <button id="resetview">Reset view</button>
198 </div>
199 <div class="controls" id="params"></div>
200 <div id="panels"></div>
201 <p class="stats" id="stats"></p>
202 <p class="stats" id="benchresult"></p>
203 <div class="editor">
204 <div class="editor-head">
205 <span id="editor-title">the solver, in MATLAB</span>
206 <span>
207 <button id="recompile" type="button">Recompile</button>
208 <button id="revert" type="button">Revert</button>
209 </span>
210 </div>
211 <div class="editor-body">
212 <div class="editor-code">
213 <pre id="highlight" aria-hidden="true"></pre>
214 <textarea
215 id="source"
216 spellcheck="false"
217 autocomplete="off"
218 autocapitalize="off"
219 aria-label="model source (MATLAB)"
220 ></textarea>
221 </div>
222 <pre id="compiled"></pre>
223 </div>
224 </div>
225 <div class="cli">
226 <div class="cli-head">
227 <span>The same parameters on the desktop, via the reference TypeScript solver</span>
228 <button id="copycmd" type="button">Copy</button>
229 </div>
230 <code id="cmd"></code>
231 </div>
232 <p id="blurb"></p>
233 <p id="err"></p>
234 </main>
235 <script type="module" src="/src/main.ts"></script>
236 </body>
237</html>