Skip to content

Commit 5943c0d

Browse files
committed
Add missing shader files for build
1 parent 4f4fbc0 commit 5943c0d

4 files changed

Lines changed: 366 additions & 5 deletions

File tree

.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,7 @@ lib/shaders/interstellar-blackhole.ts
6161
lib/shaders/riemann-zeta-lattice.ts
6262
lib/shaders/hopf-fibration-flow.ts
6363
lib/shaders/hyperbolic-dot-disk.ts
64-
lib/shaders/mobius-lens.ts
65-
lib/shaders/feedback-portal.ts
64+
# Active shaders (NOT ignored - needed for build):
65+
# - lib/shaders/gentle-scroll-warp.ts
66+
# - lib/shaders/mobius-lens.ts
67+
# - lib/shaders/feedback-portal.ts

components/Projects.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,23 @@
1-
import { Github, ExternalLink, Package } from 'lucide-react';
1+
import { Github, ExternalLink } from 'lucide-react';
2+
3+
// Custom PyPI icon (Python logo simplified)
4+
function PyPIIcon({ className }: { className?: string }) {
5+
return (
6+
<svg className={className} viewBox="0 0 24 24" fill="currentColor">
7+
<path d="M12.042 0c-1.112.014-2.173.093-3.103.24-2.757.437-3.254 1.35-3.254 3.037v2.223h6.511v.74H4.984c-1.891 0-3.548 1.137-4.067 3.3-.599 2.48-.625 4.027 0 6.618.464 1.93 1.572 3.3 3.463 3.3h2.24v-2.974c0-2.147 1.857-4.04 4.067-4.04h6.503c1.81 0 3.254-1.49 3.254-3.311V3.277c0-1.77-1.492-3.098-3.254-3.037-1.113.04-2.29.05-3.148.24zm-3.62 1.789a1.22 1.22 0 011.222 1.233c0 .682-.548 1.232-1.222 1.232a1.231 1.231 0 01-1.222-1.232c0-.682.547-1.233 1.222-1.233z"/>
8+
<path d="M18.62 6.24v2.883c0 2.242-1.903 4.13-4.066 4.13h-6.504c-1.781 0-3.254 1.525-3.254 3.311v6.207c0 1.77 1.538 2.81 3.254 3.311 2.054.6 4.023.709 6.504 0 1.65-.47 3.254-1.416 3.254-3.311v-2.483h-6.503v-.74h9.757c1.891 0 2.595-1.319 3.067-3.3.486-2.041.466-4.003 0-6.618-.335-1.883-1.176-3.3-3.067-3.3H18.62zm-3.719 13.32a1.22 1.22 0 011.222 1.233c0 .68-.549 1.232-1.222 1.232a1.231 1.231 0 01-1.222-1.232c0-.682.547-1.233 1.222-1.233z"/>
9+
</svg>
10+
);
11+
}
12+
13+
// Custom npm icon
14+
function NpmIcon({ className }: { className?: string }) {
15+
return (
16+
<svg className={className} viewBox="0 0 24 24" fill="currentColor">
17+
<path d="M0 7.334v8h6.666v1.332H12v-1.332h12v-8H0zm6.666 6.664H5.334v-4H3.999v4H1.335V8.667h5.331v5.331zm4 0v1.336H8.001V8.667h5.334v5.332h-2.669v-.001zm12.001 0h-1.33v-4h-1.336v4h-1.335v-4h-1.33v4h-2.671V8.667h8.002v5.331zM10.665 10H12v2.667h-1.335V10z"/>
18+
</svg>
19+
);
20+
}
221

