PageSourceSearch

https://johnnysheng.com/assets/index-C7fHUOC7.js

js johnnysheng.com collected 2026-10-06 03:10:04 UTC 13,510 bytes, 294 lines download raw bytes

1import{u as Z,r as c,T as J,V as B,S as Q,O as ee,W as ne,n as oe,o as M,c as te,M as ie,j as n,d as u}from"./index-y4oTXVR4.js";import{g as re,a as se,b as ae}from"./experimentConfig-B3ULZx1R.js";const le=`/**
2 * GALOIS Shader - Complex Polynomial Domain Coloring
3 *
4 * Visualizes polynomial solutions through domain coloring:
5 * - White/bright regions indicate zeros (solutions)
6 * - Hue encodes the phase/angle of the polynomial output
7 * - Brightness encodes the magnitude
8 *
9 * This is Step 1 of the 7-step GALOIS implementation guide.
10 * See: src/components/experiments/v20/GALOIS_IMPLEMENTATION_GUIDE.md
11 */
12
13precision highp float;
14
15uniform vec2 u_resolution;
16uniform float u_time;
17uniform vec2 u_mouse;
18uniform vec3 u_backgroundColor;
19
20// Polynomial configuration
21uniform vec2 u_coefficients[6]; // Complex coefficients (real, imag) - up to degree 5
22uniform int u_degree;
23uniform float u_zoom;
24uniform vec2 u_pan;
25
26// Visual settings
27uniform float u_brightness;
28uniform float u_saturation;
29
30/**
31 * Complex number multiplication
32 * (a + bi)(c + di) = (ac - bd) + (ad + bc)i
33 */
34vec2 cmul(vec2 a, vec2 b) {
35    return vec2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
36}
37
38/**
39 * Complex number division
40 * (a + bi)/(c + di) = ((ac + bd) + (bc - ad)i) / (c² + d²)
41 */
42vec2 cdiv(vec2 a, vec2 b) {
43    float denom = b.x * b.x + b.y * b.y;
44    return vec2(
45        (a.x * b.x + a.y * b.y) / denom,
46        (a.y * b.x - a.x * b.y) / denom
47    );
48}
49
50/**
51 * Evaluate polynomial at complex point z
52 * P(z) = a₀ + a₁z + a₂z² + ... + aₙzⁿ
53 */
54vec2 evalPoly(vec2 z) {
55    vec2 result = u_coefficients[0];
56    vec2 zPower = z;
57
58    // Horner's method would be more efficient, but this is clearer
59    for (int i = 1; i <= 5; i++) {
60        if (i > u_degree) break;
61        result += cmul(u_coefficients[i], zPower);
62        zPower = cmul(zPower, z);
63    }
64
65    return result;
66}
67
68/**
69 * HSV to RGB color conversion
70 * Standard algorithm for mapping hue/saturation/value to RGB
71 */
72vec3 hsv2rgb(vec3 c) {
73    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
74    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
75    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
76}
77
78/**
79 * Domain coloring function
80 * Maps complex output to color:
81 * - Hue from phase angle (argument)
82 * - Brightness from magnitude (modulus)
83 */
84vec3 domainColor(vec2 w) {
85    float magnitude = length(w);
86    float phase = atan(w.y, w.x);
87
88    // Brightness: white near zeros (where magnitude → 0)
89    // Using inverse relationship so small magnitudes = bright
90    float brightness = 1.0 - 1.0 / (1.0 + magnitude * u_brightness);
91
92    // Hue from phase angle, normalized to [0, 1]
93    float hue = phase / (2.0 * 3.14159265) + 0.5;
94
95    // Create color with configurable saturation
96    return hsv2rgb(vec3(hue, u_saturation, brightness));
97}
98
99/**
100 * Grid overlay for visual reference
101 * Shows unit circle and coordinate axes
102 */
103float grid(vec2 z, float lineWidth) {
104    // Unit circle
105    float circle = abs(length(z) - 1.0);
106    float circleAlpha = 1.0 - smoothstep(0.0, lineWidth, circle);
107
108    // Axes
109    float xAxis = abs(z.y);
110    float yAxis = abs(z.x);
111    float axisAlpha = 1.0 - smoothstep(0.0, lineWidth * 0.5, min(xAxis, yAxis));
112
113    // Grid lines at integer values
114    float gridX = abs(fract(z.x + 0.5) - 0.5);
115    float gridY = abs(fract(z.y + 0.5) - 0.5);
116    float gridAlpha = 1.0 - smoothstep(0.0, lineWidth * 0.3, min(gridX, gridY));
117
118    return max(max(circleAlpha * 0.3, axisAlpha * 0.2), gridAlpha * 0.1);
119}
120
121/**
122 * Animated highlight for solution regions
123 * Creates a subtle pulsing effect around zeros
124 */
125float solutionGlow(float magnitude, float time) {
126    float pulse = sin(time * 2.0) * 0.5 + 0.5;
127    float glow = exp(-magnitude * 3.0) * (0.5 + 0.5 * pulse);
128    return glow;
129}
130
131void main() {
132    // Map pixel coordinates to complex plane
133    vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution) / min(u_resolution.x, u_resolution.y);
134    vec2 z = uv * u_zoom + u_pan;
135
136    // Evaluate polynomial at this point
137    vec2 w = evalPoly(z);
138
139    // Get base domain coloring
140    vec3 color = domainColor(w);
141
142    // Add solution glow effect
143    float glow = solutionGlow(length(w), u_time);
144    color = mix(color, vec3(1.0), glow * 0.5);
145
146    // Optional: Add grid overlay
147    float lineWidth = 0.02 * u_zoom;
148    float gridAlpha = grid(z, lineWidth);
149    vec3 gridColor = vec3(1.0, 1.0, 1.0);
150    color = mix(color, gridColor, gridAlpha * 0.3);
151
152    // Mouse interaction: highlight region near cursor
153    vec2 mouseComplex = (u_mouse - 0.5) * u_zoom * 2.0 + u_pan;
154    float mouseDist = length(z - mouseComplex);
155    float mouseHighlight = exp(-mouseDist * mouseDist * 2.0) * 0.2;
156    color = mix(color, vec3(1.0, 0.9, 0.8), mouseHighlight);
157
158    // Blend with background color for dark theme support
159    color = mix(u_backgroundColor, color, 0.95);
160
161    gl_FragColor = vec4(color, 1.0);
162}
163`,_="v20",ce=u.div`
164  position: fixed;
165  top: 0;
166  left: 0;
167  width: 100%;
168  height: 100%;
169  background: ${e=>e.$isDarkMode?"#000":"#fff"};
170`,ue=u.div`
171  position: fixed;
172  top: 0;
173  left: 0;
174  right: 0;
175  padding: 40px;
176  display: flex;
177  justify-content: space-between;
178  align-items: center;
179  z-index: 100;
180  pointer-events: none;
181
182  & > * {
183    pointer-events: auto;
184  }
185`,F=u.button`
186  background: ${e=>e.theme.colors.background.overlay};
187  border: 1px solid ${e=>e.theme.colors.border.subtle};
188  color: ${e=>e.theme.colors.text.secondary};
189  padding: 8px 16px;
190  font-family: ${e=>e.theme.fonts.primary};
191  font-size: 12px;
192  letter-spacing: 2px;
193  cursor: pointer;
194  backdrop-filter: blur(5px);
195  -webkit-backdrop-filter: blur(5px);
196  transition: ${e=>e.theme.transitions.standard};
197
198  &:hover {
199    background: ${e=>e.theme.colors.background.secondary};
200    color: ${e=>e.theme.colors.text.hover};
201    border-color: ${e=>e.theme.colors.accent.blue};
202  }
203`,de=u.div`
204  display: flex;
205  gap: 10px;
206`,me=u.div`
207  position: fixed;
208  bottom: 40px;
209  left: 40px;
210  background: ${e=>e.$isDarkMode?"rgba(0, 0, 0, 0.8)":"rgba(255, 255, 255, 0.8)"};
211  border: 1px solid ${e=>e.$isDarkMode?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.1)"};
212  border-radius: 8px;
213  padding: 20px;
214  backdrop-filter: blur(10px);
215  -webkit-backdrop-filter: blur(10px);
216  z-index: 100;
217  min-width: 280px;
218`,pe=u.div`
219  color: ${e=>e.$isDarkMode?"rgba(255, 255, 255, 0.8)":"rgba(0, 0, 0, 0.8)"};
220  font-family: 'Roboto Mono', monospace;
221  font-size: 11px;
222  letter-spacing: 2px;
223  text-transform: uppercase;
224  margin-bottom: 16px;
225`,y=u.div`
226  display: flex;
227  align-items: center;
228  justify-content: space-between;
229  margin-bottom: 12px;
230`,k=u.label`
231  color: ${e=>e.$isDarkMode?"rgba(255, 255, 255, 0.6)":"rgba(0, 0, 0, 0.6)"};
232  font-family: 'Roboto Mono', monospace;
233  font-size: 11px;
234`,$=u.input`
235  width: 120px;
236  height: 4px;
237  -webkit-appearance: none;
238  background: ${e=>e.$isDarkMode?"rgba(255, 255, 255, 0.2)":"rgba(0, 0, 0, 0.2)"};
239  border-radius: 2px;
240  cursor: pointer;
241
242  &::-webkit-slider-thumb {
243    -webkit-appearance: none;
244    width: 14px;
245    height: 14px;
246    border-radius: 50%;
247    background: #4A9EFF;
248    cursor: pointer;
249  }
250
251  &::-moz-range-thumb {
252    width: 14px;
253    height: 14px;
254    border-radius: 50%;
255    background: #4A9EFF;
256    cursor: pointer;
257    border: none;
258  }
259`,z=u.span`
260  color: ${e=>e.$isDarkMode?"rgba(255, 255, 255, 0.5)":"rgba(0, 0, 0, 0.5)"};
261  font-family: 'Roboto Mono', monospace;
262  font-size: 10px;
263  min-width: 40px;
264  text-align: right;
265`,xe=u.div`
266  color: ${e=>e.$isDarkMode?"rgba(255, 255, 255, 0.9)":"rgba(0, 0, 0, 0.9)"};
267  font-family: 'Roboto Mono', monospace;
268  font-size: 14px;
269  margin-top: 16px;
270  padding-top: 16px;
271  border-top: 1px solid ${e=>e.$isDarkMode?"rgba(255, 255, 255, 0.1)":"rgba(0, 0, 0, 0.1)"};
272  text-align: center;
273`,ge=u.div`
274  color: ${e=>e.$isDarkMode?"rgba(74, 158, 255, 0.8)":"rgba(74, 158, 255, 1)"};
275  font-family: 'Roboto Mono', monospace;
276  font-size: 10px;
277  margin-top: 8px;
278  text-align: center;
279`,fe=u.div`
280  position: absolute;
281  top: 20px;
282  left: 20px;
283  color: ${e=>e.$isDarkMode?"rgba(255, 255, 255, 0.6)":"rgba(0, 0, 0, 0.6)"};
284  font-family: 'Roboto Mono', monospace;
285  font-size: 12px;
286  letter-spacing: 0.1em;
287  text-transform: uppercase;
288  pointer-events: none;
289  z-index: 1;
290`,C=(e,d)=>{if(Math.abs(d)<.001)return e.toFixed(2);if(Math.abs(e)<.001)return`${d.toFixed(2)}i`;const o=d>=0?"+":"";return`${e.toFixed(2)}${o}${d.toFixed(2)}i`},he=(e,d)=>{const o=[];for(let i=d;i>=0;i--){const r=e[i];if(Math.abs(r.x)<.001&&Math.abs(r.y)<.001)continue;let m="";if(i===d?m=i===0?C(r.x,r.y):"":m=o.length>0?" + ":"",i===0)m+=C(r.x,r.y);else if(i===1){const s=C(r.x,r.y);m+=s==="1.00"?"z":`(${s})z`}else{const s=C(r.x,r.y);m+=s==="1.00"?`z${I(i)}`:`(${s})z${I(i)}`}m.trim()&&o.push(m)}return o.join("")+" = 0"},I=e=>["⁰","¹","²","³","⁴","⁵"][e]||e.toString(),ve=`
291void main() {
292  gl_Position = vec4(position, 1.0);
293}
294`,$e=()=>{const e=Z(),{theme:d,isDarkMode:o}=c.useContext(J),i=c.useRef(null),r=c.useRef(null),m=c.useRef(null),[s,N]=c.useState(3),[b,O]=c.useState(4),[D,be]=c.useState(.5),[j,we]=c.useState(.7),[x,V]=c.useState([{x:1,y:0},{x:0,y:0},{x:0,y:0},{x:1,y:0},{x:0,y:0},{x:0,y:0}]),E=re(_),R=se(_),A=ae(_),S=c.useCallback((t,p,v)=>{V(a=>{const g=[...a];return g[t]={...g[t],[p]:parseFloat(v)},g})},[]);return c.useEffect(()=>{if(!m.current)return;const t=m.current;t.uniforms.u_degree.value=s,x.forEach((p,v)=>{t.uniforms.u_coefficients.value[v].set(p.x,p.y)}),t.uniforms.u_zoom.value=b,t.uniforms.u_brightness.value=D,t.uniforms.u_saturation.value=j},[s,x,b,D,j]),c.useEffect(()=>{if(!i.current)return;const t=o?new B(.02,.02,.05):new B(.95,.95,.98),p=new Q,v=new ee(-1,1,1,-1,.1,10);v.position.z=5;const a=new ne({alpha:!0,antialias:!0,powerPreference:"high-performance"}),g=Math.min(window.devicePixelRatio,2);a.setSize(window.innerWidth,window.innerHeight),a.setPixelRatio(g),a.domElement.style.position="absolute",a.domElement.style.top="0",a.domElement.style.left="0",i.current.appendChild(a.domElement);const W=new oe(2,2),K=window.innerWidth*g,U=window.innerHeight*g,X=x.map(l=>new M(l.x,l.y)),f=new te({uniforms:{u_time:{value:0},u_resolution:{value:new M(K,U)},u_mouse:{value:new M(.5,.5)},u_backgroundColor:{value:t},u_coefficients:{value:X},u_degree:{value:s},u_zoom:{value:b},u_pan:{value:new M(0,0)},u_brightness:{value:D},u_saturation:{value:j}},vertexShader:ve,fragmentShader:le,transparent:!0});m.current=f;const Y=new ie(W,f);p.add(Y);const q=performance.now(),G=()=>{const l=(performance.now()-q)/1e3;f.uniforms.u_time.value=l,a.render(p,v),r.current=requestAnimationFrame(G)};G();const L=l=>{const h=l.clientX/window.innerWidth,w=1-l.clientY/window.innerHeight;f.uniforms.u_mouse.value.set(h,w)};window.addEventListener("mousemove",L);const H=l=>{if(l.touches.length>0){const h=l.touches[0],w=h.clientX/window.innerWidth,P=1-h.clientY/window.innerHeight;f.uniforms.u_mouse.value.set(w,P)}};window.addEventListener("touchmove",H,{passive:!0});const T=()=>{const{innerWidth:l,innerHeight:h}=window;a.setSize(l,h);const w=l*g,P=h*g;f.uniforms.u_resolution.value.set(w,P)};return window.addEventListener("resize",T),()=>{r.current&&cancelAnimationFrame(r.current),window.removeEventListener("mousemove",L),window.removeEventListener("touchmove",H),window.removeEventListener("resize",T),i.current&&a.domElement.parentNode===i.current&&i.current.removeChild(a.domElement),a.dispose(),W.dispose(),f.dispose(),m.current=null}},[o]),c.useEffect(()=>{const t=p=>{p.key==="Escape"&&e("/experiments"),p.key==="ArrowLeft"&&e(`/experiments/${E.id}`),p.key==="ArrowRight"&&e(`/experiments/${R.id}`)};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[e,E,R]),n.jsxs(ce,{$isDarkMode:o,children:[n.jsx("div",{ref:i}),n.jsxs(fe,{$isDarkMode:o,children:[_.toUpperCase(),": ",(A==null?void 0:A.name)||"Galois"]}),n.jsxs(ue,{children:[n.jsx(F,{theme:d,onClick:()=>e("/experiments"),children:"← BACK"}),n.jsxs(de,{children:[n.jsx(F,{theme:d,onClick:()=>e(`/experiments/${E.id}`),children:"← PREV"}),n.jsx(F,{theme:d,onClick:()=>e(`/experiments/${R.id}`),children:"NEXT →"})]})]}),n.jsxs(me,{$isDarkMode:o,children:[n.jsx(pe,{$isDarkMode:o,children:"Polynomial Controls"}),n.jsxs(y,{children:[n.jsx(k,{$isDarkMode:o,children:"Degree"}),n.jsx($,{$isDarkMode:o,type:"range",min:"2",max:"5",step:"1",value:s,onChange:t=>N(parseInt(t.target.value))}),n.jsx(z,{$isDarkMode:o,children:s})]}),n.jsxs(y,{children:[n.jsx(k,{$isDarkMode:o,children:"Zoom"}),n.jsx($,{$isDarkMode:o,type:"range",min:"1",max:"10",step:"0.1",value:b,onChange:t=>O(parseFloat(t.target.value))}),n.jsx(z,{$isDarkMode:o,children:b.toFixed(1)})]}),n.jsxs(y,{children:[n.jsx(k,{$isDarkMode:o,children:"a₀ (Re)"}),n.jsx($,{$isDarkMode:o,type:"range",min:"-2",max:"2",step:"0.1",value:x[0].x,onChange:t=>S(0,"x",t.target.value)}),n.jsx(z,{$isDarkMode:o,children:x[0].x.toFixed(1)})]}),n.jsxs(y,{children:[n.jsx(k,{$isDarkMode:o,children:"a₁ (Re)"}),n.jsx($,{$isDarkMode:o,type:"range",min:"-2",max:"2",step:"0.1",value:x[1].x,onChange:t=>S(1,"x",t.target.value)}),n.jsx(z,{$isDarkMode:o,children:x[1].x.toFixed(1)})]}),n.jsxs(y,{children:[n.jsx(k,{$isDarkMode:o,children:"a₂ (Re)"}),n.jsx($,{$isDarkMode:o,type:"range",min:"-2",max:"2",step:"0.1",value:x[2].x,onChange:t=>S(2,"x",t.target.value)}),n.jsx(z,{$isDarkMode:o,children:x[2].x.toFixed(1)})]}),n.jsx(xe,{$isDarkMode:o,children:he(x,s)}),n.jsxs(ge,{$isDarkMode:o,children:[s," solutions • White regions = zeros"]})]})]})};export{$e as default};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.