xuym-codex-collab 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,323 @@
1
+ import {
2
+ BufferGeometry, Color, Float32BufferAttribute, NormalBlending,
3
+ PerspectiveCamera, Points, Scene, ShaderMaterial, Vector2, WebGLRenderer,
4
+ } from '/vendor/three.module.js';
5
+
6
+ // Stars and pointer dust each render in a single draw call.
7
+ const canvas = document.getElementById('ambient-canvas');
8
+ const follower = document.getElementById('cursor-follower');
9
+ const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)');
10
+ const finePointer = matchMedia('(hover: hover) and (pointer: fine)');
11
+ const palettes = {
12
+ day: ['#728f9e', '#9a8bb4', '#a2b7c5', .88],
13
+ night: ['#8fbdbb', '#b5b0e2', '#c5d6e0', .88],
14
+ };
15
+ const pointer = { x: 0, y: 0, ringX: 0, ringY: 0, fieldX: 0, fieldY: 0, alpha: 0, active: false, hover: false, down: false };
16
+ const trailCount = 44;
17
+ const trailPositions = new Float32Array(trailCount * 3);
18
+ const trailBirths = new Float32Array(trailCount).fill(-100);
19
+ const trailSeeds = new Float32Array(trailCount);
20
+ let width = innerWidth, height = innerHeight, frame = 0, lastFrame = 0, elapsed = 0;
21
+ let renderer, scene, camera, stars, dust, starGeometry, dustGeometry;
22
+ let initialized = false, lost = false, trailIndex = 0, lastTrail = 0, lastTrailX = 0, lastTrailY = 0;
23
+ let density = 1;
24
+ const isStatic = () => reducedMotion.matches || !finePointer.matches;
25
+ const uniforms = {
26
+ uTime: { value: 0 },
27
+ uPixelRatio: { value: 1 },
28
+ uSpan: { value: new Vector2(1, 1) },
29
+ uPointer: { value: new Vector2() },
30
+ uParallax: { value: new Vector2() },
31
+ uPointerAlpha: { value: 0 },
32
+ uColorA: { value: new Color() },
33
+ uColorB: { value: new Color() },
34
+ uColorC: { value: new Color() },
35
+ uOpacity: { value: 1 },
36
+ };
37
+
38
+ const starVertex = `
39
+ uniform float uTime;
40
+ uniform float uPixelRatio;
41
+ uniform vec2 uSpan;
42
+ uniform vec2 uPointer;
43
+ uniform vec2 uParallax;
44
+ uniform float uPointerAlpha;
45
+ attribute float aSize;
46
+ attribute float aPhase;
47
+ attribute float aTone;
48
+ varying float vTone;
49
+ varying float vAlpha;
50
+ varying float vSparkle;
51
+ void main() {
52
+ float depth = (8.0 - position.z) / 8.0;
53
+ vec3 p = vec3(position.xy * uSpan * depth, position.z);
54
+ p.x += sin(uTime * .085 + aPhase) * .065;
55
+ p.y += cos(uTime * .065 + aPhase * 1.3) * .045;
56
+ // Farther layers respond less, creating subtle depth without camera sway.
57
+ p.xy += uParallax * (.025 + (position.z + 4.0) * .012);
58
+ vec2 delta = uPointer * depth - p.xy;
59
+ float proximity = exp(-dot(delta, delta) / (1.1 * depth * depth));
60
+ p.xy += delta * proximity * .09 * uPointerAlpha;
61
+ vec4 mv = modelViewMatrix * vec4(p, 1.0);
62
+ vec4 projected = projectionMatrix * mv;
63
+ gl_Position = projected;
64
+ gl_PointSize = clamp(aSize * uPixelRatio * (8.0 / -mv.z), 2.0, 28.0 * uPixelRatio);
65
+ vec2 screen = projected.xy / projected.w;
66
+ float quietCenter = 1.0 - exp(-pow(screen.x * 2.2, 2.0) - pow(screen.y * 1.6, 2.0)) * .45;
67
+ vAlpha = (.65 + sin(uTime * .42 + aPhase) * .16) * quietCenter;
68
+ vTone = aTone;
69
+ vSparkle = step(.83, aTone);
70
+ }
71
+ `;
72
+ const starFragment = `
73
+ uniform vec3 uColorA;
74
+ uniform vec3 uColorB;
75
+ uniform vec3 uColorC;
76
+ uniform float uOpacity;
77
+ varying float vTone;
78
+ varying float vAlpha;
79
+ varying float vSparkle;
80
+ void main() {
81
+ vec2 p = gl_PointCoord * 2.0 - 1.0;
82
+ float radius = dot(p, p);
83
+ if (radius > 1.0) discard;
84
+ float core = exp(-radius * 80.0);
85
+ float halo = exp(-radius * 6.0) * .17;
86
+ float cross = (exp(-abs(p.x) * 65.0) + exp(-abs(p.y) * 65.0)) * exp(-radius * 7.0) * .18 * vSparkle;
87
+ float alpha = (core + halo + cross) * vAlpha * uOpacity;
88
+ vec3 color = mix(uColorA, uColorB, smoothstep(.1, .8, vTone));
89
+ color = mix(color, uColorC, vSparkle * .55);
90
+ gl_FragColor = vec4(color, alpha);
91
+ #include <colorspace_fragment>
92
+ }
93
+ `;
94
+ const dustVertex = `
95
+ uniform float uTime;
96
+ uniform float uPixelRatio;
97
+ attribute float aBirth;
98
+ attribute float aSeed;
99
+ varying float vAlpha;
100
+ void main() {
101
+ float age = uTime - aBirth;
102
+ float life = clamp(1.0 - age / 1.25, 0.0, 1.0);
103
+ vec3 p = position;
104
+ p.x += sin(aSeed * 25.0) * age * .042;
105
+ p.y += age * (.035 + aSeed * .035);
106
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
107
+ gl_PointSize = (6.0 + aSeed * 7.0) * uPixelRatio * (.7 + life * .3);
108
+ vAlpha = life * life * .65;
109
+ }
110
+ `;
111
+ const dustFragment = `
112
+ uniform vec3 uColorB;
113
+ uniform vec3 uColorC;
114
+ uniform float uOpacity;
115
+ varying float vAlpha;
116
+ void main() {
117
+ vec2 p = gl_PointCoord * 2.0 - 1.0;
118
+ float radius = dot(p, p);
119
+ if (radius > 1.0) discard;
120
+ float softPoint = exp(-radius * 35.0) + exp(-radius * 5.0) * .12;
121
+ gl_FragColor = vec4(mix(uColorB, uColorC, .4), softPoint * vAlpha * uOpacity);
122
+ #include <colorspace_fragment>
123
+ }
124
+ `;
125
+
126
+ function stop() {
127
+ cancelAnimationFrame(frame);
128
+ frame = 0;
129
+ lastFrame = 0;
130
+ }
131
+ function schedule() {
132
+ if (renderer && !frame && !document.hidden && !isStatic() && !lost) frame = requestAnimationFrame(tick);
133
+ }
134
+ function createScene() {
135
+ if (initialized) return;
136
+ initialized = true;
137
+ try {
138
+ renderer = new WebGLRenderer({ canvas, alpha: true, antialias: false, powerPreference: 'low-power' });
139
+ renderer.setClearColor(0x000000, 0);
140
+ scene = new Scene();
141
+ camera = new PerspectiveCamera(42, width / height, .1, 30);
142
+ camera.position.z = 8;
143
+ starGeometry = new BufferGeometry();
144
+ const positions = [], sizes = [], phases = [], tones = [];
145
+ let seed = 173;
146
+ const random = () => { seed = (seed * 16807) % 2147483647; return (seed - 1) / 2147483646; };
147
+ for (let i = 0; i < 148; i++) {
148
+ positions.push((random() - .5) * 1.18, (random() - .5) * 1.18, random() * 6.5 - 4);
149
+ sizes.push(6 + Math.pow(random(), 2) * 13);
150
+ phases.push(random() * Math.PI * 2);
151
+ tones.push(random());
152
+ }
153
+ starGeometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
154
+ starGeometry.setAttribute('aSize', new Float32BufferAttribute(sizes, 1));
155
+ starGeometry.setAttribute('aPhase', new Float32BufferAttribute(phases, 1));
156
+ starGeometry.setAttribute('aTone', new Float32BufferAttribute(tones, 1));
157
+ const settings = { uniforms, transparent: true, depthTest: false, depthWrite: false, blending: NormalBlending };
158
+ stars = new Points(starGeometry, new ShaderMaterial({ ...settings, vertexShader: starVertex, fragmentShader: starFragment }));
159
+ stars.frustumCulled = false;
160
+ dustGeometry = new BufferGeometry();
161
+ dustGeometry.setAttribute('position', new Float32BufferAttribute(trailPositions, 3));
162
+ dustGeometry.setAttribute('aBirth', new Float32BufferAttribute(trailBirths, 1));
163
+ for (let i = 0; i < trailCount; i++) trailSeeds[i] = random();
164
+ dustGeometry.setAttribute('aSeed', new Float32BufferAttribute(trailSeeds, 1));
165
+ dust = new Points(dustGeometry, new ShaderMaterial({ ...settings, vertexShader: dustVertex, fragmentShader: dustFragment }));
166
+ dust.frustumCulled = false;
167
+ scene.add(stars, dust);
168
+ document.documentElement.dataset.webgl = 'ready';
169
+ resizeRenderer();
170
+ } catch {
171
+ disposeScene();
172
+ // CSS keeps a quiet star field if WebGL is unavailable.
173
+ document.documentElement.dataset.webgl = 'fallback';
174
+ }
175
+ }
176
+ function disposeScene() {
177
+ starGeometry?.dispose(); dustGeometry?.dispose();
178
+ stars?.material.dispose(); dust?.material.dispose();
179
+ renderer?.dispose();
180
+ renderer = null;
181
+ }
182
+ function resizeRenderer() {
183
+ if (!renderer) return;
184
+ density = Math.min(devicePixelRatio || 1, 1.5, 2048 / Math.max(width, height));
185
+ renderer.setPixelRatio(density);
186
+ renderer.setSize(width, height, false);
187
+ camera.aspect = width / height;
188
+ camera.updateProjectionMatrix();
189
+ const spanY = 2 * Math.tan(42 * Math.PI / 360) * 8;
190
+ uniforms.uSpan.value.set(spanY * camera.aspect, spanY);
191
+ uniforms.uPixelRatio.value = density;
192
+ starGeometry.setDrawRange(0, Math.min(148, Math.max(65, Math.round(width * height / 9500))));
193
+ }
194
+ function render() {
195
+ if (renderer && !lost) renderer.render(scene, camera);
196
+ }
197
+ function updatePalette() {
198
+ const colors = palettes[document.body.dataset.theme] || palettes.day;
199
+ uniforms.uColorA.value.set(colors[0]);
200
+ uniforms.uColorB.value.set(colors[1]);
201
+ uniforms.uColorC.value.set(colors[2]);
202
+ uniforms.uOpacity.value = colors[3];
203
+ render();
204
+ }
205
+ function clearDust() {
206
+ trailBirths.fill(-100);
207
+ if (dustGeometry) {
208
+ dustGeometry.getAttribute('aBirth').array.set(trailBirths);
209
+ dustGeometry.getAttribute('aBirth').needsUpdate = true;
210
+ }
211
+ }
212
+ function updatePreferences() {
213
+ stop();
214
+ const staticMode = isStatic();
215
+ document.documentElement.dataset.effectsPaused = String(document.hidden);
216
+ document.documentElement.dataset.effectsStatic = String(staticMode);
217
+ // Touch/reduced-motion starts with CSS alone; no WebGL context or render loop.
218
+ if (!staticMode) createScene();
219
+ if (staticMode) {
220
+ pointer.alpha = 0;
221
+ uniforms.uPointerAlpha.value = 0;
222
+ clearDust();
223
+ render();
224
+ }
225
+ updatePalette();
226
+ schedule();
227
+ }
228
+ function follow(dt) {
229
+ const ease = 1 - Math.exp(-dt / 90);
230
+ const slow = 1 - Math.exp(-dt / 300);
231
+ pointer.ringX += (pointer.x - pointer.ringX) * ease;
232
+ pointer.ringY += (pointer.y - pointer.ringY) * ease;
233
+ pointer.fieldX += (pointer.x - pointer.fieldX) * slow;
234
+ pointer.fieldY += (pointer.y - pointer.fieldY) * slow;
235
+ pointer.alpha += ((pointer.active ? 1 : 0) - pointer.alpha) * ease;
236
+ const scale = pointer.down ? .72 : pointer.hover ? 1.35 : 1;
237
+ follower.style.transform = `translate3d(${pointer.ringX - 15}px, ${pointer.ringY - 15}px, 0) scale(${scale})`;
238
+ follower.style.opacity = String(pointer.alpha * (pointer.hover ? .35 : .8));
239
+ const span = uniforms.uSpan.value;
240
+ uniforms.uPointer.value.set((pointer.fieldX / width - .5) * span.x, (.5 - pointer.fieldY / height) * span.y);
241
+ uniforms.uPointerAlpha.value = pointer.alpha;
242
+ const targetX = pointer.active ? (pointer.x / width - .5) * span.x : 0;
243
+ const targetY = pointer.active ? (.5 - pointer.y / height) * span.y : 0;
244
+ const parallax = uniforms.uParallax.value;
245
+ parallax.x += (targetX - parallax.x) * slow;
246
+ parallax.y += (targetY - parallax.y) * slow;
247
+ }
248
+ function addDust(now) {
249
+ if (!dustGeometry || now - lastTrail < 28 || Math.hypot(pointer.x - lastTrailX, pointer.y - lastTrailY) < 5) return;
250
+ const span = uniforms.uSpan.value;
251
+ const index = trailIndex++ % trailCount;
252
+ const position = dustGeometry.getAttribute('position');
253
+ position.setXYZ(index, (pointer.x / width - .5) * span.x, (.5 - pointer.y / height) * span.y, 0);
254
+ position.needsUpdate = true;
255
+ const births = dustGeometry.getAttribute('aBirth');
256
+ births.setX(index, elapsed);
257
+ births.needsUpdate = true;
258
+ lastTrail = now; lastTrailX = pointer.x; lastTrailY = pointer.y;
259
+ }
260
+ function tick(now) {
261
+ frame = 0;
262
+ if (document.hidden || isStatic() || lost) return;
263
+ if (lastFrame && now - lastFrame < 31) { schedule(); return; }
264
+ const dt = lastFrame ? Math.min(now - lastFrame, 64) : 33;
265
+ lastFrame = now;
266
+ elapsed += dt / 1000;
267
+ uniforms.uTime.value = elapsed;
268
+ follow(dt);
269
+ render();
270
+ schedule();
271
+ }
272
+ function resize() {
273
+ width = innerWidth; height = innerHeight;
274
+ resizeRenderer();
275
+ clearDust();
276
+ render();
277
+ }
278
+ window.addEventListener('pointermove', event => {
279
+ if (isStatic() || event.pointerType === 'touch') return;
280
+ pointer.x = event.clientX; pointer.y = event.clientY;
281
+ if (!pointer.active) {
282
+ pointer.ringX = pointer.fieldX = pointer.x;
283
+ pointer.ringY = pointer.fieldY = pointer.y;
284
+ lastTrailX = pointer.x; lastTrailY = pointer.y;
285
+ }
286
+ pointer.active = true;
287
+ pointer.hover = !!event.target.closest('a, button, input, textarea, select, summary');
288
+ addDust(performance.now());
289
+ schedule();
290
+ }, { passive: true });
291
+ window.addEventListener('pointerdown', event => {
292
+ if (isStatic() || event.pointerType === 'touch') return;
293
+ pointer.down = true; schedule();
294
+ }, { passive: true });
295
+ window.addEventListener('pointerup', () => { pointer.down = false; schedule(); }, { passive: true });
296
+ const leave = () => { pointer.active = false; pointer.down = false; schedule(); };
297
+ document.documentElement.addEventListener('pointerleave', leave);
298
+ window.addEventListener('blur', leave);
299
+ window.addEventListener('resize', resize, { passive: true });
300
+ document.addEventListener('visibilitychange', () => {
301
+ document.documentElement.dataset.effectsPaused = String(document.hidden);
302
+ if (document.hidden) stop(); else schedule();
303
+ });
304
+ canvas.addEventListener('webglcontextlost', event => {
305
+ event.preventDefault(); lost = true; stop();
306
+ document.documentElement.dataset.webgl = 'fallback';
307
+ });
308
+ canvas.addEventListener('webglcontextrestored', () => {
309
+ lost = false;
310
+ document.documentElement.dataset.webgl = 'ready';
311
+ clearDust(); render(); schedule();
312
+ });
313
+ new MutationObserver(updatePalette).observe(document.body, { attributes: true, attributeFilter: ['data-theme'] });
314
+ reducedMotion.addEventListener('change', updatePreferences);
315
+ finePointer.addEventListener('change', updatePreferences);
316
+ window.addEventListener('pagehide', () => stop());
317
+ window.addEventListener('pageshow', () => schedule());
318
+ // Previous comparison URLs now open this single star field.
319
+ const url = new URL(location.href);
320
+ if (url.searchParams.has('effect')) {
321
+ url.searchParams.delete('effect'); history.replaceState(null, '', url);
322
+ }
323
+ updatePreferences();
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
2
+ <rect width="48" height="48" rx="14" fill="#3f7057"/>
3
+ <g fill="none" stroke="#faf9f6" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
4
+ <path d="m17 15-9 9 9 9m14-18 9 9-9 9m-5-22-4 26"/>
5
+ </g>
6
+ </svg>