@anglefeint/astro-theme 0.1.13 → 0.1.15
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.
- package/package.json +2 -4
- package/src/layouts/BlogPost.astro +2 -1
- package/src/layouts/shells/AiShell.astro +2 -1
- package/src/layouts/shells/CyberShell.astro +4 -1
- package/src/layouts/shells/HackerShell.astro +2 -1
- package/src/layouts/shells/MatrixShell.astro +2 -1
- package/src/scripts/about/background.js +150 -0
- package/src/scripts/about/interactions.js +101 -0
- package/src/scripts/about/modals.js +347 -0
- package/src/scripts/about/reading-ui.js +60 -0
- package/src/scripts/about/runtime.js +20 -0
- package/src/scripts/about-effects.js +12 -551
- package/src/scripts/blogpost/hero-canvas.js +253 -0
- package/src/scripts/blogpost/interactions.js +73 -0
- package/src/scripts/blogpost/network-canvas.js +117 -0
- package/src/scripts/blogpost/read-progress.js +52 -0
- package/src/scripts/blogpost/red-queen-tv.js +604 -0
- package/src/scripts/blogpost-effects.js +20 -1084
- /package/{public → src}/styles/about-page.css +0 -0
- /package/{public → src}/styles/blog-list.css +0 -0
- /package/{public → src}/styles/blog-post.css +0 -0
- /package/{public → src}/styles/home-page.css +0 -0
- /package/{public → src}/styles/theme-ai.css +0 -0
- /package/{public → src}/styles/theme-cyber.css +0 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
export function initHeroCanvas(prefersReducedMotion) {
|
|
2
|
+
var shell = document.querySelector('.hero-shell');
|
|
3
|
+
if (!shell) return;
|
|
4
|
+
|
|
5
|
+
var canvas = shell.querySelector('.hero-canvas');
|
|
6
|
+
var wrap = shell.querySelector('.hero-canvas-wrap');
|
|
7
|
+
if (!canvas || !wrap) return;
|
|
8
|
+
|
|
9
|
+
var src = canvas.getAttribute('data-hero-src');
|
|
10
|
+
if (!src) return;
|
|
11
|
+
|
|
12
|
+
var heroStart = 0;
|
|
13
|
+
var heroRaf = 0;
|
|
14
|
+
var baseCanvas = document.createElement('canvas');
|
|
15
|
+
var baseCtx = baseCanvas.getContext('2d');
|
|
16
|
+
var pixelCanvas = document.createElement('canvas');
|
|
17
|
+
var pixelCtx = pixelCanvas.getContext('2d');
|
|
18
|
+
var noiseCanvas = document.createElement('canvas');
|
|
19
|
+
var noiseCtx = noiseCanvas.getContext('2d');
|
|
20
|
+
var edgeCanvas = document.createElement('canvas');
|
|
21
|
+
var edgeCtx = edgeCanvas.getContext('2d');
|
|
22
|
+
var edgeReady = false;
|
|
23
|
+
|
|
24
|
+
var EDGE_PHASE = 1.8;
|
|
25
|
+
var REVEAL_PHASE = 2.5;
|
|
26
|
+
var INTRO_END = EDGE_PHASE + REVEAL_PHASE;
|
|
27
|
+
|
|
28
|
+
function sizeCanvas() {
|
|
29
|
+
var rect = shell.querySelector('.hero-stack').getBoundingClientRect();
|
|
30
|
+
var dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
31
|
+
canvas.width = Math.max(2, Math.round(rect.width * dpr));
|
|
32
|
+
canvas.height = Math.max(2, Math.round(rect.height * dpr));
|
|
33
|
+
canvas.style.width = rect.width + 'px';
|
|
34
|
+
canvas.style.height = rect.height + 'px';
|
|
35
|
+
noiseCanvas.width = canvas.width;
|
|
36
|
+
noiseCanvas.height = 64;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function drawBase(ctx, img, w, h) {
|
|
40
|
+
var iw = img.width;
|
|
41
|
+
var ih = img.height;
|
|
42
|
+
var scale = Math.max(w / iw, h / ih);
|
|
43
|
+
var sw = w / scale;
|
|
44
|
+
var sh = h / scale;
|
|
45
|
+
var sx = (iw - sw) / 2;
|
|
46
|
+
var sy = (ih - sh) / 2;
|
|
47
|
+
ctx.drawImage(img, sx, sy, sw, sh, 0, 0, w, h);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function buildEdge(img) {
|
|
51
|
+
var w = canvas.width;
|
|
52
|
+
var h = canvas.height;
|
|
53
|
+
edgeCanvas.width = w;
|
|
54
|
+
edgeCanvas.height = h;
|
|
55
|
+
baseCanvas.width = w;
|
|
56
|
+
baseCanvas.height = h;
|
|
57
|
+
drawBase(baseCtx, img, w, h);
|
|
58
|
+
drawBase(edgeCtx, img, w, h);
|
|
59
|
+
|
|
60
|
+
var srcImage = edgeCtx.getImageData(0, 0, w, h);
|
|
61
|
+
var d = srcImage.data;
|
|
62
|
+
var out = edgeCtx.createImageData(w, h);
|
|
63
|
+
var od = out.data;
|
|
64
|
+
|
|
65
|
+
for (var y = 1; y < h - 1; y++) {
|
|
66
|
+
for (var x = 1; x < w - 1; x++) {
|
|
67
|
+
var idx = function(px, py) {
|
|
68
|
+
return ((py * w) + px) * 4;
|
|
69
|
+
};
|
|
70
|
+
var i = idx(x, y);
|
|
71
|
+
function luma(px, py) {
|
|
72
|
+
var j = idx(px, py);
|
|
73
|
+
return d[j] * 0.299 + d[j + 1] * 0.587 + d[j + 2] * 0.114;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
var gx = -luma(x - 1, y - 1) - 2 * luma(x - 1, y) - luma(x - 1, y + 1)
|
|
77
|
+
+ luma(x + 1, y - 1) + 2 * luma(x + 1, y) + luma(x + 1, y + 1);
|
|
78
|
+
var gy = -luma(x - 1, y - 1) - 2 * luma(x, y - 1) - luma(x + 1, y - 1)
|
|
79
|
+
+ luma(x - 1, y + 1) + 2 * luma(x, y + 1) + luma(x + 1, y + 1);
|
|
80
|
+
var mag = Math.min(255, Math.sqrt(gx * gx + gy * gy));
|
|
81
|
+
|
|
82
|
+
od[i] = Math.min(255, mag * 0.4);
|
|
83
|
+
od[i + 1] = Math.min(255, mag * 0.85);
|
|
84
|
+
od[i + 2] = Math.min(255, mag * 1.0);
|
|
85
|
+
od[i + 3] = mag > 20 ? Math.min(255, mag * 1.5) : 0;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
edgeCtx.putImageData(out, 0, 0);
|
|
90
|
+
edgeReady = true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function heroRender(t) {
|
|
94
|
+
if (!heroStart) heroStart = t;
|
|
95
|
+
var elapsed = (t - heroStart) * 0.001;
|
|
96
|
+
var ctx = canvas.getContext('2d');
|
|
97
|
+
if (!ctx || !canvas.img) {
|
|
98
|
+
heroRaf = requestAnimationFrame(heroRender);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
var w = canvas.width;
|
|
103
|
+
var h = canvas.height;
|
|
104
|
+
ctx.clearRect(0, 0, w, h);
|
|
105
|
+
|
|
106
|
+
if (elapsed < EDGE_PHASE && edgeReady) {
|
|
107
|
+
var edgeFade = Math.min(1, elapsed / 0.5);
|
|
108
|
+
ctx.fillStyle = 'rgba(8, 16, 28, 1)';
|
|
109
|
+
ctx.fillRect(0, 0, w, h);
|
|
110
|
+
ctx.globalAlpha = edgeFade;
|
|
111
|
+
ctx.drawImage(edgeCanvas, 0, 0);
|
|
112
|
+
ctx.globalAlpha = 1;
|
|
113
|
+
|
|
114
|
+
var scanY = (elapsed / EDGE_PHASE) * h;
|
|
115
|
+
ctx.fillStyle = 'rgba(120, 220, 255, 0.3)';
|
|
116
|
+
ctx.fillRect(0, scanY - 1, w, 2);
|
|
117
|
+
var scanGlow = ctx.createLinearGradient(0, scanY - 30, 0, scanY + 30);
|
|
118
|
+
scanGlow.addColorStop(0, 'rgba(120, 220, 255, 0)');
|
|
119
|
+
scanGlow.addColorStop(0.5, 'rgba(120, 220, 255, 0.15)');
|
|
120
|
+
scanGlow.addColorStop(1, 'rgba(120, 220, 255, 0)');
|
|
121
|
+
ctx.fillStyle = scanGlow;
|
|
122
|
+
ctx.fillRect(0, scanY - 30, w, 60);
|
|
123
|
+
} else if (elapsed < INTRO_END) {
|
|
124
|
+
var revealT = (elapsed - EDGE_PHASE) / REVEAL_PHASE;
|
|
125
|
+
var maxBlock = 32;
|
|
126
|
+
var blockSize = Math.max(1, Math.round(maxBlock * (1 - revealT * revealT)));
|
|
127
|
+
|
|
128
|
+
if (blockSize > 1) {
|
|
129
|
+
var smallW = Math.max(1, Math.ceil(w / blockSize));
|
|
130
|
+
var smallH = Math.max(1, Math.ceil(h / blockSize));
|
|
131
|
+
if (pixelCanvas.width !== smallW || pixelCanvas.height !== smallH) {
|
|
132
|
+
pixelCanvas.width = smallW;
|
|
133
|
+
pixelCanvas.height = smallH;
|
|
134
|
+
}
|
|
135
|
+
pixelCtx.clearRect(0, 0, smallW, smallH);
|
|
136
|
+
pixelCtx.drawImage(baseCanvas, 0, 0, smallW, smallH);
|
|
137
|
+
ctx.imageSmoothingEnabled = false;
|
|
138
|
+
ctx.drawImage(pixelCanvas, 0, 0, smallW, smallH, 0, 0, w, h);
|
|
139
|
+
ctx.imageSmoothingEnabled = true;
|
|
140
|
+
} else {
|
|
141
|
+
ctx.drawImage(baseCanvas, 0, 0);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
var tintAlpha = 0.18 * (1 - revealT);
|
|
145
|
+
ctx.globalCompositeOperation = 'screen';
|
|
146
|
+
ctx.fillStyle = 'rgba(100, 200, 255, ' + tintAlpha + ')';
|
|
147
|
+
ctx.fillRect(0, 0, w, h);
|
|
148
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
149
|
+
} else {
|
|
150
|
+
ctx.drawImage(baseCanvas, 0, 0);
|
|
151
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.03)';
|
|
152
|
+
for (var i = 0; i < h; i += 3) {
|
|
153
|
+
ctx.fillRect(0, i, w, 1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
var scanPos = ((elapsed * 40) % (h + 60)) - 30;
|
|
157
|
+
var barGrad = ctx.createLinearGradient(0, scanPos - 30, 0, scanPos + 30);
|
|
158
|
+
barGrad.addColorStop(0, 'rgba(120, 220, 255, 0)');
|
|
159
|
+
barGrad.addColorStop(0.5, 'rgba(120, 220, 255, 0.06)');
|
|
160
|
+
barGrad.addColorStop(1, 'rgba(120, 220, 255, 0)');
|
|
161
|
+
ctx.fillStyle = barGrad;
|
|
162
|
+
ctx.fillRect(0, scanPos - 30, w, 60);
|
|
163
|
+
|
|
164
|
+
if (Math.random() < 0.08) {
|
|
165
|
+
var glitchY = Math.random() * h;
|
|
166
|
+
var glitchH = 2 + Math.random() * 12;
|
|
167
|
+
var shiftX = (Math.random() - 0.5) * 12;
|
|
168
|
+
ctx.save();
|
|
169
|
+
ctx.globalAlpha = 0.22;
|
|
170
|
+
ctx.drawImage(baseCanvas, 0, Math.floor(glitchY), w, Math.ceil(glitchH), Math.round(shiftX), Math.floor(glitchY), w, Math.ceil(glitchH));
|
|
171
|
+
ctx.restore();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (elapsed >= 6 && Math.random() < 0.025) {
|
|
175
|
+
var dropoutY = Math.floor(Math.random() * h);
|
|
176
|
+
var dropoutH = 2 + Math.floor(Math.random() * 2);
|
|
177
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.85)';
|
|
178
|
+
ctx.fillRect(0, dropoutY, w, dropoutH);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (Math.random() < 0.03) {
|
|
182
|
+
var burstY = Math.random() * h * 0.8;
|
|
183
|
+
var burstH = 4 + Math.random() * 20;
|
|
184
|
+
if (noiseCanvas.width !== w) {
|
|
185
|
+
noiseCanvas.width = w;
|
|
186
|
+
noiseCanvas.height = 64;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
noiseCtx.clearRect(0, 0, noiseCanvas.width, noiseCanvas.height);
|
|
190
|
+
for (var n = 0; n < 180; n++) {
|
|
191
|
+
var nx = Math.random() * noiseCanvas.width;
|
|
192
|
+
var ny = Math.random() * noiseCanvas.height;
|
|
193
|
+
var nw = 1 + Math.random() * 3;
|
|
194
|
+
var nh = 1 + Math.random() * 2;
|
|
195
|
+
var alpha = 0.08 + Math.random() * 0.18;
|
|
196
|
+
noiseCtx.fillStyle = 'rgba(160,220,255,' + alpha.toFixed(3) + ')';
|
|
197
|
+
noiseCtx.fillRect(nx, ny, nw, nh);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
ctx.drawImage(
|
|
201
|
+
noiseCanvas,
|
|
202
|
+
0,
|
|
203
|
+
0,
|
|
204
|
+
w,
|
|
205
|
+
Math.ceil(Math.min(burstH, noiseCanvas.height)),
|
|
206
|
+
0,
|
|
207
|
+
Math.floor(burstY),
|
|
208
|
+
w,
|
|
209
|
+
Math.ceil(burstH)
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
heroRaf = requestAnimationFrame(heroRender);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
var img = new Image();
|
|
218
|
+
img.onload = function() {
|
|
219
|
+
canvas.img = img;
|
|
220
|
+
sizeCanvas();
|
|
221
|
+
buildEdge(img);
|
|
222
|
+
wrap.classList.add('ready');
|
|
223
|
+
if (prefersReducedMotion) {
|
|
224
|
+
var staticCtx = canvas.getContext('2d');
|
|
225
|
+
if (staticCtx) staticCtx.drawImage(baseCanvas, 0, 0);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
heroRaf = requestAnimationFrame(heroRender);
|
|
229
|
+
};
|
|
230
|
+
img.src = new URL(src, window.location.href).href;
|
|
231
|
+
|
|
232
|
+
window.addEventListener('resize', function() {
|
|
233
|
+
if (canvas.img) {
|
|
234
|
+
sizeCanvas();
|
|
235
|
+
buildEdge(canvas.img);
|
|
236
|
+
}
|
|
237
|
+
}, { passive: true });
|
|
238
|
+
|
|
239
|
+
function onHeroVisibilityChange() {
|
|
240
|
+
if (prefersReducedMotion) return;
|
|
241
|
+
if (document.hidden) {
|
|
242
|
+
if (heroRaf) cancelAnimationFrame(heroRaf);
|
|
243
|
+
heroRaf = 0;
|
|
244
|
+
} else if (canvas.img && !heroRaf) {
|
|
245
|
+
heroRaf = requestAnimationFrame(heroRender);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
document.addEventListener('visibilitychange', onHeroVisibilityChange);
|
|
250
|
+
window.addEventListener('beforeunload', function() {
|
|
251
|
+
cancelAnimationFrame(heroRaf);
|
|
252
|
+
}, { once: true });
|
|
253
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export function initPostInteractions(prefersReducedMotion) {
|
|
2
|
+
var glow = document.querySelector('.ai-mouse-glow');
|
|
3
|
+
if (glow) {
|
|
4
|
+
var raf;
|
|
5
|
+
var x = 0;
|
|
6
|
+
var y = 0;
|
|
7
|
+
document.addEventListener('mousemove', function(e) {
|
|
8
|
+
x = e.clientX;
|
|
9
|
+
y = e.clientY;
|
|
10
|
+
if (!raf) {
|
|
11
|
+
raf = requestAnimationFrame(function() {
|
|
12
|
+
glow.style.setProperty('--mouse-x', x + 'px');
|
|
13
|
+
glow.style.setProperty('--mouse-y', y + 'px');
|
|
14
|
+
raf = 0;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
document.querySelectorAll('.ai-prose-body a[href]').forEach(function(a) {
|
|
21
|
+
var href = a.getAttribute('href') || '';
|
|
22
|
+
if (!href || href.startsWith('#')) return;
|
|
23
|
+
a.classList.add('ai-link-preview');
|
|
24
|
+
try {
|
|
25
|
+
a.setAttribute('data-preview', href.startsWith('http') ? new URL(href, location.origin).hostname : href);
|
|
26
|
+
} catch (_err) {
|
|
27
|
+
a.setAttribute('data-preview', href);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
var paras = document.querySelectorAll('.ai-prose-body p, .ai-prose-body h2, .ai-prose-body h3, .ai-prose-body pre, .ai-prose-body blockquote, .ai-prose-body ul, .ai-prose-body ol');
|
|
32
|
+
if (window.IntersectionObserver) {
|
|
33
|
+
var io = new IntersectionObserver(function(entries) {
|
|
34
|
+
entries.forEach(function(entry) {
|
|
35
|
+
if (entry.isIntersecting) {
|
|
36
|
+
entry.target.classList.add('ai-para-visible');
|
|
37
|
+
io.unobserve(entry.target);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}, { rootMargin: '0px 0px -60px 0px', threshold: 0.1 });
|
|
41
|
+
|
|
42
|
+
paras.forEach(function(p) {
|
|
43
|
+
io.observe(p);
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
paras.forEach(function(p) {
|
|
47
|
+
p.classList.add('ai-para-visible');
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
var regen = document.querySelector('.ai-regenerate');
|
|
52
|
+
var article = document.querySelector('.ai-article');
|
|
53
|
+
var scan = document.querySelector('.ai-load-scan');
|
|
54
|
+
if (regen && article) {
|
|
55
|
+
regen.addEventListener('click', function() {
|
|
56
|
+
regen.disabled = true;
|
|
57
|
+
regen.classList.add('ai-regenerating');
|
|
58
|
+
article.classList.add('ai-regenerate-flash');
|
|
59
|
+
if (scan) {
|
|
60
|
+
scan.style.animation = 'none';
|
|
61
|
+
scan.offsetHeight;
|
|
62
|
+
scan.style.animation = 'ai-scan 0.8s ease-out forwards';
|
|
63
|
+
scan.style.top = '0';
|
|
64
|
+
scan.style.opacity = '1';
|
|
65
|
+
}
|
|
66
|
+
setTimeout(function() {
|
|
67
|
+
article.classList.remove('ai-regenerate-flash');
|
|
68
|
+
regen.classList.remove('ai-regenerating');
|
|
69
|
+
regen.disabled = false;
|
|
70
|
+
}, prefersReducedMotion ? 120 : 1200);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export function initNetworkCanvas(prefersReducedMotion) {
|
|
2
|
+
var canvas = document.querySelector('.ai-network-canvas');
|
|
3
|
+
if (!canvas) return;
|
|
4
|
+
|
|
5
|
+
var ctx = canvas.getContext('2d');
|
|
6
|
+
if (!ctx) return;
|
|
7
|
+
|
|
8
|
+
var MAX_DPR = 2;
|
|
9
|
+
var rafId = 0;
|
|
10
|
+
var start = 0;
|
|
11
|
+
var last = 0;
|
|
12
|
+
var fps = prefersReducedMotion ? 1 : 30;
|
|
13
|
+
var frameMs = 1000 / fps;
|
|
14
|
+
var points = [];
|
|
15
|
+
var edges = [];
|
|
16
|
+
|
|
17
|
+
function seededRandom(seed) {
|
|
18
|
+
var s = seed >>> 0;
|
|
19
|
+
return function() {
|
|
20
|
+
s = (1664525 * s + 1013904223) >>> 0;
|
|
21
|
+
return s / 4294967296;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resize() {
|
|
26
|
+
var rect = canvas.getBoundingClientRect();
|
|
27
|
+
var dpr = Math.min(MAX_DPR, window.devicePixelRatio || 1);
|
|
28
|
+
canvas.width = Math.max(2, Math.round(rect.width * dpr));
|
|
29
|
+
canvas.height = Math.max(2, Math.round(rect.height * dpr));
|
|
30
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
31
|
+
|
|
32
|
+
var w = rect.width;
|
|
33
|
+
var h = rect.height;
|
|
34
|
+
var rand = seededRandom(0xA13F09);
|
|
35
|
+
var count = Math.max(20, Math.min(36, Math.round((w * h) / 32000)));
|
|
36
|
+
var connectDist = Math.min(160, Math.max(90, Math.min(w, h) * 0.18));
|
|
37
|
+
var maxEdges = 120;
|
|
38
|
+
points = [];
|
|
39
|
+
|
|
40
|
+
for (var i = 0; i < count; i++) {
|
|
41
|
+
points.push({
|
|
42
|
+
x: 20 + rand() * Math.max(20, w - 40),
|
|
43
|
+
y: 20 + rand() * Math.max(20, h - 40),
|
|
44
|
+
r: 1 + rand() * 1.6,
|
|
45
|
+
p: rand() * Math.PI * 2,
|
|
46
|
+
a: 0.5 + rand() * 0.5,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
edges = [];
|
|
51
|
+
for (var a = 0; a < points.length; a++) {
|
|
52
|
+
for (var b = a + 1; b < points.length; b++) {
|
|
53
|
+
if (edges.length >= maxEdges) break;
|
|
54
|
+
var dx = points[a].x - points[b].x;
|
|
55
|
+
var dy = points[a].y - points[b].y;
|
|
56
|
+
var d = Math.sqrt(dx * dx + dy * dy);
|
|
57
|
+
if (d < connectDist) edges.push([a, b, d / connectDist]);
|
|
58
|
+
}
|
|
59
|
+
if (edges.length >= maxEdges) break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function render(ts) {
|
|
64
|
+
if (!start) start = ts;
|
|
65
|
+
if (!prefersReducedMotion && ts - last < frameMs) {
|
|
66
|
+
rafId = requestAnimationFrame(render);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
last = ts;
|
|
71
|
+
var t = (ts - start) * 0.001;
|
|
72
|
+
var w = canvas.clientWidth;
|
|
73
|
+
var h = canvas.clientHeight;
|
|
74
|
+
ctx.clearRect(0, 0, w, h);
|
|
75
|
+
|
|
76
|
+
for (var i = 0; i < edges.length; i++) {
|
|
77
|
+
var e = edges[i];
|
|
78
|
+
var p1 = points[e[0]];
|
|
79
|
+
var p2 = points[e[1]];
|
|
80
|
+
var alpha = (1 - e[2]) * (prefersReducedMotion ? 0.2 : (0.18 + 0.06 * Math.sin(t * 0.9 + i)));
|
|
81
|
+
ctx.strokeStyle = 'rgba(190, 236, 255,' + Math.max(0.06, alpha).toFixed(3) + ')';
|
|
82
|
+
ctx.lineWidth = 0.6;
|
|
83
|
+
ctx.beginPath();
|
|
84
|
+
ctx.moveTo(p1.x, p1.y);
|
|
85
|
+
ctx.lineTo(p2.x, p2.y);
|
|
86
|
+
ctx.stroke();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (var j = 0; j < points.length; j++) {
|
|
90
|
+
var p = points[j];
|
|
91
|
+
var pulse = prefersReducedMotion ? 1 : (1 + 0.18 * Math.sin(t * 1.5 + p.p));
|
|
92
|
+
ctx.fillStyle = 'rgba(228, 251, 255,' + (0.58 * p.a).toFixed(3) + ')';
|
|
93
|
+
ctx.beginPath();
|
|
94
|
+
ctx.arc(p.x, p.y, p.r * pulse, 0, Math.PI * 2);
|
|
95
|
+
ctx.fill();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!prefersReducedMotion) rafId = requestAnimationFrame(render);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function stop() {
|
|
102
|
+
if (!rafId) return;
|
|
103
|
+
cancelAnimationFrame(rafId);
|
|
104
|
+
rafId = 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
resize();
|
|
108
|
+
render(performance.now());
|
|
109
|
+
|
|
110
|
+
window.addEventListener('resize', resize, { passive: true });
|
|
111
|
+
document.addEventListener('visibilitychange', function() {
|
|
112
|
+
if (prefersReducedMotion) return;
|
|
113
|
+
if (document.hidden) stop();
|
|
114
|
+
else if (!rafId) rafId = requestAnimationFrame(render);
|
|
115
|
+
});
|
|
116
|
+
window.addEventListener('beforeunload', stop, { once: true });
|
|
117
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export function initReadProgressAndBackToTop(prefersReducedMotion) {
|
|
2
|
+
var progress = document.querySelector('.ai-read-progress');
|
|
3
|
+
var toast = document.querySelector('.ai-stage-toast');
|
|
4
|
+
var stageSeen = { p30: false, p60: false, p90: false };
|
|
5
|
+
var toastTimer = 0;
|
|
6
|
+
var hasScrolled = false;
|
|
7
|
+
|
|
8
|
+
function showStageToast(msg) {
|
|
9
|
+
if (!toast) return;
|
|
10
|
+
toast.textContent = msg;
|
|
11
|
+
toast.classList.add('visible');
|
|
12
|
+
clearTimeout(toastTimer);
|
|
13
|
+
toastTimer = setTimeout(function() {
|
|
14
|
+
toast.classList.remove('visible');
|
|
15
|
+
}, 900);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (progress) {
|
|
19
|
+
function onScroll() {
|
|
20
|
+
var scrollTop = window.scrollY || document.documentElement.scrollTop;
|
|
21
|
+
var scrollHeight = document.documentElement.scrollHeight - window.innerHeight;
|
|
22
|
+
var p = scrollHeight > 0 ? Math.min(1, scrollTop / scrollHeight) : 1;
|
|
23
|
+
progress.style.setProperty('--read-progress', String(p));
|
|
24
|
+
var btn = document.querySelector('.ai-back-to-top');
|
|
25
|
+
if (btn) btn.classList.toggle('visible', scrollTop > 400);
|
|
26
|
+
if (!hasScrolled && scrollTop > 6) hasScrolled = true;
|
|
27
|
+
if (!hasScrolled) return;
|
|
28
|
+
if (!stageSeen.p30 && p >= 0.3) {
|
|
29
|
+
stageSeen.p30 = true;
|
|
30
|
+
showStageToast('context parsed');
|
|
31
|
+
}
|
|
32
|
+
if (!stageSeen.p60 && p >= 0.6) {
|
|
33
|
+
stageSeen.p60 = true;
|
|
34
|
+
showStageToast('inference stable');
|
|
35
|
+
}
|
|
36
|
+
if (!stageSeen.p90 && p >= 0.9) {
|
|
37
|
+
stageSeen.p90 = true;
|
|
38
|
+
showStageToast('output finalized');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
onScroll();
|
|
43
|
+
window.addEventListener('scroll', onScroll, { passive: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
var backTop = document.querySelector('.ai-back-to-top');
|
|
47
|
+
if (backTop) {
|
|
48
|
+
backTop.addEventListener('click', function() {
|
|
49
|
+
window.scrollTo({ top: 0, behavior: prefersReducedMotion ? 'auto' : 'smooth' });
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|