322
const projects = [
423
{
@@ -96,7 +115,7 @@ function ProjectCard({ project }: { project: typeof projects[0] }) {
96115
className="p-1.5 text-gray-500 hover:text-yellow-500 hover:bg-white/[0.08] rounded-lg transition-all"
97116
title="PyPI"
98117
>
99-
<Package className="w-4 h-4" />
118+
<PyPIIcon className="w-4 h-4" />
100119
</a>
101120
)}
102121
{project.npm && (
@@ -107,7 +126,7 @@ function ProjectCard({ project }: { project: typeof projects[0] }) {
107126
className="p-1.5 text-gray-500 hover:text-red-400 hover:bg-white/[0.08] rounded-lg transition-all"
108127
title="npm"
109128
>
110-
<Package className="w-4 h-4" />
129+
<NpmIcon className="w-4 h-4" />
111130
</a>
112131
)}
113132
</div>

lib/shaders/feedback-portal.ts

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { ShaderVariant } from './index';
2+
3+
// Concept #3: True multipass feedback trails (portal / disk)
4+
// Implements a dissipative advection–diffusion update in a feedback buffer.
5+
export const feedbackPortal: ShaderVariant = {
6+
id: 'feedback-portal',
7+
name: 'Feedback Portal',
8+
description:
9+
'True feedback trails in a soft central portal; mouse injects faint dye into a slow vortex.',
10+
mathStory:
11+
'We evolve a dye field in a disk using a dissipative advection–diffusion step: the previous frame is back-traced along a gentle spiral (a stable focus) and lightly blurred to mimic diffusion, then exponentially faded. A small continuous source plus mouse injection perturbs the flow; because the buffer feeds itself (ping-pong), paths leave real temporal trails instead of redrawing “fake” streaks.',
12+
pipeline: {
13+
// Keep insertion order: feedback first (offscreen), image second (screen).
14+
sources: {
15+
feedback: `
16+
precision mediump float;
17+
18+
uniform float iTime;
19+
uniform vec2 iResolution;
20+
uniform vec2 iMouse;
21+
uniform sampler2D iChannel0;
22+
23+
vec2 rot90(vec2 p) { return vec2(-p.y, p.x); }
24+
25+
float hash12(vec2 p) {
26+
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
27+
}
28+
29+
float portalMask(vec2 p, float radius) {
30+
float r = length(p);
31+
// 1.0 inside, 0.0 outside with a soft edge
32+
return smoothstep(radius, radius - 0.02, r);
33+
}
34+
35+
void main() {
36+
vec2 frag = gl_FragCoord.xy;
37+
vec2 uv = frag / iResolution.xy;
38+
39+
float minRes = min(iResolution.x, iResolution.y);
40+
vec2 p = (frag - 0.5 * iResolution.xy) / minRes;
41+
42+
const float portalR = 0.58;
43+
float portal = portalMask(p, portalR);
44+
float r = length(p);
45+
46+
// --- Velocity field (in "p" units per frame) ---
47+
// A stable spiral (damped rotation) + small time wobble.
48+
vec2 v = vec2(0.0);
49+
float swirl = (0.010 * exp(-2.0 * r * r) + 0.002);
50+
v += rot90(p) * swirl;
51+
v += -p * 0.006;
52+
v += 0.0016 * vec2(
53+
sin(0.35 * iTime + 6.0 * p.y),
54+
cos(0.30 * iTime + 6.0 * p.x)
55+
);
56+
57+
// Mouse perturbation (very subtle): local swirl + drift.
58+
vec2 mP = (iMouse - 0.5 * iResolution.xy) / minRes;
59+
vec2 dm = p - mP;
60+
float md2 = dot(dm, dm);
61+
float mInflu = exp(-md2 / 0.010);
62+
v += rot90(dm) * (0.0030 * mInflu);
63+
v += dm * (0.0015 * mInflu);
64+
65+
v *= portal;
66+
67+
// Convert displacement in p-space to UV-space.
68+
vec2 toUv = vec2(minRes / iResolution.x, minRes / iResolution.y);
69+
vec2 uvBack = uv - v * toUv;
70+
71+
// --- Advection + diffusion (5 taps) ---
72+
vec2 px = 1.0 / iResolution.xy;
73+
vec4 c0 = texture2D(iChannel0, uvBack);
74+
vec4 c1 = texture2D(iChannel0, uvBack + vec2(px.x, 0.0));
75+
vec4 c2 = texture2D(iChannel0, uvBack - vec2(px.x, 0.0));
76+
vec4 c3 = texture2D(iChannel0, uvBack + vec2(0.0, px.y));
77+
vec4 c4 = texture2D(iChannel0, uvBack - vec2(0.0, px.y));
78+
vec4 state = c0 * 0.60 + (c1 + c2 + c3 + c4) * 0.10;
79+
80+
// Exponential fade (stronger outside the portal).
81+
float fade = mix(0.88, 0.988, portal);
82+
state *= fade;
83+
state *= portal;
84+
85+
// --- Continuous faint source to keep motion visible even without mouse ---
86+
vec2 emitter = 0.22 * vec2(cos(0.12 * iTime), sin(0.10 * iTime));
87+
float eInk = exp(-dot(p - emitter, p - emitter) / (0.030 * 0.030));
88+
vec3 eCol = vec3(0.15, 0.3, 0.4);
89+
state.rgb += eCol * (0.04 * eInk) * portal;
90+
state.a += (0.05 * eInk) * portal;
91+
92+
// Mouse dye injection (visible and persistent).
93+
float mInk = exp(-md2 / (0.018 * 0.018));
94+
vec3 mCol = vec3(0.2, 0.35, 0.5);
95+
state.rgb += mCol * (0.1 * mInk) * portal;
96+
state.a += (0.12 * mInk) * portal;
97+
98+
// Visible rim to sell the "portal" boundary.
99+
float rim = exp(-pow((r - portalR) / 0.018, 2.0));
100+
state.rgb += vec3(0.1, 0.15, 0.2) * (0.02 * rim);
101+
state.a += 0.03 * rim;
102+
103+
// Tiny dither prevents banding in slow fades.
104+
float d = (hash12(frag + fract(iTime)) - 0.5) * 0.002;
105+
state.rgb += d;
106+
107+
gl_FragColor = clamp(state, 0.0, 4.0);
108+
}
109+
`,
110+
111+
image: `
112+
precision mediump float;
113+
114+
uniform float iTime;
115+
uniform vec2 iResolution;
116+
uniform vec2 iMouse;
117+
uniform sampler2D iChannel0;
118+
119+
float hash12(vec2 p) {
120+
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
121+
}
122+
123+
float portalMask(vec2 p, float radius) {
124+
float r = length(p);
125+
return smoothstep(radius, radius - 0.02, r);
126+
}
127+
128+
vec3 toneMap(vec3 x) {
129+
// Soft filmic-ish curve using an exponential rolloff.
130+
x = 1.0 - exp(-2.2 * x);
131+
return pow(clamp(x, 0.0, 1.0), vec3(0.4545));
132+
}
133+
134+
void main() {
135+
vec2 frag = gl_FragCoord.xy;
136+
vec2 uv = frag / iResolution.xy;
137+
138+
float minRes = min(iResolution.x, iResolution.y);
139+
vec2 p = (frag - 0.5 * iResolution.xy) / minRes;
140+
float r = length(p);
141+
142+
const float portalR = 0.58;
143+
float portal = portalMask(p, portalR);
144+
float rim = exp(-pow((r - portalR) / 0.022, 2.0));
145+
146+
vec4 state = texture2D(iChannel0, uv);
147+
vec3 dye = state.rgb;
148+
float dens = state.a;
149+
150+
// Subtle chroma drift based on density and time.
151+
float h = 0.07 * iTime + 2.0 * dens;
152+
vec3 pal = 0.55 + 0.45 * cos(6.28318 * (h + vec3(0.0, 0.33, 0.67)));
153+
pal = mix(vec3(dot(pal, vec3(0.299, 0.587, 0.114))), pal, 0.65);
154+
155+
vec3 glow = dye * pal * 2.5;
156+
glow += (dens * dens) * vec3(0.2, 0.3, 0.4);
157+
glow = toneMap(glow);
158+
159+
// Background (dark but not too dark).
160+
vec3 bg = vec3(0.02, 0.03, 0.045);
161+
bg += 0.025 * vec3(0.0, 0.04, 0.08) * smoothstep(0.8, 0.0, r);
162+
163+
// Compose: keep the effect mostly inside the portal.
164+
vec3 col = bg;
165+
col = mix(col, bg + glow, portal);
166+
167+
// Rim highlight and a very soft vignette.
168+
col += rim * vec3(0.12, 0.18, 0.25) * 0.5;
169+
col *= 1.0 - 0.2 * r * r;
170+
171+
// Mouse proximity brightens the rim.
172+
vec2 mP = (iMouse - 0.5 * iResolution.xy) / minRes;
173+
float md = length(p - mP);
174+
float mRim = exp(-md * 8.0) * rim;
175+
col += mRim * vec3(0.15, 0.2, 0.25) * 0.3;
176+
177+
// Dither.
178+
col += (hash12(frag + iTime) - 0.5) * 0.003;
179+
180+
gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
181+
}
182+
`,
183+
},
184+
channels: {
185+
// feedback reads its own previous frame (true feedback loop)
186+
feedback: { iChannel0: 'feedback' },
187+
// final image reads from feedback buffer
188+
image: { iChannel0: 'feedback' },
189+
},
190+
},
191+
};

lib/shaders/mobius-lens.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { ShaderVariant } from './index';
2+
3+
// Concept #1: Local Möbius lens warping a faint hyperbolic coordinate net
4+
export const mobiusLens: ShaderVariant = {
5+
id: 'mobius-lens',
6+
name: 'Möbius Lens',
7+
description: 'A subtle conformal lens: local Möbius warp over a faint hyperbolic grid',
8+
mathStory:
9+
'Treat the screen as a calm patch of the Poincaré disk, where hyperbolic isometries are Möbius maps z ↦ (z−a)/(1−āz). The mouse sets a small parameter a, but we blend that isometry locally to create a gentle, conformal “lens”. The rings and rays are level sets of hyperbolic distance and angle, so interaction feels like a coordinate net bending in curved space rather than a flashy effect.',
10+
source: `
11+
precision highp float;
12+
13+
uniform float iTime;
14+
uniform vec2 iResolution;
15+
uniform vec2 iMouse;
16+
uniform float iScroll;
17+
18+
const float PI = 3.14159265358979323846;
19+
20+
float hash(vec2 p) {
21+
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
22+
}
23+
24+
vec2 cmul(vec2 a, vec2 b) {
25+
return vec2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
26+
}
27+
28+
vec2 cconj(vec2 z) {
29+
return vec2(z.x, -z.y);
30+
}
31+
32+
vec2 cdiv(vec2 a, vec2 b) {
33+
float d = max(dot(b, b), 1e-6);
34+
return vec2(a.x * b.x + a.y * b.y, a.y * b.x - a.x * b.y) / d;
35+
}
36+
37+
// Möbius transform: (z - a) / (1 - conj(a) * z)
38+
vec2 mobius(vec2 z, vec2 a) {
39+
vec2 num = z - a;
40+
vec2 denom = vec2(1.0, 0.0) - cmul(cconj(a), z);
41+
return cdiv(num, denom);
42+
}
43+
44+
float atanh_approx(float x) {
45+
x = clamp(x, -0.999, 0.999);
46+
return 0.5 * log((1.0 + x) / (1.0 - x));
47+
}
48+
49+
// Hyperbolic distance to origin in the Poincaré disk model
50+
float hypDist0(vec2 z) {
51+
float r = clamp(length(z), 0.0, 0.999);
52+
return 2.0 * atanh_approx(r);
53+
}
54+
55+
float line01(float x, float w) {
56+
return 1.0 - smoothstep(0.0, w, x);
57+
}
58+
59+
float stripe(float v, float freq, float w) {
60+
return line01(abs(sin(v * freq)), w);
61+
}
62+
63+
vec2 clampToDisk(vec2 z, float rMax) {
64+
float r = length(z);
65+
float k = min(1.0, rMax / max(r, 1e-6));
66+
return z * k;
67+
}
68+
69+
void main() {
70+
vec2 frag = gl_FragCoord.xy;
71+
vec2 res = iResolution.xy;
72+
float minRes = min(res.x, res.y);
73+
74+
vec2 uv = (frag - 0.5 * res) / minRes; // roughly [-1,1]
75+
vec2 st = frag / res; // [0,1]
76+
77+
// Deep, calm base color with a gentle vertical gradient
78+
vec3 col = vec3(0.008, 0.012, 0.020);
79+
col += vec3(0.006, 0.008, 0.012) * (0.35 + 0.65 * st.y);
80+
81+
// Dither to reduce banding
82+
col += (hash(frag) - 0.5) * 0.008;
83+
84+
float t = iTime;
85+
float scroll = iScroll;
86+
87+
// Work in a soft Poincaré-disk-like patch (clamped for stability)
88+
vec2 p = uv * 1.10;
89+
p = clampToDisk(p, 0.999);
90+
91+
// Mouse position mapped to the same coordinate space
92+
vec2 mouse = iMouse;
93+
if (mouse.x <= 0.0 && mouse.y <= 0.0) {
94+
mouse = 0.5 * res;
95+
}
96+
vec2 m = (mouse - 0.5 * res) / minRes;
97+
m *= 0.70; // keep the lens safely inside
98+
m = clampToDisk(m, 0.65);
99+
100+
// A tiny conformal "lens" parameter (mouse + slow drift + scroll bias)
101+
vec2 drift = 0.06 * vec2(sin(t * 0.020), cos(t * 0.017));
102+
vec2 a = m * 0.35 + drift + vec2(0.0, (scroll - 0.5) * 0.04);
103+
a = clampToDisk(a, 0.65);
104+
105+
// Local Möbius lens: blend identity with a disk isometry
106+
vec2 z = p;
107+
vec2 zIso = mobius(z, a);
108+
109+
float d = length(z - a);
110+
float lens = 0.10 * exp(-6.0 * d * d); // local influence
111+
lens *= 1.0 - smoothstep(0.75, 1.0, length(z)); // reduce near boundary
112+
z = mix(z, zIso, lens);
113+
114+
float r = length(z);
115+
float hd = hypDist0(z);
116+
float ang = atan(z.y, z.x);
117+
118+
// Faint hyperbolic coordinate net (distance shells + angular rays)
119+
float phase = t * 0.035 + scroll * 0.9;
120+
121+
float rings = stripe(hd - phase * 0.55, 2.6, 0.08) * 0.7;
122+
float rays = stripe(ang + phase * 0.15, 7.0, 0.05) * 0.55;
123+
float weave = stripe(hd * 0.55 + ang - phase * 0.25, 3.0, 0.09) * 0.4;
124+
125+
float grid = rings + rays + weave;
126+
127+
// Fade so it stays subtle and doesn't pop at the edges
128+
float fade = smoothstep(0.10, 1.20, hd) * (1.0 - smoothstep(0.90, 1.03, r));
129+
grid *= fade;
130+
131+
// Very subtle mouse emphasis (mostly expressed through warping)
132+
float dm = length(p - m);
133+
float mouseGlow = 0.06 * exp(-12.0 * dm * dm);
134+
135+
vec3 gridColor = vec3(0.2, 0.8, 0.85);
136+
vec3 glowColor = vec3(0.15, 0.55, 0.6);
137+
138+
col += grid * gridColor * 0.6;
139+
col += mouseGlow * glowColor * 2.0;
140+
141+
// Gentle vignette
142+
float v = 1.0 - 0.25 * dot(uv, uv);
143+
col *= clamp(v, 0.0, 1.0);
144+
145+
col = pow(col, vec3(0.95));
146+
gl_FragColor = vec4(col, 1.0);
147+
}
148+
`,
149+
};

0 commit comments

Comments
 (0)