PageSourceSearch

https://shoemoney.com/webgpu-world.js

js shoemoney.com collected 2026-10-01 10:44:46 UTC 11,653 bytes, 232 lines download raw bytes

1/* Native WebGPU sculpture. A bounded Canvas 2D rendition is the deliberate fallback. */
2(() => {
3  'use strict';
4  const shader = `
5    struct Scene { viewport: vec4f, pointer: vec4f };
6    @group(0) @binding(0) var<uniform> scene: Scene;
7    struct VertexOut {
8      @builtin(position) position: vec4f,
9      @location(0) uv: vec2f,
10      @location(1) tint: vec4f,
11    };
12    @vertex fn vertexMain(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> VertexOut {
13      let corners = array<vec2f, 6>(vec2f(-1.,-1.),vec2f(1.,-1.),vec2f(-1.,1.),vec2f(-1.,1.),vec2f(1.,-1.),vec2f(1.,1.));
14      let tau = 6.28318530718;
15      let time = scene.viewport.z;
16      let u = f32(ii % 240u) / 240. * tau;
17      let v = f32(ii / 240u) / 88. * tau;
18      let twist = v + u * 3. + time * 0.11;
19      let major = 1.8 + .23 * sin(u * 3. + time * .24);
20      let minor = .50 + .12 * sin(v * 2. + u * 4. - time * .26);
21      var p = vec3f((major + minor*cos(twist))*cos(u), (major + minor*cos(twist))*sin(u), minor*sin(twist));
22      let ay = time * .085 + scene.pointer.x * .26;
23      p = vec3f(p.x*cos(ay)+p.z*sin(ay),p.y,-p.x*sin(ay)+p.z*cos(ay));
24      let ax = .82 + sin(time*.09)*.2 + scene.pointer.y*.14;
25      p = vec3f(p.x,p.y*cos(ax)-p.z*sin(ax),p.y*sin(ax)+p.z*cos(ax));
26      let az = -.32 + scene.pointer.z * .20;
27      p = vec3f(p.x*cos(az)-p.y*sin(az),p.x*sin(az)+p.y*cos(az),p.z);
28      let perspective = 4.6 / (5.4 + p.z);
29      let aspect = scene.viewport.x / scene.viewport.y;
30      let mobile = select(1., .8, aspect < .8);
31      var position = vec2f(p.x/aspect,p.y) * .49 * perspective * mobile;
32      position.x += select(.46, .08, aspect < .8);
33      position.y += .10 - scene.pointer.z * .08;
34      let pointerPos = vec2f(scene.pointer.x/aspect,scene.pointer.y);
35      let distance = length(position-pointerPos);
36      let repel = exp(-distance*distance*5.) * .045;
37      position += normalize(position-pointerPos+vec2f(.0001)) * repel;
38      let wave = sin(distance*16.-scene.pointer.w*8.) * exp(-scene.pointer.w*1.8) * .13;
39      position += normalize(position-pointerPos+vec2f(.0001))*wave;
40      let depth = clamp((p.z+2.6)/5.2,0.,1.);
41      let size = (1.25 + depth*1.6) * scene.viewport.w;
42      let corner = corners[vi];
43      let offset = corner * size / scene.viewport.xy * 2.;
44      var output: VertexOut;
45      output.position = vec4f(position+offset,0.,1.);
46      output.uv = corner;
47      let base = mix(vec3f(.45,.91,.81),vec3f(.84,1.,.25),.5+.5*sin(u*2.+time*.1));
48      let edgeFade = smoothstep(-1.,-.25,position.x);
49      output.tint = vec4f(base,(.14+depth*.52)*edgeFade);
50      return output;
51    }
52    @fragment fn fragmentMain(input: VertexOut) -> @location(0) vec4f {
53      let radius = length(input.uv);
54      let alpha = (1.-smoothstep(.15,1.,radius))*input.tint.a;
55      return vec4f(input.tint.rgb*alpha,alpha);
56    }
57  `;
58
59  class ShoeMoneyWorld {
60    constructor(canvas, options = {}) {
61      this.canvas = canvas;
62      this.motion = options.motion !== false;
63      this.canvas.dataset.motion = this.motion ? 'on' : 'off';
64      this.destroyed = false;
65      this.renderedFrames = 0;
66      this.pointer = { x: .35, y: .1, tx: .35, ty: .1 };
67      this.time = 16;
68      this.burstAge = 20;
69      this.lastFrame = 0;
70      this.raf = 0;
71      this.scroll = 0;
72      this.mode = 'initializing';
73      this.uniformData = new Float32Array(8);
74      this.listeners = new AbortController();
75      const signal = this.listeners.signal;
76      window.addEventListener('resize', () => this.resize(), { signal, passive: true });
77      window.addEventListener('pointermove', event => {
78        this.pointer.tx = (event.clientX / innerWidth - .5) * 2;
79        this.pointer.ty = (.5 - event.clientY / innerHeight) * 2;
80        if (!this.motion) return;
81        this.requestFrame();
82      }, { signal, passive: true });
83      window.addEventListener('pointerdown', () => { if (this.motion) { this.burstAge = 0; this.requestFrame(); } }, { signal, passive: true });
84      window.addEventListener('scroll', () => { this.scroll = Math.min(scrollY / innerHeight, 5); }, { signal, passive: true });
85      document.addEventListener('visibilitychange', () => {
86        if (document.hidden) { cancelAnimationFrame(this.raf); this.raf = 0; this.lastFrame = 0; }
87        else this.requestFrame();
88      }, { signal });
89      this.initialize();
90    }
91
92    async initialize() {
93      try {
94        if (!navigator.gpu || !window.isSecureContext) throw new Error('WebGPU unavailable');
95        const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'low-power' });
96        if (!adapter || this.destroyed) throw new Error('No GPU adapter');
97        const device = await adapter.requestDevice();
98        if (this.destroyed) { device.destroy(); return; }
99        this.device = device;
100        device.addEventListener('uncapturederror', event => { event.preventDefault(); this.useFallback(); });
101        device.lost.then(() => { if (!this.destroyed) this.useFallback(); });
102        const context = this.canvas.getContext('webgpu');
103        if (!context) throw new Error('No WebGPU context');
104        const format = navigator.gpu.getPreferredCanvasFormat();
105        const module = device.createShaderModule({ label: 'ShoeMoney orbital field', code: shader });
106        const info = await module.getCompilationInfo();
107        if (info.messages.some(message => message.type === 'error')) throw new Error('Shader compilation failed');
108        this.pipeline = await device.createRenderPipelineAsync({
109          label: 'ShoeMoney instanced particle sculpture',
110          layout: 'auto',
111          vertex: { module, entryPoint: 'vertexMain' },
112          fragment: { module, entryPoint: 'fragmentMain', targets: [{ format, blend: {
113            color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
114            alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
115          } }] },
116          primitive: { topology: 'triangle-list' },
117        });
118        if (this.destroyed) return;
119        this.uniformBuffer = device.createBuffer({ size: 32, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
120        this.bindGroup = device.createBindGroup({ layout: this.pipeline.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer: this.uniformBuffer } }] });
121        this.context = context;
122        this.format = format;
123        this.mode = 'webgpu';
124        this.canvas.dataset.renderer = this.mode;
125        this.resize();
126      } catch { this.useFallback(); }
127    }
128
129    useFallback() {
130      if (this.destroyed || this.mode === 'canvas2d') return;
131      cancelAnimationFrame(this.raf);
132      this.raf = 0;
133      this.mode = 'canvas2d';
134      // A canvas cannot change context types after creation. Replace only the decorative canvas.
135      const replacement = this.canvas.cloneNode(false);
136      this.canvas.replaceWith(replacement);
137      this.canvas = replacement;
138      this.context = replacement.getContext('2d', { alpha: true });
139      this.canvas.dataset.renderer = this.context ? 'canvas2d' : 'css';
140      if (this.uniformBuffer) { this.uniformBuffer.destroy(); this.uniformBuffer = null; }
141      if (this.device) { const oldDevice = this.device; this.device = null; oldDevice.destroy(); }
142      this.resize();
143    }
144
145    resize() {
146      if (this.destroyed) return;
147      this.dpr = Math.min(devicePixelRatio || 1, 1.6);
148      this.canvas.width = Math.max(1, Math.round(innerWidth * this.dpr));
149      this.canvas.height = Math.max(1, Math.round(innerHeight * this.dpr));
150      if (this.mode === 'webgpu') this.context.configure({ device: this.device, format: this.format, alphaMode: 'premultiplied' });
151      this.requestFrame();
152    }
153
154    setMotion(enabled) {
155      this.motion = enabled;
156      this.canvas.dataset.motion = enabled ? 'on' : 'off';
157      cancelAnimationFrame(this.raf);
158      this.raf = 0;
159      this.lastFrame = 0;
160      this.requestFrame();
161    }
162
163    requestFrame() {
164      if (!this.raf && !this.destroyed && !document.hidden && this.mode !== 'initializing') this.raf = requestAnimationFrame(timestamp => this.frame(timestamp));
165    }
166
167    frame(timestamp) {
168      this.raf = 0;
169      if (this.destroyed || document.hidden) return;
170      const delta = this.lastFrame ? Math.min((timestamp - this.lastFrame) / 1000, .05) : 0;
171      this.lastFrame = timestamp;
172      if (this.motion) {
173        this.time += delta;
174        this.burstAge += delta;
175        this.pointer.x += (this.pointer.tx - this.pointer.x) * .045;
176        this.pointer.y += (this.pointer.ty - this.pointer.y) * .045;
177      }
178      try {
179        if (this.mode === 'webgpu') this.drawGPU();
180        else if (this.mode === 'canvas2d' && this.context) this.drawCanvas();
181      } catch { this.useFallback(); }
182      this.renderedFrames++;
183      this.canvas.dataset.frames = String(this.renderedFrames);
184      if (this.motion) this.requestFrame();
185    }
186
187    drawGPU() {
188      this.uniformData.set([this.canvas.width, this.canvas.height, this.time, this.dpr, this.pointer.x, this.pointer.y, this.scroll, this.burstAge]);
189      this.device.queue.writeBuffer(this.uniformBuffer, 0, this.uniformData);
190      const encoder = this.device.createCommandEncoder();
191      const pass = encoder.beginRenderPass({ colorAttachments: [{ view: this.context.getCurrentTexture().createView(), clearValue: { r: 0, g: 0, b: 0, a: 0 }, loadOp: 'clear', storeOp: 'store' }] });
192      pass.setPipeline(this.pipeline);
193      pass.setBindGroup(0, this.bindGroup);
194      pass.draw(6, 240 * 88);
195      pass.end();
196      this.device.queue.submit([encoder.finish()]);
197    }
198
199    drawCanvas() {
200      const ctx = this.context, width = this.canvas.width, height = this.canvas.height;
201      ctx.clearRect(0, 0, width, height);
202      const aspect = width / height, mobile = aspect < .8, t = this.time;
203      const ay = t * .085 + this.pointer.x * .26, ax = .82 + Math.sin(t * .09) * .2 + this.pointer.y * .14, az = -.32 + this.scroll * .20;
204      const cx = width * (mobile ? .54 : .73), cy = height * (.45 + this.scroll * .04), factor = height * .245 * (mobile ? .8 : 1);
205      const sin = Math.sin, cos = Math.cos;
206      for (let j = 0; j < 30; j++) {
207        for (let i = 0; i < 110; i++) {
208          const u = i / 110 * Math.PI * 2, v = j / 30 * Math.PI * 2, twist = v + u * 3 + t * .11;
209          const major = 1.8 + .23 * sin(u * 3 + t * .24), minor = .50 + .12 * sin(v * 2 + u * 4 - t * .26);
210          let x = (major + minor * cos(twist)) * cos(u), y = (major + minor * cos(twist)) * sin(u), z = minor * sin(twist);
211          [x, z] = [x * cos(ay) + z * sin(ay), -x * sin(ay) + z * cos(ay)];
212          [y, z] = [y * cos(ax) - z * sin(ax), y * sin(ax) + z * cos(ax)];
213          [x, y] = [x * cos(az) - y * sin(az), x * sin(az) + y * cos(az)];
214          const perspective = 4.6 / (5.4 + z), depth = Math.max(0, Math.min(1, (z + 2.6) / 5.2));
215          const px = cx + x * perspective * factor, py = cy - y * perspective * factor;
216          const mix = .5 + .5 * sin(u * 2 + t * .1), alpha = (.2 + depth * .48) * Math.min(1, Math.max(0, px / (width * .5)));
217          ctx.fillStyle = `rgba(${Math.round(115 + mix * 99)},${Math.round(232 + mix * 23)},${Math.round(207 - mix * 143)},${alpha})`;
218          ctx.beginPath(); ctx.arc(px, py, (.8 + depth * .8) * this.dpr, 0, Math.PI * 2); ctx.fill();
219        }
220      }
221    }
222
223    destroy() {
224      this.destroyed = true;
225      cancelAnimationFrame(this.raf);
226      this.listeners.abort();
227      if (this.uniformBuffer) this.uniformBuffer.destroy();
228      if (this.device) this.device.destroy();
229    }
230  }
231  window.ShoeMoneyWorld = ShoeMoneyWorld;
232})();

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.