abgunaydin commited on
Commit
e34aa23
·
verified ·
1 Parent(s): 55d1c52

Deploy neuropulse: 3D transformer visualizer + demo mode

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
README.md CHANGED
@@ -1,10 +1,33 @@
1
  ---
2
- title: Neuropulse
3
- emoji: 💻
4
- colorFrom: pink
5
- colorTo: yellow
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: Neuropulse — Watch a real transformer think
3
+ emoji: 🧠
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: static
7
+ pinned: true
8
+ license: mit
9
+ short_description: Live 3D rendering of Phi-3-mini, every pixel a real tensor
10
  ---
11
 
12
+ # Neuropulse
13
+
14
+ A live 3D rendering of **Phi-3-mini** running as a hand-written WGSL/WebGPU
15
+ inference engine. Every point of light is a real tensor — attention heads, FFN
16
+ cells, residual norms — drawn one-to-one from the running model, not a video or
17
+ an animation.
18
+
19
+ ## Two ways to explore
20
+
21
+ - **Start learning now — no download.** The [`?demo=1`](./app/?demo=1) path
22
+ replays **real runs captured live earlier** (the recordings are committed
23
+ tensors, always badged "RECORDED RUN"). The full UI, guided lessons, tours,
24
+ and ablation experiments all work with nothing to install.
25
+ - **Download & run live (~2 GB).** On a WebGPU-capable browser you can pull the
26
+ quantized weights and watch your *own* prompts run on your GPU. Close the tab
27
+ and the inference stops.
28
+
29
+ Canonical site: **[neuropulse.live](https://neuropulse.live)**. This Space is a
30
+ mirror.
31
+
32
+ Built with vanilla TypeScript + Three.js over a from-scratch WGSL Phi-3 engine.
33
+ No frameworks, no telemetry, no fakery.
app/index.html ADDED
The diff for this file is too large to render. See raw diff
 
architecture-dark.svg ADDED
architecture-light.svg ADDED
assets/app-5dUOJK1P.js ADDED
The diff for this file is too large to render. See raw diff
 
assets/main-D1-Ogq2V.js ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import{W as E,S as L,P as B,B as R,a as C,C as u,b as Y,A as _,c as j}from"./three.module-Bwi0mW4U.js";(function(){const r=document.getElementById("page-canvas");if(!r||window.matchMedia("(prefers-reduced-motion: reduce)").matches)return;const p=window.innerWidth<820||/Mobi|Android|iPhone|iPad/i.test(navigator.userAgent),h=p?9e3:28e3,W=p?1.5:2,v=new E({canvas:r,antialias:!p,alpha:!0,powerPreference:"high-performance"});v.setPixelRatio(Math.min(window.devicePixelRatio,W));const w=new L,o=new B(55,1,.1,300);o.position.set(0,0,16);const l=new Float32Array(h*3),d=new Float32Array(h*3);for(let e=0;e<h;e++){const s=(Math.floor(Math.random()*32)/31-.5)*18,a=Math.pow(Math.random(),.7)*9+.5,m=Math.random()*Math.PI*2;l[e*3]=a*Math.cos(m),l[e*3+1]=s+(Math.random()-.5)*.4,l[e*3+2]=a*Math.sin(m)-4,d[e*3]=Math.random(),d[e*3+1]=Math.random(),d[e*3+2]=Math.random()}const f=new R;f.setAttribute("position",new C(l,3)),f.setAttribute("seed",new C(d,3));const c={time:{value:0},prog:{value:0},fade:{value:1},cyan:{value:new u(6220500)},cyanHot:{value:new u(58879)},violet:{value:new u(12616956)}},F=new Y({uniforms:c,transparent:!0,depthWrite:!1,blending:_,vertexShader:`
2
+ attribute vec3 seed;
3
+ uniform float time;
4
+ uniform float prog;
5
+ uniform float fade;
6
+ uniform vec3 cyan;
7
+ uniform vec3 cyanHot;
8
+ uniform vec3 violet;
9
+ varying vec3 vColor;
10
+ varying float vAlpha;
11
+
12
+ void main() {
13
+ vec3 p = position;
14
+ float t = time * 0.12;
15
+
16
+ // Volumetric drift (dominant in hero) fades as prog rises
17
+ float ph = seed.x * 6.2831;
18
+ float ph2 = seed.y * 6.2831;
19
+ float amp = 0.5 + seed.z * 1.3;
20
+ float volWeight = mix(1.0, 0.35, prog);
21
+
22
+ p.x += sin(t + ph) * amp * volWeight;
23
+ p.y += cos(t * 0.8 + ph2) * amp * 0.35 * volWeight;
24
+ p.z += sin(t * 0.6 + ph + seed.y * 3.0) * amp * 0.7 * volWeight;
25
+
26
+ // Leftward flow that ramps up as prog rises (mid-scroll onward)
27
+ float flowAmt = smoothstep(0.15, 0.85, prog);
28
+ float flowSpeed = (0.6 + seed.z * 1.4) * flowAmt;
29
+ float wrap = 36.0;
30
+ p.x = mod(p.x + wrap * 0.5 - time * flowSpeed, wrap) - wrap * 0.5;
31
+
32
+ vec4 mv = modelViewMatrix * vec4(p, 1.0);
33
+ gl_Position = projectionMatrix * mv;
34
+
35
+ // Slightly smaller points as flow tightens
36
+ float depth = -mv.z;
37
+ float baseSize = 1.0 + seed.z * 3.4;
38
+ gl_PointSize = baseSize * mix(1.0, 0.7, prog) * (90.0 / depth);
39
+
40
+ // Palette: mostly cyan, rare violet, rarest hot-cyan activity
41
+ float hot = step(0.965, seed.x);
42
+ float violetMix = smoothstep(0.55, 0.95, seed.y);
43
+ vec3 base = mix(cyan, violet, violetMix);
44
+ vColor = mix(base, cyanHot, hot);
45
+
46
+ // Per-point breathing + depth fade + master alpha
47
+ float pulse = 0.55 + 0.45 * sin(t * 2.4 + seed.x * 18.0 + seed.y * 12.0);
48
+ vAlpha = pulse * smoothstep(40.0, 5.0, depth) * fade;
49
+ }
50
+ `,fragmentShader:`
51
+ varying vec3 vColor;
52
+ varying float vAlpha;
53
+ void main() {
54
+ vec2 uv = gl_PointCoord - 0.5;
55
+ float d = length(uv);
56
+ if (d > 0.5) discard;
57
+ float fall = smoothstep(0.5, 0.0, d);
58
+ fall = pow(fall, 1.4);
59
+ gl_FragColor = vec4(vColor * (1.0 + fall * 0.6), fall * vAlpha);
60
+ }
61
+ `}),g=new j(f,F);w.add(g);function y(){const e=window.innerWidth,i=window.innerHeight;v.setSize(e,i,!1),o.aspect=e/i,o.updateProjectionMatrix()}y(),window.addEventListener("resize",y);let M=0,x=0;window.addEventListener("pointermove",e=>{M=e.clientX/window.innerWidth*2-1,x=e.clientY/window.innerHeight*2-1},{passive:!0});let t=0,b=1;function n(e,i,s){const a=Math.max(0,Math.min(1,(s-e)/(i-e)));return a*a*(3-2*a)}function A(){const e=window.scrollY,i=Math.max(1,document.documentElement.scrollHeight-window.innerHeight);t=Math.max(0,Math.min(1,e/i));const s=1-n(.04,.15,t),a=n(.15,.25,t)*(1-n(.7,.78,t)),m=n(.78,.88,t)*(1-n(.93,.98,t)),H=n(.93,1,t);b=Math.max(s,.35+(1-a)*0,m*.85,H)}A(),window.addEventListener("scroll",A,{passive:!0});let P=!0;document.addEventListener("visibilitychange",()=>{P=document.visibilityState==="visible"}),r.style.opacity="0",r.style.transition="opacity 1.1s cubic-bezier(0.16, 1, 0.3, 1)",requestAnimationFrame(()=>{r.style.opacity="1"});let S=0;function z(){if(P){S+=1/60,c.time.value=S,c.prog.value=t,c.fade.value=b;const e=1-Math.min(1,t*4);o.position.x+=(M*.7*e-o.position.x)*.03,o.position.y+=(-x*.5*e-o.position.y)*.03,o.lookAt(0,0,0),g.rotation.y+=6e-4*(1-t*.7),v.render(w,o)}requestAnimationFrame(z)}z()})();
assets/three.module-Bwi0mW4U.js ADDED
The diff for this file is too large to render. See raw diff
 
comparison-dark.svg ADDED
comparison-light.svg ADDED
controls-dark.svg ADDED
controls-light.svg ADDED
favicon-16.png ADDED
favicon-192.png ADDED
favicon-32.png ADDED
favicon-48.png ADDED
favicon-512.png ADDED
favicon-96.png ADDED
favicon.ico ADDED
favicon.svg ADDED
hero-dark.svg ADDED
hero-light.svg ADDED
index.html CHANGED
@@ -1,19 +1,1361 @@
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>Neuropulse — Watch a real transformer think</title>
7
+ <meta name="description" content="A live 3D rendering of Phi-3-mini running on your GPU. Every attention head, every FFN cell, every activation — drawn one-to-one from real WebGPU tensors. The whole model runs in your tab; close it and the inference stops." />
8
+ <link rel="canonical" href="https://neuropulse.live/" />
9
+ <link rel="icon" href="./favicon.ico" sizes="any" />
10
+ <link rel="icon" type="image/svg+xml" href="./favicon.svg" />
11
+ <link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
12
+ <link rel="icon" type="image/png" sizes="96x96" href="./favicon-96.png" />
13
+ <link rel="icon" type="image/png" sizes="192x192" href="./favicon-192.png" />
14
+ <link rel="apple-touch-icon" sizes="192x192" href="./favicon-192.png" />
15
+
16
+ <!-- Open Graph -->
17
+ <meta property="og:type" content="website" />
18
+ <meta property="og:title" content="Neuropulse — Watch a real transformer think" />
19
+ <meta property="og:description" content="Live 3D rendering of Phi-3-mini running on your GPU. Strict 1:1 from real WebGPU tensors." />
20
+ <meta property="og:url" content="https://neuropulse.live/" />
21
+ <meta property="og:image" content="https://neuropulse.live/og.png" />
22
+ <meta name="twitter:card" content="summary_large_image" />
23
+ <meta name="twitter:title" content="Neuropulse — Watch a real transformer think" />
24
+ <meta name="twitter:description" content="Live 3D rendering of Phi-3-mini running on your GPU. Strict 1:1 from real WebGPU tensors." />
25
+ <meta name="twitter:image" content="https://neuropulse.live/og.png" />
26
+
27
+ <!-- Structured data: identity + cross-links to the kernel-fusion research line. -->
28
+ <script type="application/ld+json">
29
+ {
30
+ "@context": "https://schema.org",
31
+ "@graph": [
32
+ {
33
+ "@type": "WebSite",
34
+ "@id": "https://neuropulse.live#site",
35
+ "url": "https://neuropulse.live",
36
+ "name": "Neuropulse",
37
+ "description": "Live 3D rendering of Phi-3-mini running on your GPU. Every tensor drawn one-to-one from real WebGPU activations.",
38
+ "publisher": { "@id": "https://neuropulse.live#author" },
39
+ "isPartOf": {
40
+ "@type": "CreativeWork",
41
+ "name": "Kernel-fusion research line",
42
+ "url": "https://kernelfusion.dev"
43
+ }
44
+ },
45
+ {
46
+ "@type": "Person",
47
+ "@id": "https://neuropulse.live#author",
48
+ "name": "Ahmet Baris Gunaydin",
49
+ "url": "https://barisgunaydin.com",
50
+ "sameAs": [
51
+ "https://barisgunaydin.com",
52
+ "https://kernelfusion.dev",
53
+ "https://gpubench.dev",
54
+ "https://zerotvm.com",
55
+ "https://webgpudna.com",
56
+ "https://neuropulse.live",
57
+ "https://webgpu-q.vercel.app",
58
+ "https://markview.ai",
59
+ "https://safenpm.dev",
60
+ "https://fused-lora.pages.dev",
61
+ "https://github.com/abgnydn",
62
+ "https://www.linkedin.com/in/abgnydn/"
63
+ ]
64
+ }
65
+ ]
66
+ }
67
+ </script>
68
+
69
+ <link rel="preconnect" href="https://fonts.googleapis.com">
70
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
71
+ <link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght,SOFT@9..144,300..700,30..100&family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
72
+
73
+
74
+ <style>
75
+ :root {
76
+ --bg: #08060f;
77
+ --bg-warm: #0d0810;
78
+ --ink: #f4ecdf;
79
+ --ink-soft: #cbc1ad;
80
+ --ink-dim: #8a8170;
81
+ --ink-faint: #514a3e;
82
+ --rule: rgba(244, 236, 223, 0.10);
83
+ --cyan: #5eead4;
84
+ --cyan-bright: #00e5ff;
85
+ --violet: #c084fc;
86
+ --amber: #ff8c42;
87
+ --serif: 'Fraunces', 'Iowan Old Style', Georgia, serif;
88
+ --sans: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
89
+ --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
90
+
91
+ /* ── canonical cross-site alias layer (see ~/sites-shared/tokens.css) ── */
92
+ --surface: var(--bg-warm);
93
+ --border: var(--rule);
94
+ --text: var(--ink);
95
+ --muted: var(--ink-dim);
96
+ --accent: var(--cyan-bright);
97
+ }
98
+
99
+ * { box-sizing: border-box; margin: 0; padding: 0; }
100
+ html {
101
+ scroll-behavior: smooth;
102
+ overflow-x: clip;
103
+ /* bg on <html> so the fixed z-index:-1 page canvas sits above it */
104
+ background: var(--bg);
105
+ }
106
+ body {
107
+ font-family: var(--sans);
108
+ background: transparent;
109
+ color: var(--ink);
110
+ -webkit-font-smoothing: antialiased;
111
+ -moz-osx-font-smoothing: grayscale;
112
+ line-height: 1.6;
113
+ }
114
+ /* Full-page persistent Three.js backdrop (src/canvas.ts).
115
+ z-index:-1 sits above html bg, below all document content. */
116
+ #page-canvas {
117
+ position: fixed;
118
+ inset: 0;
119
+ width: 100vw;
120
+ height: 100vh;
121
+ z-index: -1;
122
+ pointer-events: none;
123
+ display: block;
124
+ }
125
+ ::selection { background: var(--cyan-bright); color: #02020a; }
126
+ a { color: inherit; text-decoration: none; }
127
+
128
+ /* ─────────────────────────────────────────────────────────
129
+ HERO — type-only editorial void.
130
+ We deliberately do NOT show any of the real demo here. The
131
+ reveal is the surprise behind the CTA.
132
+ ───────────────────────────────────────────────────────── */
133
+ .hero {
134
+ position: relative;
135
+ height: 100vh;
136
+ min-height: 700px;
137
+ width: 100%;
138
+ overflow: hidden;
139
+ display: flex;
140
+ flex-direction: column;
141
+ background: var(--bg);
142
+ }
143
+ /* Slow-drifting ambient gradient — subtle, hints at "something alive" */
144
+ .hero::before {
145
+ content: '';
146
+ position: absolute;
147
+ inset: -20%;
148
+ z-index: 0;
149
+ background:
150
+ radial-gradient(ellipse 55% 40% at 22% 30%, rgba(94, 234, 212, 0.05), transparent 60%),
151
+ radial-gradient(ellipse 50% 50% at 78% 70%, rgba(192, 132, 252, 0.04), transparent 60%),
152
+ radial-gradient(ellipse 40% 30% at 50% 92%, rgba(0, 229, 255, 0.03), transparent 60%);
153
+ animation: hero-drift 40s ease-in-out infinite alternate;
154
+ }
155
+ @keyframes hero-drift {
156
+ 0% { transform: translate(0, 0) rotate(0deg) scale(1); }
157
+ 50% { transform: translate(2%, -1.5%) rotate(1deg) scale(1.05); }
158
+ 100% { transform: translate(-1.5%, 2%) rotate(-1deg) scale(1.02); }
159
+ }
160
+ /* The per-hero canvas is gone — #page-canvas (fixed, z:-1) now handles it
161
+ across the whole document. The drift gradient on .hero::before stays as
162
+ a subtle warm tint since the page canvas behind it is transparent. */
163
+ /* Film grain overlay — editorial cinematic texture */
164
+ .hero-grain {
165
+ content: '';
166
+ position: absolute;
167
+ inset: 0;
168
+ z-index: 2;
169
+ pointer-events: none;
170
+ opacity: 0.07;
171
+ mix-blend-mode: overlay;
172
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
173
+ }
174
+ /* Bottom vignette to anchor the type and lead the eye to the scroll cue */
175
+ .hero-vignette {
176
+ position: absolute;
177
+ inset: 0;
178
+ z-index: 3;
179
+ pointer-events: none;
180
+ background:
181
+ linear-gradient(180deg, transparent 60%, rgba(8, 6, 15, 0.85) 100%);
182
+ }
183
+
184
+ /* Floating nav over the hero video */
185
+ .hero-nav {
186
+ position: relative;
187
+ z-index: 10;
188
+ display: flex;
189
+ justify-content: space-between;
190
+ align-items: center;
191
+ padding: 28px clamp(24px, 5vw, 64px);
192
+ animation: heroFadeIn 0.8s cubic-bezier(0.16,1,0.3,1) 0.1s both;
193
+ }
194
+ .logo {
195
+ font-family: var(--serif);
196
+ font-weight: 400;
197
+ font-size: 1.35rem;
198
+ letter-spacing: -0.01em;
199
+ font-variation-settings: "opsz" 144, "SOFT" 50;
200
+ }
201
+ .logo-neuro { color: var(--ink); }
202
+ .logo-pulse { color: var(--cyan-bright); font-style: italic; }
203
+ .nav-links {
204
+ display: flex;
205
+ gap: 32px;
206
+ align-items: center;
207
+ font-family: var(--sans);
208
+ font-size: 0.82rem;
209
+ letter-spacing: 0.04em;
210
+ text-transform: lowercase;
211
+ color: var(--ink-soft);
212
+ }
213
+ .nav-links a { transition: color 0.2s; }
214
+ .nav-links a:hover { color: var(--cyan-bright); }
215
+ .nav-star {
216
+ display: flex;
217
+ align-items: center;
218
+ gap: 6px;
219
+ padding: 6px 12px;
220
+ border: 1px solid rgba(244, 236, 223, 0.18);
221
+ border-radius: 999px;
222
+ background: rgba(8, 6, 15, 0.45);
223
+ backdrop-filter: blur(6px);
224
+ }
225
+ .nav-star svg { vertical-align: -3px; }
226
+ .star-count {
227
+ font-family: var(--mono);
228
+ font-size: 0.75rem;
229
+ color: var(--ink-soft);
230
+ }
231
+ @media (max-width: 720px) {
232
+ .nav-links { gap: 18px; font-size: 0.74rem; }
233
+ .nav-links .nav-link-hide { display: none; }
234
+ }
235
+
236
+ /* Hero text stack */
237
+ .hero-content {
238
+ position: relative;
239
+ z-index: 5;
240
+ flex: 1;
241
+ display: flex;
242
+ flex-direction: column;
243
+ justify-content: center;
244
+ padding: 0 clamp(24px, 5vw, 64px) clamp(60px, 10vh, 120px);
245
+ max-width: 1280px;
246
+ width: 100%;
247
+ margin: 0 auto;
248
+ }
249
+ /* Staggered entrance animations */
250
+ @keyframes heroFadeIn {
251
+ from { opacity: 0; transform: translateY(20px); }
252
+ to { opacity: 1; transform: translateY(0); }
253
+ }
254
+ .hero-eyebrow {
255
+ display: inline-flex;
256
+ align-items: center;
257
+ gap: 10px;
258
+ align-self: flex-start;
259
+ padding: 7px 14px;
260
+ margin-bottom: 36px;
261
+ border: 1px solid rgba(94, 234, 212, 0.35);
262
+ border-radius: 999px;
263
+ background: rgba(8, 6, 15, 0.55);
264
+ backdrop-filter: blur(8px);
265
+ font-family: var(--mono);
266
+ font-size: 0.72rem;
267
+ letter-spacing: 0.08em;
268
+ text-transform: lowercase;
269
+ color: var(--cyan);
270
+ animation: heroFadeIn 1s cubic-bezier(0.16,1,0.3,1) 0.2s both;
271
+ }
272
+ .hero-eyebrow .dot {
273
+ width: 7px;
274
+ height: 7px;
275
+ border-radius: 50%;
276
+ background: var(--cyan-bright);
277
+ box-shadow: 0 0 12px var(--cyan-bright);
278
+ animation: dot-pulse 2.4s ease-in-out infinite;
279
+ }
280
+ @keyframes dot-pulse {
281
+ 0%, 100% { opacity: 1; transform: scale(1); }
282
+ 50% { opacity: 0.5; transform: scale(0.85); }
283
+ }
284
+
285
+ .hero-h1 {
286
+ font-family: var(--serif);
287
+ font-weight: 300;
288
+ font-variation-settings: "opsz" 144, "SOFT" 30;
289
+ font-size: clamp(2.8rem, 8.5vw, 7rem);
290
+ line-height: 0.96;
291
+ letter-spacing: -0.035em;
292
+ color: var(--ink);
293
+ max-width: 14ch;
294
+ margin-bottom: 32px;
295
+ animation: heroFadeIn 1.2s cubic-bezier(0.16,1,0.3,1) 0.4s both;
296
+ }
297
+ .hero-h1 em {
298
+ font-style: italic;
299
+ font-weight: 300;
300
+ color: var(--cyan);
301
+ font-variation-settings: "opsz" 144, "SOFT" 100;
302
+ text-shadow: 0 0 40px rgba(0,229,255,0.15);
303
+ }
304
+ .hero-h1 .v {
305
+ color: var(--violet);
306
+ font-style: italic;
307
+ font-variation-settings: "opsz" 144, "SOFT" 100;
308
+ }
309
+
310
+ .hero-lede {
311
+ font-family: var(--serif);
312
+ font-weight: 300;
313
+ font-size: clamp(1.05rem, 1.6vw, 1.35rem);
314
+ line-height: 1.55;
315
+ color: var(--ink-soft);
316
+ max-width: 38rem;
317
+ margin-bottom: 44px;
318
+ animation: heroFadeIn 1.2s cubic-bezier(0.16,1,0.3,1) 0.65s both;
319
+ }
320
+ .hero-lede strong {
321
+ font-weight: 500;
322
+ color: var(--ink);
323
+ font-style: italic;
324
+ }
325
+
326
+ .cta-row {
327
+ display: flex;
328
+ gap: 18px;
329
+ flex-wrap: wrap;
330
+ align-items: center;
331
+ animation: heroFadeIn 1.2s cubic-bezier(0.16,1,0.3,1) 0.85s both;
332
+ }
333
+
334
+ /* Hero stats strip — the scale numbers earn the hero, not paragraph 4 */
335
+ .hero-stats {
336
+ display: flex;
337
+ flex-wrap: wrap;
338
+ gap: 28px 48px;
339
+ margin-top: 56px;
340
+ padding-top: 28px;
341
+ border-top: 1px solid var(--rule);
342
+ max-width: 44rem;
343
+ animation: heroFadeIn 1.2s cubic-bezier(0.16,1,0.3,1) 1.05s both;
344
+ }
345
+ .hero-stat { display: flex; flex-direction: column; gap: 8px; }
346
+ .hero-stat .v {
347
+ font-family: var(--serif);
348
+ font-weight: 300;
349
+ font-variation-settings: "opsz" 144, "SOFT" 60;
350
+ font-size: 1.85rem;
351
+ line-height: 1;
352
+ letter-spacing: -0.025em;
353
+ color: var(--ink);
354
+ }
355
+ .hero-stat .v em {
356
+ font-style: italic;
357
+ color: var(--cyan);
358
+ font-variation-settings: "opsz" 144, "SOFT" 100;
359
+ }
360
+ .hero-stat .l {
361
+ font-family: var(--mono);
362
+ font-size: 0.68rem;
363
+ letter-spacing: 0.14em;
364
+ text-transform: uppercase;
365
+ color: var(--ink-dim);
366
+ }
367
+ @media (max-width: 560px) {
368
+ .hero-stats { gap: 20px 32px; margin-top: 40px; padding-top: 22px; }
369
+ .hero-stat .v { font-size: 1.55rem; }
370
+ }
371
+ .btn {
372
+ display: inline-flex;
373
+ align-items: center;
374
+ gap: 10px;
375
+ padding: 14px 26px;
376
+ font-family: var(--sans);
377
+ font-size: 0.92rem;
378
+ font-weight: 500;
379
+ letter-spacing: 0.005em;
380
+ border-radius: 4px;
381
+ border: 1px solid transparent;
382
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
383
+ cursor: pointer;
384
+ white-space: nowrap;
385
+ }
386
+ .btn-primary {
387
+ background: var(--ink);
388
+ color: #02020a;
389
+ border-color: var(--ink);
390
+ }
391
+ .btn-primary:hover {
392
+ background: var(--cyan-bright);
393
+ border-color: var(--cyan-bright);
394
+ transform: translateY(-1px);
395
+ box-shadow: 0 12px 32px rgba(0, 229, 255, 0.32);
396
+ }
397
+ .btn-ghost {
398
+ background: transparent;
399
+ color: var(--ink);
400
+ border-color: rgba(244, 236, 223, 0.32);
401
+ backdrop-filter: blur(6px);
402
+ }
403
+ .btn-ghost:hover {
404
+ border-color: var(--ink);
405
+ background: rgba(244, 236, 223, 0.06);
406
+ }
407
+ .btn .arrow {
408
+ transition: transform 0.25s;
409
+ }
410
+ .btn:hover .arrow {
411
+ transform: translateX(4px);
412
+ }
413
+
414
+ /* Scroll cue at the bottom of the hero */
415
+ .scroll-cue {
416
+ position: absolute;
417
+ z-index: 5;
418
+ bottom: 32px;
419
+ left: 50%;
420
+ transform: translateX(-50%);
421
+ display: flex;
422
+ flex-direction: column;
423
+ align-items: center;
424
+ gap: 12px;
425
+ font-family: var(--mono);
426
+ font-size: 0.65rem;
427
+ letter-spacing: 0.16em;
428
+ text-transform: uppercase;
429
+ color: var(--ink-dim);
430
+ }
431
+ .scroll-line {
432
+ width: 1px;
433
+ height: 36px;
434
+ background: linear-gradient(180deg, transparent, var(--ink-dim));
435
+ position: relative;
436
+ overflow: hidden;
437
+ }
438
+ .scroll-line::after {
439
+ content: '';
440
+ position: absolute;
441
+ top: -16px;
442
+ left: 0;
443
+ right: 0;
444
+ height: 16px;
445
+ background: var(--cyan-bright);
446
+ animation: scroll-fall 2.4s ease-in-out infinite;
447
+ }
448
+ @keyframes scroll-fall {
449
+ 0% { transform: translateY(0); opacity: 0; }
450
+ 30% { opacity: 1; }
451
+ 100% { transform: translateY(52px); opacity: 0; }
452
+ }
453
+
454
+ /* ─────────────────────────────────────────────────────────
455
+ EDITORIAL — narrow column long-read
456
+ ───────────────────────────────────────────────────────── */
457
+ .essay {
458
+ max-width: 680px;
459
+ margin: 0 auto;
460
+ padding: clamp(80px, 14vh, 160px) clamp(24px, 5vw, 32px);
461
+ }
462
+ .essay-section {
463
+ margin-bottom: clamp(80px, 12vh, 140px);
464
+ opacity: 0; transform: translateY(32px);
465
+ transition: opacity 0.8s cubic-bezier(0.16,1,0.3,1), transform 0.8s cubic-bezier(0.16,1,0.3,1);
466
+ }
467
+ .essay-section.visible { opacity: 1; transform: translateY(0); }
468
+ .essay-end {
469
+ opacity: 0; transform: translateY(32px);
470
+ transition: opacity 0.8s cubic-bezier(0.16,1,0.3,1), transform 0.8s cubic-bezier(0.16,1,0.3,1);
471
+ }
472
+ .essay-end.visible { opacity: 1; transform: translateY(0); }
473
+ .essay-num {
474
+ font-family: var(--mono);
475
+ font-size: 0.74rem;
476
+ letter-spacing: 0.18em;
477
+ text-transform: uppercase;
478
+ color: var(--cyan);
479
+ margin-bottom: 28px;
480
+ display: flex;
481
+ align-items: center;
482
+ gap: 14px;
483
+ }
484
+ .essay-num::before {
485
+ content: '';
486
+ width: 28px;
487
+ height: 1px;
488
+ background: var(--cyan);
489
+ }
490
+ .essay h2 {
491
+ font-family: var(--serif);
492
+ font-weight: 300;
493
+ font-variation-settings: "opsz" 144, "SOFT" 40;
494
+ font-size: clamp(2rem, 4.4vw, 3.4rem);
495
+ line-height: 1.04;
496
+ letter-spacing: -0.025em;
497
+ color: var(--ink);
498
+ margin-bottom: 44px;
499
+ max-width: 18ch;
500
+ }
501
+ .essay h2 em {
502
+ font-style: italic;
503
+ color: var(--cyan);
504
+ font-variation-settings: "opsz" 144, "SOFT" 100;
505
+ }
506
+ .essay p {
507
+ font-family: var(--serif);
508
+ font-weight: 300;
509
+ font-variation-settings: "opsz" 14, "SOFT" 40;
510
+ font-size: 1.18rem;
511
+ line-height: 1.72;
512
+ color: var(--ink-soft);
513
+ margin-bottom: 1.4em;
514
+ max-width: 38rem;
515
+ }
516
+ .essay p strong {
517
+ font-weight: 500;
518
+ color: var(--ink);
519
+ }
520
+ .essay p em {
521
+ font-style: italic;
522
+ color: var(--ink);
523
+ }
524
+ .essay p code, .essay p .mono {
525
+ font-family: var(--mono);
526
+ font-size: 0.92em;
527
+ color: var(--cyan);
528
+ background: rgba(94, 234, 212, 0.08);
529
+ padding: 1px 6px;
530
+ border-radius: 3px;
531
+ }
532
+
533
+ /* Drop cap on the first paragraph of section 01 */
534
+ .essay-section.lead p:first-of-type::first-letter {
535
+ font-family: var(--serif);
536
+ font-weight: 300;
537
+ font-variation-settings: "opsz" 144, "SOFT" 100;
538
+ font-size: 5.2em;
539
+ float: left;
540
+ line-height: 0.82;
541
+ margin: 0.08em 0.12em -0.05em 0;
542
+ color: var(--cyan);
543
+ font-style: italic;
544
+ }
545
+
546
+ /* Pull quote */
547
+ .pull {
548
+ font-family: var(--serif);
549
+ font-weight: 300;
550
+ font-variation-settings: "opsz" 144, "SOFT" 60;
551
+ font-style: italic;
552
+ font-size: clamp(1.4rem, 2.6vw, 2rem);
553
+ line-height: 1.32;
554
+ color: var(--ink);
555
+ border-left: 2px solid var(--cyan);
556
+ padding: 8px 0 8px 28px;
557
+ margin: 48px 0;
558
+ max-width: 36rem;
559
+ }
560
+
561
+ /* Horizontal rule between essay sections */
562
+ .essay-rule {
563
+ border: none;
564
+ height: 1px;
565
+ background: var(--rule);
566
+ max-width: 4rem;
567
+ margin: 0 0 clamp(80px, 12vh, 140px);
568
+ }
569
+
570
+ /* Promise — three small evidence cards inline in the essay column */
571
+ .evidence {
572
+ display: grid;
573
+ gap: 1px;
574
+ background: var(--rule);
575
+ border: 1px solid var(--rule);
576
+ margin: 48px 0 8px;
577
+ }
578
+ .evidence-row {
579
+ display: grid;
580
+ grid-template-columns: 90px 1fr;
581
+ background: var(--bg);
582
+ padding: 24px 4px 24px 0;
583
+ align-items: start;
584
+ }
585
+ .evidence-key {
586
+ font-family: var(--mono);
587
+ font-size: 0.72rem;
588
+ letter-spacing: 0.1em;
589
+ text-transform: uppercase;
590
+ color: var(--cyan);
591
+ padding-top: 4px;
592
+ }
593
+ .evidence-val {
594
+ font-family: var(--sans);
595
+ font-size: 0.98rem;
596
+ line-height: 1.55;
597
+ color: var(--ink);
598
+ }
599
+ .evidence-val span {
600
+ display: block;
601
+ color: var(--ink-dim);
602
+ font-size: 0.86rem;
603
+ margin-top: 4px;
604
+ }
605
+
606
+ /* Anatomy diagram inline */
607
+ .anatomy-figure {
608
+ margin: 48px 0;
609
+ text-align: center;
610
+ }
611
+ .anatomy-figure svg {
612
+ width: 100%;
613
+ max-width: 520px;
614
+ height: auto;
615
+ }
616
+ .anatomy-figure figcaption {
617
+ font-family: var(--mono);
618
+ font-size: 0.72rem;
619
+ letter-spacing: 0.08em;
620
+ color: var(--ink-dim);
621
+ margin-top: 16px;
622
+ }
623
+
624
+ /* Anatomy parts list */
625
+ .parts-list {
626
+ list-style: none;
627
+ margin: 40px 0;
628
+ border-top: 1px solid var(--rule);
629
+ }
630
+ .parts-list li {
631
+ padding: 22px 0;
632
+ border-bottom: 1px solid var(--rule);
633
+ display: grid;
634
+ grid-template-columns: 1fr;
635
+ gap: 8px;
636
+ }
637
+ .parts-list strong {
638
+ font-family: var(--sans);
639
+ font-weight: 500;
640
+ font-size: 0.98rem;
641
+ color: var(--ink);
642
+ letter-spacing: -0.005em;
643
+ }
644
+ .parts-list span {
645
+ font-family: var(--serif);
646
+ font-weight: 300;
647
+ font-size: 1.04rem;
648
+ line-height: 1.6;
649
+ color: var(--ink-soft);
650
+ }
651
+
652
+ /* Validation report block */
653
+ .proof {
654
+ font-family: var(--mono);
655
+ font-size: 0.82rem;
656
+ line-height: 1.85;
657
+ background: #050309;
658
+ border: 1px solid var(--rule);
659
+ border-radius: 4px;
660
+ padding: 28px 32px;
661
+ margin: 40px 0;
662
+ color: var(--ink-soft);
663
+ overflow-x: auto;
664
+ }
665
+ .proof .head {
666
+ color: var(--cyan-bright);
667
+ margin-bottom: 12px;
668
+ font-weight: 500;
669
+ }
670
+ .proof .ok { color: #6ee7b7; }
671
+ .proof .label { color: var(--ink-dim); }
672
+ .proof .k { color: var(--violet); }
673
+
674
+ /* Stack list */
675
+ .stack-list {
676
+ list-style: none;
677
+ counter-reset: stack;
678
+ margin: 40px 0;
679
+ }
680
+ .stack-list li {
681
+ counter-increment: stack;
682
+ padding: 28px 0;
683
+ border-top: 1px solid var(--rule);
684
+ display: grid;
685
+ grid-template-columns: 56px 1fr;
686
+ gap: 16px;
687
+ align-items: start;
688
+ }
689
+ .stack-list li:last-child { border-bottom: 1px solid var(--rule); }
690
+ .stack-list li::before {
691
+ content: counter(stack, decimal-leading-zero);
692
+ font-family: var(--mono);
693
+ font-size: 0.78rem;
694
+ color: var(--cyan);
695
+ letter-spacing: 0.06em;
696
+ padding-top: 5px;
697
+ }
698
+ .stack-list strong {
699
+ display: block;
700
+ font-family: var(--sans);
701
+ font-weight: 500;
702
+ font-size: 1.02rem;
703
+ color: var(--ink);
704
+ margin-bottom: 6px;
705
+ }
706
+ .stack-list span {
707
+ font-family: var(--serif);
708
+ font-weight: 300;
709
+ font-size: 1.02rem;
710
+ line-height: 1.6;
711
+ color: var(--ink-soft);
712
+ }
713
+ .stack-list code {
714
+ font-family: var(--mono);
715
+ font-size: 0.86em;
716
+ color: var(--cyan);
717
+ background: rgba(94, 234, 212, 0.08);
718
+ padding: 1px 6px;
719
+ border-radius: 3px;
720
+ }
721
+
722
+ /* ─────────────────────────────────────────────────────────
723
+ "In real numbers" — full-bleed scale payoff between 04 and the CTA.
724
+ Breaks out of the 680px essay column because these numbers want room.
725
+ ───────────────────────────────────────────────────────── */
726
+ .numbers {
727
+ position: relative;
728
+ left: 50%;
729
+ right: 50%;
730
+ width: 100vw;
731
+ max-width: 100vw;
732
+ margin-left: -50vw;
733
+ margin-right: -50vw;
734
+ margin-bottom: clamp(80px, 12vh, 140px);
735
+ padding: clamp(56px, 9vh, 100px) clamp(24px, 5vw, 64px);
736
+ background:
737
+ radial-gradient(ellipse 80% 40% at 50% 0%, rgba(94, 234, 212, 0.04), transparent 60%),
738
+ radial-gradient(ellipse 80% 40% at 50% 100%, rgba(192, 132, 252, 0.03), transparent 60%);
739
+ border-top: 1px solid var(--rule);
740
+ border-bottom: 1px solid var(--rule);
741
+ opacity: 0; transform: translateY(32px);
742
+ transition: opacity 0.8s cubic-bezier(0.16,1,0.3,1), transform 0.8s cubic-bezier(0.16,1,0.3,1);
743
+ }
744
+ .numbers.visible { opacity: 1; transform: translateY(0); }
745
+ .numbers-inner { max-width: 1280px; margin: 0 auto; position: relative; z-index: 2; }
746
+ /* Per-section numbers canvas is gone — #page-canvas's prog uniform
747
+ ramps up horizontal flow automatically in this region. */
748
+ .numbers h2 {
749
+ font-family: var(--serif);
750
+ font-weight: 300;
751
+ font-variation-settings: "opsz" 144, "SOFT" 40;
752
+ font-size: clamp(2rem, 4.4vw, 3.4rem);
753
+ line-height: 1.04;
754
+ letter-spacing: -0.025em;
755
+ color: var(--ink);
756
+ margin: 24px 0 60px;
757
+ max-width: 22ch;
758
+ }
759
+ .numbers h2 em {
760
+ font-style: italic;
761
+ color: var(--cyan);
762
+ font-variation-settings: "opsz" 144, "SOFT" 100;
763
+ }
764
+ .numbers-grid {
765
+ display: grid;
766
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
767
+ gap: 1px;
768
+ background: var(--rule);
769
+ border: 1px solid var(--rule);
770
+ }
771
+ .numbers-cell {
772
+ padding: clamp(32px, 5vh, 56px) clamp(24px, 3vw, 40px);
773
+ background: var(--bg);
774
+ display: flex;
775
+ flex-direction: column;
776
+ gap: 18px;
777
+ transition: background 0.35s;
778
+ }
779
+ .numbers-cell:hover { background: var(--bg-warm); }
780
+ .numbers-num {
781
+ font-family: var(--serif);
782
+ font-weight: 300;
783
+ font-variation-settings: "opsz" 144, "SOFT" 80;
784
+ font-size: clamp(3rem, 6.4vw, 5.2rem);
785
+ line-height: 0.95;
786
+ letter-spacing: -0.03em;
787
+ color: var(--ink);
788
+ }
789
+ .numbers-num em {
790
+ font-style: italic;
791
+ color: var(--cyan);
792
+ font-variation-settings: "opsz" 144, "SOFT" 100;
793
+ }
794
+ .numbers-label {
795
+ font-family: var(--mono);
796
+ font-size: 0.7rem;
797
+ letter-spacing: 0.14em;
798
+ text-transform: uppercase;
799
+ color: var(--cyan);
800
+ }
801
+ .numbers-sub {
802
+ font-family: var(--serif);
803
+ font-weight: 300;
804
+ font-size: 1rem;
805
+ line-height: 1.55;
806
+ color: var(--ink-soft);
807
+ max-width: 32ch;
808
+ }
809
+
810
+ /* End-of-essay closing CTA */
811
+ .essay-end {
812
+ text-align: left;
813
+ padding-top: 40px;
814
+ }
815
+ .essay-end h2 {
816
+ margin-bottom: 24px;
817
+ }
818
+ .essay-end p {
819
+ margin-bottom: 36px;
820
+ }
821
+
822
+ /* ─────────────────────────────────────────────────────────
823
+ FOOTER — minimal, full width
824
+ ───────────────────────────────────────────────────────── */
825
+ footer {
826
+ border-top: 1px solid var(--rule);
827
+ padding: 48px clamp(24px, 5vw, 64px);
828
+ margin-top: 80px;
829
+ }
830
+ .footer-inner {
831
+ max-width: 1280px;
832
+ margin: 0 auto;
833
+ display: flex;
834
+ flex-direction: column;
835
+ gap: 28px;
836
+ }
837
+ .footer-row {
838
+ display: flex;
839
+ justify-content: space-between;
840
+ align-items: center;
841
+ flex-wrap: wrap;
842
+ gap: 20px;
843
+ }
844
+ .footer-credit {
845
+ font-family: var(--sans);
846
+ font-size: 0.85rem;
847
+ color: var(--ink-dim);
848
+ }
849
+ .footer-credit a {
850
+ color: var(--ink-soft);
851
+ transition: color 0.2s;
852
+ }
853
+ .footer-credit a:hover { color: var(--cyan-bright); }
854
+ .footer-links {
855
+ display: flex;
856
+ gap: 24px;
857
+ align-items: center;
858
+ font-family: var(--sans);
859
+ font-size: 0.82rem;
860
+ letter-spacing: 0.02em;
861
+ text-transform: lowercase;
862
+ color: var(--ink-soft);
863
+ }
864
+ .footer-links a { transition: color 0.2s; }
865
+ .footer-links a:hover { color: var(--cyan-bright); }
866
+ .footer-related {
867
+ display: flex;
868
+ gap: 18px;
869
+ align-items: center;
870
+ flex-wrap: wrap;
871
+ font-family: var(--mono);
872
+ font-size: 0.74rem;
873
+ color: var(--ink-dim);
874
+ }
875
+ .footer-related .label {
876
+ text-transform: uppercase;
877
+ letter-spacing: 0.1em;
878
+ color: var(--ink-faint);
879
+ }
880
+ .footer-related a {
881
+ color: var(--ink-soft);
882
+ transition: color 0.2s;
883
+ }
884
+ .footer-related a:hover { color: var(--cyan-bright); }
885
+ .footer-bottom {
886
+ font-family: var(--mono);
887
+ font-size: 0.7rem;
888
+ color: var(--ink-faint);
889
+ letter-spacing: 0.06em;
890
+ display: flex;
891
+ justify-content: space-between;
892
+ flex-wrap: wrap;
893
+ gap: 12px;
894
+ padding-top: 20px;
895
+ border-top: 1px solid var(--rule);
896
+ }
897
+
898
+ /* Responsive */
899
+ @media (max-width: 720px) {
900
+ .hero { min-height: 600px; }
901
+ .hero-h1 { font-size: clamp(2.4rem, 11vw, 4rem); }
902
+ .hero-lede { font-size: 1rem; }
903
+ .essay { padding: 80px 24px; }
904
+ .essay h2 { font-size: clamp(1.7rem, 7vw, 2.4rem); }
905
+ .essay p { font-size: 1.08rem; }
906
+ .evidence-row { grid-template-columns: 1fr; gap: 8px; padding: 20px 0; }
907
+ .evidence-key { padding-top: 0; }
908
+ .stack-list li { grid-template-columns: 40px 1fr; }
909
+ .scroll-cue { display: none; }
910
+ }
911
+ </style>
912
+ <script type="module" crossorigin src="./assets/main-D1-Ogq2V.js"></script>
913
+ <link rel="modulepreload" crossorigin href="./assets/three.module-Bwi0mW4U.js">
914
+ </head>
915
+ <body>
916
+
917
+ <!-- Persistent page-wide Three.js backdrop. One canvas, scroll-driven. -->
918
+ <canvas id="page-canvas" aria-hidden="true"></canvas>
919
+
920
+ <!-- ───────────────────────────────────────────────
921
+ HERO — type-only editorial void.
922
+ Deliberately no preview of the demo: the reveal
923
+ is the surprise behind the CTA. Page canvas sits
924
+ behind this entire section at z:-1.
925
+ ─────────────────────────────────────────────── -->
926
+ <section class="hero">
927
+ <div class="hero-grain"></div>
928
+ <div class="hero-vignette"></div>
929
+
930
+ <nav class="hero-nav">
931
+ <a href="/" class="logo">
932
+ <span class="logo-neuro">neuro</span><span class="logo-pulse">pulse</span>
933
+ </a>
934
+ <div class="nav-links">
935
+ <a href="#essay" class="nav-link-hide">essay</a>
936
+ <a href="#stack" class="nav-link-hide">stack</a>
937
+ <a href="/app/">demo</a>
938
+ <a href="https://github.com/abgnydn/neuropulse" target="_blank" rel="noopener" class="nav-star">
939
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
940
+ <span class="star-count" id="navStarCount">·</span>
941
+ </a>
942
+ </div>
943
+ </nav>
944
+
945
+ <div class="hero-content">
946
+ <div class="hero-eyebrow">
947
+ <span class="dot"></span>
948
+ live · phi-3-mini · webgpu
949
+ </div>
950
+ <h1 class="hero-h1">
951
+ A real transformer, <em>laid&nbsp;bare.</em>
952
+ </h1>
953
+ <p class="hero-lede">
954
+ 3.8 billion parameters. On your GPU, in your browser. Every point, every line, every pulse — a real tensor, read one-to-one. <strong>The model itself, drawn from the inside.</strong> <em>Shift-click any of the 1,024 attention heads to turn it off and watch what breaks.</em>
955
+ </p>
956
+ <div class="cta-row">
957
+ <a href="/app/" class="btn btn-primary">
958
+ Launch the demo
959
+ <span class="arrow">→</span>
960
+ </a>
961
+ <a href="#essay" class="btn btn-ghost">Read the essay</a>
962
+ </div>
963
+
964
+ <div class="hero-stats">
965
+ <div class="hero-stat">
966
+ <div class="v"><em>3.8</em>B</div>
967
+ <div class="l">parameters</div>
968
+ </div>
969
+ <div class="hero-stat">
970
+ <div class="v">292</div>
971
+ <div class="l">dispatches / token</div>
972
+ </div>
973
+ <div class="hero-stat">
974
+ <div class="v">22</div>
975
+ <div class="l">GPU buffers</div>
976
+ </div>
977
+ <div class="hero-stat">
978
+ <div class="v"><em>0</em></div>
979
+ <div class="l">servers</div>
980
+ </div>
981
+ </div>
982
+ </div>
983
+
984
+ <div class="scroll-cue">
985
+ <span>scroll</span>
986
+ <div class="scroll-line"></div>
987
+ </div>
988
+ </section>
989
+
990
+ <!-- ───────────────────────────────────────────────
991
+ EDITORIAL LONG-READ
992
+ ─────────────────────────────────────────────── -->
993
+ <article class="essay" id="essay">
994
+
995
+ <section class="essay-section lead">
996
+ <div class="essay-num">01 · The Promise</div>
997
+ <h2>The model, <em>drawn from inside.</em></h2>
998
+ <p>Most "AI visualizations" online are decoration — dots pulsing to a chosen rhythm, animated metaphors built on top of static images. The model itself sits somewhere else entirely.</p>
999
+ <p>Neuropulse takes the opposite path. Every brightness, every line, every motion is a direct readout of a real <span class="mono">WebGPU</span> buffer mid-forward-pass. When the model thinks, you watch <em>it</em> think — the thing itself, not a representation of it.</p>
1000
+ <p class="pull">Strict 1:1. Every pixel a function of a real tensor.</p>
1001
+ <p>3.8B weights in your GPU. Attention in <span class="mono">WGSL</span>. Next-token sampled in your tab. The whole pipeline lives inside this page — close it and the inference stops with it.</p>
1002
+
1003
+ <div class="evidence">
1004
+ <div class="evidence-row">
1005
+ <div class="evidence-key">model</div>
1006
+ <div class="evidence-val">Phi-3-mini, q4f16_1<span>3.8B parameters · same weights Microsoft ships</span></div>
1007
+ </div>
1008
+ <div class="evidence-row">
1009
+ <div class="evidence-key">runtime</div>
1010
+ <div class="evidence-val">WebGPU compute shaders<span>13 pipelines · 22 buffers · 292 dispatches per token</span></div>
1011
+ </div>
1012
+ <div class="evidence-row">
1013
+ <div class="evidence-key">privacy</div>
1014
+ <div class="evidence-val">Your GPU only<span>zero server calls · nothing leaves your machine</span></div>
1015
+ </div>
1016
+ </div>
1017
+ </section>
1018
+
1019
+ <hr class="essay-rule"/>
1020
+
1021
+ <section class="essay-section">
1022
+ <div class="essay-num">02 · What you're watching</div>
1023
+ <h2>Every part of the model, <em>labeled.</em></h2>
1024
+ <p>The 3D scene is not a metaphor. Every glowing element maps to a specific tensor in Phi-3-mini's compute graph.</p>
1025
+ <p>The 3,072 points of the residual stream are laid out by PCA of the model's layer-0 <code>qkv_proj</code> weights — so dims read into attention together sit near each other. Each point's brightness is the <em>live</em> value of that residual dim on every step.</p>
1026
+ <p>Hover an attention head — the brightness you see <em>is</em> that head's output magnitude.</p>
1027
+
1028
+ <figure class="anatomy-figure">
1029
+ <svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
1030
+ <defs>
1031
+ <radialGradient id="anaCyan" cx="50%" cy="50%"><stop offset="0%" stop-color="#5eead4" stop-opacity="0.9"/><stop offset="100%" stop-color="#5eead4" stop-opacity="0"/></radialGradient>
1032
+ <radialGradient id="anaViolet" cx="50%" cy="50%"><stop offset="0%" stop-color="#c084fc" stop-opacity="0.85"/><stop offset="100%" stop-color="#c084fc" stop-opacity="0"/></radialGradient>
1033
+ </defs>
1034
+ <g stroke="rgba(94,234,212,0.18)" stroke-width="0.5">
1035
+ <line x1="50" y1="60" x2="350" y2="60"/>
1036
+ <line x1="50" y1="100" x2="350" y2="100"/>
1037
+ <line x1="50" y1="140" x2="350" y2="140"/>
1038
+ <line x1="50" y1="180" x2="350" y2="180"/>
1039
+ <line x1="50" y1="220" x2="350" y2="220"/>
1040
+ <line x1="50" y1="260" x2="350" y2="260"/>
1041
+ <line x1="50" y1="300" x2="350" y2="300"/>
1042
+ <line x1="50" y1="340" x2="350" y2="340"/>
1043
+ </g>
1044
+ <circle cx="140" cy="90" r="32" fill="url(#anaCyan)"/>
1045
+ <text x="68" y="55" font-family="JetBrains Mono, monospace" font-size="9" fill="#5eead4">attention heads</text>
1046
+ <line x1="140" y1="90" x2="92" y2="58" stroke="#5eead4" stroke-width="0.5"/>
1047
+
1048
+ <rect x="165" y="160" width="70" height="70" fill="url(#anaViolet)" rx="6"/>
1049
+ <text x="245" y="200" font-family="JetBrains Mono, monospace" font-size="9" fill="#c084fc">FFN slab</text>
1050
+ <line x1="235" y1="195" x2="245" y2="197" stroke="#c084fc" stroke-width="0.5"/>
1051
+
1052
+ <line x1="200" y1="30" x2="200" y2="370" stroke="#5eead4" stroke-opacity="0.4" stroke-width="2"/>
1053
+ <text x="205" y="22" font-family="JetBrains Mono, monospace" font-size="9" fill="#5eead4">residual stream</text>
1054
+
1055
+ <g>
1056
+ <rect x="290" y="260" width="8" height="60" fill="#5eead4" opacity="0.4"/>
1057
+ <rect x="302" y="260" width="8" height="60" fill="#5eead4" opacity="0.5"/>
1058
+ <rect x="314" y="260" width="8" height="60" fill="#5eead4" opacity="0.6"/>
1059
+ <rect x="326" y="260" width="8" height="60" fill="#5eead4" opacity="0.4"/>
1060
+ </g>
1061
+ <text x="278" y="340" font-family="JetBrains Mono, monospace" font-size="9" fill="#5eead4">KV cache</text>
1062
+
1063
+ <circle cx="200" cy="370" r="14" fill="#ff8c42" opacity="0.5"/>
1064
+ <text x="135" y="395" font-family="JetBrains Mono, monospace" font-size="9" fill="#ff8c42">LM head → next token</text>
1065
+
1066
+ <circle cx="200" cy="30" r="10" fill="#c084fc" opacity="0.7"/>
1067
+ </svg>
1068
+ <figcaption>fig. 1 — the anatomy of a single forward pass</figcaption>
1069
+ </figure>
1070
+
1071
+ <ul class="parts-list">
1072
+ <li>
1073
+ <strong>32 layer rings</strong>
1074
+ <span>Each ring is one transformer block. Brightness tracks the post-attention plus post-FFN residual norm for that layer — watch the signal build as the prompt flows upward.</span>
1075
+ </li>
1076
+ <li>
1077
+ <strong>32 attention heads per layer</strong>
1078
+ <span>Cyan heads on the outer ring. Each lights up proportional to its output magnitude. 1,024 heads in total (32 layers × 32 heads), all live.</span>
1079
+ </li>
1080
+ <li>
1081
+ <strong>FFN slab</strong>
1082
+ <span>The violet 8,192-neuron expansion. By far the largest compute budget in the model. You can see it pulse as the MLP activates.</span>
1083
+ </li>
1084
+ <li>
1085
+ <strong>Residual stream (3,072 dims)</strong>
1086
+ <span>The highway through the network. 3,072 points, one per dim, placed by PCA of the layer-0 <code>qkv_proj</code> weights so functionally related dims sit near each other. Brightness on each point is the live residual value at that dim.</span>
1087
+ </li>
1088
+ <li>
1089
+ <strong>KV cache strips</strong>
1090
+ <span>The growing memory of past tokens. Each strip is one position; height equals cache fill for that layer.</span>
1091
+ </li>
1092
+ <li>
1093
+ <strong>LM head</strong>
1094
+ <span>Final projection to 32,064 vocab logits. Softmax → next token. The live top-k distribution prints to the side panel as the model decodes.</span>
1095
+ </li>
1096
+ </ul>
1097
+ </section>
1098
+
1099
+ <hr class="essay-rule"/>
1100
+
1101
+ <section class="essay-section">
1102
+ <div class="essay-num">03 · Validation</div>
1103
+ <h2>Cross-checked against <em>reference Phi-3.</em></h2>
1104
+ <p>"Strict 1:1" is a strong claim, so it has to be falsifiable. Neuropulse ships with a built-in test suite that diffs the WebGPU implementation against a reference HuggingFace fp16 Phi-3-mini on a fixed set of prompts cached as <code>reference.json</code>. Click the <strong>wrench</strong> icon inside the demo to run it — the actual numbers from <em>your</em> GPU print to your browser console.</p>
1105
+
1106
+ <div class="proof">
1107
+ <div class="head">═══ What the suite checks ═══</div>
1108
+ <div>GPU: <span class="k">q4f16_1 Phi-3-mini</span> &nbsp; Reference: <span class="k">HF fp16 Phi-3-mini</span></div>
1109
+ <div>&nbsp;</div>
1110
+ <div><span class="label">[1]</span> <span class="ok">Tokenizer</span> — GPU input ids match HF byte-for-byte on every prompt</div>
1111
+ <div><span class="label">[2]</span> <span class="ok">Hidden states</span> — full 3,072-dim residual diffed at layers 0, 4, 8, 12, 16, 20, 24, 28, 31</div>
1112
+ <div><span class="label">[3]</span> <span class="ok">Attention (layer 31)</span> — online softmax cross-checked against an explicit-softmax reference path</div>
1113
+ <div><span class="label">[4]</span> <span class="ok">Logits</span> — top-k probabilities + Jensen–Shannon divergence vs HF on a 15-prompt sweep, teacher-forced for 5 steps each</div>
1114
+ <div><span class="label">[5]</span> <span class="ok">Long context</span> — 290-token prompt, 10 decode steps, top-1 matched against HF</div>
1115
+ <div><span class="label">[6]</span> <span class="ok">Sampler</span> — 5,000-sample empirical distribution vs softmax, JSD &lt; 1e-2</div>
1116
+ </div>
1117
+
1118
+ <p>Expect tiny deltas at the hidden-state level — that's the cost of int4, not drift. What matters is the last line: <strong>identical top-1 tokens vs fp16 Phi-3</strong> on the test set. Re-run it on your own machine in under a minute.</p>
1119
+ </section>
1120
+
1121
+ <hr class="essay-rule"/>
1122
+
1123
+ <section class="essay-section">
1124
+ <div class="essay-num">04 · Causal Scrubbing</div>
1125
+ <h2>And you can <em>reach in.</em></h2>
1126
+ <p>Watching is the easy half. The harder, more interesting half: <strong>shift-click any of the 1,024 attention-head spheres</strong> and that head's contribution to the residual stream is zeroed before O-projection — the canonical single-head ablation from the mech-interp literature, running live on the same WebGPU context that just rendered the frame. Press <strong>Run&nbsp;ablated</strong> and the same prompt generates twice, side by side: once whole, once missing that one head.</p>
1127
+
1128
+ <p>Or pick a layer, run a sweep, and let it ablate every head in turn. The panel paints a strip the model itself authored — each cell colored by how much the answer moved when that head went silent. <strong>Cool tones for the heads the prompt didn't need; warm ones for the heads it leaned on.</strong> About sixty seconds buys you a layer's importance map.</p>
1129
+
1130
+ <div class="proof">
1131
+ <div class="head">═══ Verified ablation gradient · prompt: "Paris is the capital of" ═══</div>
1132
+ <div>&nbsp;</div>
1133
+ <div><span class="label">{L0}</span> <span class="ok">all heads off</span> — output barely shifts. Layer-0 attention near the embeddings is heavily redundant.</div>
1134
+ <div><span class="label">{L31}</span> <span class="ok">all heads off</span> — diverges at the low-certainty positions. The final layer's FFN and residual still carry the decision.</div>
1135
+ <div><span class="label">{L28..L31}</span> <span class="ok">stacked</span> — generation hits the stop token early. Compounded ablation bites.</div>
1136
+ <div><span class="label">every layer off</span> — pure FFN + residual, no token-to-token information flow. <span class="k">"-,unlintzegesenma\\. #quierudo\\i'mʔholmo&lt;0x95&gt;-"</span></div>
1137
+ </div>
1138
+
1139
+ <p>This is the same primitive that found "induction heads" and traced the circuits behind in-context learning — work that, until now, lived in Python notebooks tethered to CPUs and A100s. <strong>Here it runs in a browser tab, on your laptop, in real time, on a 3.8B-parameter model.</strong> The next thirty seconds of your life can look exactly like a mech-interp paper figure.</p>
1140
+ </section>
1141
+
1142
+ <hr class="essay-rule"/>
1143
+
1144
+ <section class="essay-section" id="stack">
1145
+ <div class="essay-num">05 · The Stack</div>
1146
+ <h2>How it's <em>built.</em></h2>
1147
+ <p>Four pieces. No frameworks for the inference path, no dependency soup, no clever tricks hiding the model from you.</p>
1148
+
1149
+ <ol class="stack-list">
1150
+ <li>
1151
+ <div>
1152
+ <strong>WebGPU compute &amp; WGSL</strong>
1153
+ <span>13 pipelines, 22 buffers, 292 dispatches per token. Quantization: <code>q4f16_1</code>. Hand-written attention and FFN kernels.</span>
1154
+ </div>
1155
+ </li>
1156
+ <li>
1157
+ <div>
1158
+ <strong>MLC Phi-3-mini weights</strong>
1159
+ <span>The same weights as <code>mlc-ai/Phi-3-mini-4k-instruct-q4f16_1-MLC</code>, fetched directly from HuggingFace and cached in the browser's Cache API.</span>
1160
+ </div>
1161
+ </li>
1162
+ <li>
1163
+ <div>
1164
+ <strong>Three.js scene</strong>
1165
+ <span>Plain <code>WebGLRenderer</code>. No bloom, no particles, no decorative shaders. Every pixel pulls from a real tensor on every frame.</span>
1166
+ </div>
1167
+ </li>
1168
+ <li>
1169
+ <div>
1170
+ <strong>PCA layout from the model's own weights</strong>
1171
+ <span>Residual points are placed by PCA of layer 0's <code>qkv_proj.weight</code> columns; FFN points by PCA of <code>down_proj.weight</code>. Dims that get read or written together end up near each other, so the geometry is shaped by the model itself, not by hand.</span>
1172
+ </div>
1173
+ </li>
1174
+ </ol>
1175
+ </section>
1176
+
1177
+ <hr class="essay-rule"/>
1178
+
1179
+ <section class="numbers" id="numbers">
1180
+ <div class="numbers-inner">
1181
+ <div class="essay-num">06 · In real numbers</div>
1182
+ <h2>What it <em>actually</em> is.</h2>
1183
+ <div class="numbers-grid">
1184
+ <div class="numbers-cell">
1185
+ <div class="numbers-num"><em>3.8</em>B</div>
1186
+ <div class="numbers-label">parameters</div>
1187
+ <div class="numbers-sub">Phi-3-mini, q4f16_1. The same weights Microsoft ships — loaded into your GPU, not emulated.</div>
1188
+ </div>
1189
+ <div class="numbers-cell">
1190
+ <div class="numbers-num">1,024</div>
1191
+ <div class="numbers-label">attention heads, live</div>
1192
+ <div class="numbers-sub">32 layers × 32 heads. Every one of them drawn from its real output magnitude on every token.</div>
1193
+ </div>
1194
+ <div class="numbers-cell">
1195
+ <div class="numbers-num">292</div>
1196
+ <div class="numbers-label">dispatches / token</div>
1197
+ <div class="numbers-sub">13 WGSL pipelines, 22 GPU buffers. One full forward pass, in your browser tab.</div>
1198
+ </div>
1199
+ <div class="numbers-cell">
1200
+ <div class="numbers-num"><em>0</em></div>
1201
+ <div class="numbers-label">servers</div>
1202
+ <div class="numbers-sub">Inference begins and ends inside this tab — every weight, every kernel, every readback. Close the tab; it's gone with you.</div>
1203
+ </div>
1204
+ </div>
1205
+ </div>
1206
+ </section>
1207
+
1208
+ <section class="essay-end">
1209
+ <div class="essay-num">Now you</div>
1210
+ <h2>See it for <em>yourself.</em></h2>
1211
+ <p>Open Neuropulse. Feed it a prompt. Watch the model think. First load streams ~2&nbsp;GB into your GPU; next visit is instant (OPFS-cached to disk).</p>
1212
+ <a href="/app/" class="btn btn-primary">
1213
+ Launch Neuropulse
1214
+ <span class="arrow">→</span>
1215
+ </a>
1216
+ </section>
1217
+
1218
+ </article>
1219
+
1220
+ <!-- ───────────────────────────────────────────────
1221
+ FOOTER
1222
+ ─────────────────────────────────────────────── -->
1223
+ <footer>
1224
+ <div class="footer-inner">
1225
+ <div class="footer-row">
1226
+ <div class="footer-credit">
1227
+ <span class="logo" style="font-size:1rem"><span class="logo-neuro">neuro</span><span class="logo-pulse">pulse</span></span>
1228
+ &nbsp;by&nbsp;<a href="https://github.com/abgnydn" target="_blank" rel="noopener">Ahmet Barış Günaydın</a>
1229
+ </div>
1230
+ <div class="footer-links">
1231
+ <a href="/app/">demo</a>
1232
+ <a href="#essay">essay</a>
1233
+ <a href="https://github.com/abgnydn/neuropulse" target="_blank" rel="noopener" style="color:var(--cyan-bright)">★ star on github</a>
1234
+ <a href="https://www.linkedin.com/in/abgnydn/" target="_blank" rel="noopener">linkedin</a>
1235
+ <a href="https://barisgunaydin.com" target="_blank" rel="noopener">site</a>
1236
+ </div>
1237
+ </div>
1238
+ <div class="footer-related">
1239
+ <span class="label">More by Ahmet</span>
1240
+ <a href="https://kernelfusion.dev" target="_blank" rel="noopener">kernelfusion.dev</a>
1241
+ <a href="https://gpubench.dev" target="_blank" rel="noopener">gpubench.dev</a>
1242
+ <a href="https://zerotvm.com" target="_blank" rel="noopener">zerotvm.com</a>
1243
+ <a href="https://webgpudna.com" target="_blank" rel="noopener">webgpudna.com</a>
1244
+ <a href="https://webgpu-q.vercel.app" target="_blank" rel="noopener">webgpu-q.vercel.app</a>
1245
+ </div>
1246
+ <div class="footer-bottom">
1247
+ <div>real forward pass · built with WebGPU + Three.js · MIT licensed</div>
1248
+ <div>© 2026</div>
1249
+ </div>
1250
+ </div>
1251
+ </footer>
1252
+
1253
+ <script>
1254
+ // Live GitHub star count for the nav button. Cached in sessionStorage so
1255
+ // we don't hammer the unauthenticated GitHub API (60 req/hr limit).
1256
+ (function () {
1257
+ const el = document.getElementById('navStarCount')
1258
+ if (!el) return
1259
+ const CACHE_KEY = 'np:stars'
1260
+ const CACHE_TTL = 10 * 60 * 1000 // 10 minutes
1261
+ try {
1262
+ const cached = sessionStorage.getItem(CACHE_KEY)
1263
+ if (cached) {
1264
+ const { count, ts } = JSON.parse(cached)
1265
+ if (Date.now() - ts < CACHE_TTL && typeof count === 'number') {
1266
+ el.textContent = count
1267
+ return
1268
+ }
1269
+ }
1270
+ } catch {}
1271
+ fetch('https://api.github.com/repos/abgnydn/neuropulse')
1272
+ .then(r => r.ok ? r.json() : null)
1273
+ .then(d => {
1274
+ if (!d || typeof d.stargazers_count !== 'number') {
1275
+ // Repo is private or API failed — hide the count chip so we
1276
+ // don't ship a dead "·" placeholder.
1277
+ el.style.display = 'none'
1278
+ return
1279
+ }
1280
+ el.textContent = d.stargazers_count
1281
+ try { sessionStorage.setItem(CACHE_KEY, JSON.stringify({ count: d.stargazers_count, ts: Date.now() })) } catch {}
1282
+ })
1283
+ .catch(() => { el.style.display = 'none' })
1284
+ })()
1285
+ </script>
1286
+
1287
+ <script>
1288
+ // Scroll-reveal for essay sections
1289
+ const revealObs = new IntersectionObserver((entries) => {
1290
+ entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('visible'); revealObs.unobserve(e.target); } });
1291
+ }, { threshold: 0.15 });
1292
+ document.querySelectorAll('.essay-section, .essay-end, .numbers').forEach(el => revealObs.observe(el));
1293
+
1294
+ // Mobile guard — the demo needs ~2 GB of GPU memory and WebGPU, neither
1295
+ // of which work on phones. Intercept /app/ links on narrow screens and
1296
+ // show a friendly "desktop only" modal instead of crashing the browser.
1297
+ (function () {
1298
+ const IS_MOBILE = window.matchMedia('(max-width: 820px)').matches || /Mobi|Android|iPhone|iPad/i.test(navigator.userAgent);
1299
+ if (!IS_MOBILE) return;
1300
+
1301
+ const modal = document.createElement('div');
1302
+ modal.id = 'mobileGate';
1303
+ modal.innerHTML = `
1304
+ <div class="mg-backdrop"></div>
1305
+ <div class="mg-card">
1306
+ <div class="mg-eyebrow">Desktop only</div>
1307
+ <h3>Neuropulse runs a real 2 GB model on your GPU.</h3>
1308
+ <p>Your phone browser doesn't have enough GPU memory — it will crash mid-download. Open <strong>neuropulse.live</strong> on a desktop Chrome, Edge, or Safari TP to try it.</p>
1309
+ <div class="mg-actions">
1310
+ <button class="mg-btn mg-copy" type="button">Copy link</button>
1311
+ <button class="mg-btn mg-dismiss" type="button">Got it</button>
1312
+ </div>
1313
+ </div>
1314
+ `;
1315
+ const css = document.createElement('style');
1316
+ css.textContent = `
1317
+ #mobileGate { position:fixed; inset:0; z-index:10000; display:none; align-items:center; justify-content:center; padding:24px; }
1318
+ #mobileGate.open { display:flex; }
1319
+ #mobileGate .mg-backdrop { position:absolute; inset:0; background:rgba(2,2,16,0.85); backdrop-filter: blur(12px); }
1320
+ #mobileGate .mg-card { position:relative; max-width:420px; background:#0d0d1f; border:1px solid rgba(94,234,212,0.25); border-radius:16px; padding:28px 24px; color:#f4ecdf; box-shadow:0 20px 60px rgba(0,0,0,0.6); }
1321
+ #mobileGate .mg-eyebrow { font-size:0.7rem; letter-spacing:0.18em; text-transform:uppercase; color:#5eead4; margin-bottom:12px; }
1322
+ #mobileGate h3 { font-size:1.15rem; line-height:1.35; margin:0 0 12px; font-weight:600; }
1323
+ #mobileGate p { font-size:0.9rem; line-height:1.55; color:#b8b4a8; margin:0 0 20px; }
1324
+ #mobileGate strong { color:#00e5ff; font-weight:500; }
1325
+ #mobileGate .mg-actions { display:flex; gap:10px; }
1326
+ #mobileGate .mg-btn { flex:1; padding:12px 16px; border-radius:10px; border:1px solid rgba(94,234,212,0.3); background:transparent; color:#f4ecdf; font-size:0.85rem; font-family:inherit; cursor:pointer; -webkit-tap-highlight-color:transparent; }
1327
+ #mobileGate .mg-copy { background:#5eead4; color:#0d0d1f; border-color:#5eead4; font-weight:600; }
1328
+ #mobileGate .mg-copy.copied { background:#00e5ff; }
1329
+ `;
1330
+ document.head.appendChild(css);
1331
+ document.body.appendChild(modal);
1332
+
1333
+ function openGate(e) {
1334
+ if (e) e.preventDefault();
1335
+ modal.classList.add('open');
1336
+ }
1337
+ function closeGate() { modal.classList.remove('open'); }
1338
+
1339
+ document.querySelectorAll('a[href="/app/"], a[href^="/app"]').forEach(a => {
1340
+ a.addEventListener('click', openGate);
1341
+ });
1342
+
1343
+ modal.querySelector('.mg-dismiss').addEventListener('click', closeGate);
1344
+ modal.querySelector('.mg-backdrop').addEventListener('click', closeGate);
1345
+ const copyBtn = modal.querySelector('.mg-copy');
1346
+ copyBtn.addEventListener('click', () => {
1347
+ const url = 'https://neuropulse.live';
1348
+ navigator.clipboard?.writeText(url).then(() => {
1349
+ copyBtn.textContent = 'Copied ✓';
1350
+ copyBtn.classList.add('copied');
1351
+ setTimeout(closeGate, 900);
1352
+ }).catch(() => { copyBtn.textContent = url; });
1353
+ });
1354
+ })();
1355
+ </script>
1356
+
1357
+ <!-- Persistent page-wide Three.js backdrop — scroll drives particle morph,
1358
+ readability-fade dims during essay body, brightens at hero/numbers/CTA. -->
1359
+
1360
+ </body>
1361
  </html>
kernels-dark.svg ADDED
kernels-light.svg ADDED
layers-dark.svg ADDED
layers-light.svg ADDED
monogram-dark.svg ADDED
monogram-light.svg ADDED
og-dark.svg ADDED
og-light.svg ADDED
og.png ADDED
panels-dark.svg ADDED
panels-light.svg ADDED
pca-layout.json ADDED
The diff for this file is too large to render. See raw diff
 
preview-dark.svg ADDED
preview-light.svg ADDED
recordings/capital-01.json ADDED
The diff for this file is too large to render. See raw diff
 
recordings/capital-ablated-01.json ADDED
The diff for this file is too large to render. See raw diff
 
recordings/code-01.json ADDED
The diff for this file is too large to render. See raw diff
 
recordings/intro-01.json ADDED
The diff for this file is too large to render. See raw diff
 
reference.json ADDED
The diff for this file is too large to render. See raw diff
 
robots.txt ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # https://neuropulse.live
2
+ User-agent: *
3
+ Allow: /
4
+
5
+ # Disallow scraping by AI training crawlers (this is a creative work)
6
+ User-agent: GPTBot
7
+ Disallow: /
8
+
9
+ User-agent: Google-Extended
10
+ Disallow: /
11
+
12
+ User-agent: ClaudeBot
13
+ Disallow: /
14
+
15
+ User-agent: CCBot
16
+ Disallow: /
17
+
18
+ User-agent: Bytespider
19
+ Disallow: /
20
+
21
+ User-agent: anthropic-ai
22
+ Disallow: /
23
+
24
+ Sitemap: https://neuropulse.live/sitemap.xml
25
+ Host: https://neuropulse.live
runlocal-dark.svg ADDED
runlocal-light.svg ADDED
scene-dark.svg ADDED
scene-light.svg ADDED
sitemap.xml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3
+ <url>
4
+ <loc>https://neuropulse.live/</loc>
5
+ <changefreq>monthly</changefreq>
6
+ <priority>1.0</priority>
7
+ </url>
8
+ <url>
9
+ <loc>https://neuropulse.live/app/</loc>
10
+ <changefreq>monthly</changefreq>
11
+ <priority>0.9</priority>
12
+ </url>
13
+ </urlset>
stack-dark.svg ADDED
stack-light.svg ADDED
stats-dark.svg ADDED