1// This canvas was written partially by AI
2// @ts-ignore
3globalThis.canvas_cotyledon = function(
4 canvas: HTMLCanvasElement,
5 panel: HTMLElement,
6) {
7 let running = true;
8 const ctx = canvas.getContext("2d");
9
10 function resizeCanvas() {
11 canvas.width = canvas.clientWidth;
12 canvas.height = canvas.clientHeight;
13 }
14
15 resizeCanvas();
16 window.addEventListener("resize", resizeCanvas);
17
18 const clover = new Path2D(
19 "M18.9845 34.4839C20.4004 34.5218 21.8336 34.6883 23.2644 34.9578C20.1378 31.095 18.4268 27.1546 18.0555 23.2959C17.321 15.6622 21.9022 9.36595 28.8908 5.78535C34.6355 2.84212 40.258 2.98454 44.2809 5.96879C45.6605 6.99221 46.7683 8.2886 47.5877 9.78593C48.3054 8.50307 49.134 7.26623 50.0858 6.17951C51.8368 4.18037 54.1947 2.47127 57.2294 2.15019C60.2768 1.82766 63.467 2.9608 66.7548 5.52299C70.9834 8.81811 73.084 12.8864 73.5996 17.2135C74.1044 21.4504 73.0711 25.7433 71.4155 29.6117C70.6566 31.3849 69.7488 33.1106 68.7557 34.7506C70.3664 33.9983 72.0168 33.3376 73.6816 32.8312C77.2262 31.7528 81.0258 31.3024 84.8151 32.2149C88.6451 33.1371 92.2246 35.3946 95.3823 39.3157C98.4534 43.1293 99.9219 46.6818 99.997 49.9677C100.073 53.3033 98.7051 55.9829 96.8652 57.9789C95.0586 59.9387 92.7653 61.2872 90.7505 62.1315C90.692 62.1561 90.6334 62.1802 90.5746 62.2042L90.4465 62.256C91.4852 63.7304 92.4724 65.5955 93.0127 67.6979C93.5916 69.9509 93.6669 72.5285 92.674 75.1356C91.679 77.7482 89.7006 80.1559 86.5767 82.2161C86.5556 82.23 86.5342 82.2438 86.5126 82.2571C84.1333 83.7267 81.5504 84.7197 78.6932 84.9352C75.832 85.151 72.8634 84.5742 69.7337 83.1522C64.7667 80.8953 59.274 76.4525 52.8745 69.3645C52.8789 70.1568 52.8844 70.9254 52.9004 71.6677C52.9643 74.6226 53.1868 77.4534 54.0666 80.6265C55.2259 84.503 57.2821 88.4684 60.9561 92.3161C61.644 93.0366 61.8512 94.0908 61.4872 95.018L60.9919 96.2799C60.6464 97.16 59.8435 97.778 58.9041 97.8865C57.9647 97.9952 57.042 97.5769 56.5047 96.7985C52.5406 91.0574 50.3441 86.3289 49.1491 82.0434C48.0155 78.2319 47.6244 74.4579 47.5085 71.0024C45.418 73.6873 42.8696 76.4687 40.0618 78.9101C34.3517 83.8756 26.6803 88.1931 19.142 85.9955C15.5301 84.9425 12.8635 83.2751 11.0848 81.1179C9.2952 78.9474 8.5557 76.4627 8.4981 74.0631C8.43961 71.6256 9.07998 69.225 10.075 67.1703C7.76333 66.828 5.38011 65.9682 3.47071 64.2327C-0.339092 60.7699 -1.2199 54.8876 1.86982 46.4552C3.47011 42.0878 5.90372 38.9798 8.98328 37.0179C12.0444 35.0677 15.5215 34.3912 18.9845 34.4839Z",
20 );
21
22 // Background
23 const base = [0x14, 0x1a, 0x19];
24
25 let blobTextureCanvas: OffscreenCanvas;
26 let blobTextureCtx: OffscreenCanvasRenderingContext2D;
27 const blobSize = 1000; // Size of the noise texture
28 {
29 const blobTexture = new ImageData(blobSize, blobSize);
30 const data = blobTexture.data;
31
32 let x = 0;
33 let y = 0;
34 for (let i = 0; i < data.length; i += 4) {
35 if (x >= blobSize) {
36 x = 0;
37 y++;
38 }
39
40 const noiseX = Math.sin(x * .2 + y * .1) * 0.03;
41 const noiseY = Math.cos(y * .6 + x * .2) * 0.05;
42
43 const centerX = blobSize / 2;
44 const centerY = blobSize / 2;
45 const dx = (x + noiseX) - centerX;
46 const dy = (y + noiseY) - centerY;
47 const distanceFromCenterRaw = (dx * dx + dy * dy) ** 0.5;
48
49 const maxDistance = blobSize / 2;
50 const distanceFromCenter = Math.min(
51 1,
52 distanceFromCenterRaw / maxDistance,
53 );
54
55 const noiseValue = (1 - 0.5 * Math.sin(x * 0.02 - y * 0.04))
56 * (1 - 0.5 * Math.cos(x * 0.03 + y * 0.04)) * 0.3;
57
58 const gradient = (1 - distanceFromCenter)
59 * (0.95 - distanceFromCenter * 0.4);
60 const finalValue = Math.max(
61 0,
62 Math.min(1, gradient * (Math.random() * 0.3 + 0.85 + noiseValue)),
63 );
64
65 data[i] = 121;
66 data[i + 1] = 219;
67 data[i + 2] = 160;
68 data[i + 3] = Math.floor(finalValue * 255.99); // Alpha
69 x++;
70 }
71 blobTextureCanvas = new OffscreenCanvas(blobSize, blobSize);
72 blobTextureCtx = blobTextureCanvas.getContext("2d")!;
73 blobTextureCtx.putImageData(blobTexture, 0, 0);
74 }
75
76 class CotyledonParticle {
77 x: number;
78 y: number;
79 size: number;
80 velocityX: number;
81 velocityY: number;
82 rotation: number;
83 rotationSpeed: number;
84 color: string;
85
86 constructor(positioning: "random" | "edge") {
87 this.size = Math.random() * 0.1 + 0.6;
88
89 if (positioning === "edge") {
90 const edge = Math.floor(Math.random() * 5);
91 if (edge === 0 || edge === 1) {
92 // Right edge
93 this.x = 1.05;
94 this.y = Math.random();
95 this.velocityX = -Math.random() * 0.1 - 0.2;
96 this.velocityY = 0;
97 } else if (edge === 2 || edge === 3) {
98 // Top edge
99 this.x = Math.random();
100 this.y = -0.05;
101 this.velocityX = -Math.random() * 0.1 - 0.1;
102 this.velocityY = -Math.random() * 0.2 - 0.05;
103 } else {
104 // Bottom edge
105 this.x = Math.random() * 0.5 + 0.5;
106 this.y = 1.05;
107 this.velocityX = Math.random() * 0.1 + 0.1;
108 this.velocityY = -Math.random() * 0.3 - 0.1;
109 }
110 } else {
111 let tries = 0;
112 do {
113 this.x = Math.random();
114 this.y = Math.random();
115 this.velocityX = -Math.random() * 0.05 - 0.1;
116 this.velocityY = -Math.random() * 0.2 + 0.1;
117 } while (this.tooCloseToAnyOtherParticle() && (tries++ < 10));
118 }
119
120 this.rotation = Math.random() * Math.PI * 2;
121 this.rotationSpeed = (Math.random() * 0.003 - 0.0015)
122 * (Math.random() > 0.5 ? 1 : -1);
123
124 const opacity = Math.random() * 0.4 + 0.2;
125 this.color = `rgba(${base.map((x) => x + Math.floor(x * opacity)).join(",")}, 1)`;
126 }
127
128 tooCloseToAnyOtherParticle() {
129 for (let i = 0; i < cotyledonParticles.length; i++) {
130 const otherParticle = cotyledonParticles[i];
131 const distance = Math.sqrt(
132 (this.x - otherParticle.x) ** 2 + (this.y - otherParticle.y) ** 2,
133 );
134 if (distance < 0.1) {
135 return true;
136 }
137 }
138 }
139
140 update() {
141 if (this.velocityY < 0.01) {
142 this.velocityY += 0.00025;
143 }
144 this.velocityX -= 0.0001;
145 this.x += this.velocityX / 1300;
146 this.y += this.velocityY / 1000;
147 this.rotation += this.rotationSpeed;
148 return this.x < -0.05 || (this.y > 1 && this.velocityY < 0);
149 }
150
151 draw() {
152 if (!ctx) return;
153
154 ctx.save();
155 ctx.translate(this.x * canvas.width, this.y * canvas.height);
156 ctx.rotate(this.rotation);
157 ctx.scale(this.size, this.size);
158 ctx.translate(-50, -50);
159 ctx.fillStyle = this.color;
160 ctx.fill(clover);
161 ctx.restore();
162 }
163 }
164
165 class BlobParticle {
166 x: number;
167 y: number;
168 opacity: number;
169 state: 0 | 1 | 2;
170 stateTime: number;
171 stayDuration: number;
172 innerColor: string;
173 rot: number = Math.random() * Math.PI * 2;
174
175 constructor() {
176 this.x = Math.random() * 0.6 + 0.2;
177 this.y = Math.random() * 0.6 + 0.2;
178
179 this.opacity = 0; // Start fully transparent
180
181 this.state = 0;
182 this.stateTime = 0;
183 this.stayDuration = Math.random() * 10000 + 5000; // Random stay duration between 5-15 seconds
184
185 const colorMultiplier = Math.random() * 0.5 + 1.5; // 0.5-1.0 multiplier
186 const colorValues = base.map((x) => Math.floor(x * colorMultiplier));
187 this.innerColor = `rgba(${colorValues.join(",")}, 1)`;
188 }
189
190 update(deltaTime: number) {
191 this.stateTime += deltaTime;
192
193 if (this.state === 0) {
194 this.opacity = Math.min(1, this.stateTime / 15000);
195 if (this.stateTime >= 15000) {
196 this.state = 1;
197 this.stateTime = 0;
198 }
199 } else if (this.state === 1) {
200 if (this.stateTime >= this.stayDuration) {
201 this.state = 2;
202 this.stateTime = 0;
203 }
204 } else if (this.state === 2) {
205 this.opacity = Math.max(0, 1 - (this.stateTime / 15000));
206 if (this.stateTime >= 15000) {
207 return true;
208 }
209 }
210 return false;
211 }
212
213 draw() {
214 if (!ctx) return;
215
216 const screenX = this.x * canvas.width;
217 const screenY = this.y * canvas.height;
218 const screenSize = (Math.min(canvas.width, canvas.height) * 2) / blobSize;
219
220 ctx.save();
221 ctx.translate(screenX - screenSize * 0.5, screenY - screenSize * 0.5);
222 ctx.scale(screenSize, screenSize);
223 ctx.rotate(this.rot);
224 ctx.globalAlpha = this.opacity * 0.2;
225 ctx.globalCompositeOperation = "overlay";
226 ctx.drawImage(blobTextureCanvas, 0, 0);
227 ctx.restore();
228 }
229 }
230
231 const cotyledonParticles: CotyledonParticle[] = [];
232 const blobParticles: BlobParticle[] = [];
233 let blobParticleTop: BlobParticle = new BlobParticle();
234 for (let i = 0; i < 80; i++) {
235 cotyledonParticles.push(new CotyledonParticle("random"));
236 }
237 for (let i = 0; i < 9; i++) {
238 const blobParticle = new BlobParticle();
239 if (i < 4) {
240 blobParticle.state = 1;
241 blobParticle.opacity = 1;
242 blobParticle.stayDuration = Math.random() * 10000;
243 } else {
244 blobParticle.state = i < 7 ? 2 : 0;
245 blobParticle.stateTime = Math.random() * 15000;
246 }
247 if (i > 0) {
248 do {
249 blobParticle.x = Math.random();
250 blobParticle.y = Math.random();
251 } while (
252 blobParticles.some((p) =>
253 Math.sqrt((p.x - blobParticle.x) ** 2 + (p.y - blobParticle.y) ** 2)
254 < 0.1
255 )
256 );
257 }
258 blobParticles.push(blobParticle);
259 }
260
261 let lastTime = performance.now();
262
263 function animate(currentTime: number) {
264 if (!running) return;
265 if (!ctx) return;
266
267 const deltaTime = currentTime - lastTime;
268 lastTime = currentTime;
269
270 ctx.clearRect(0, 0, canvas.width, canvas.height);
271
272 for (let i = blobParticles.length - 1; i >= 0; i--) {
273 const shouldRemove = blobParticles[i].update(deltaTime);
274 if (shouldRemove) {
275 blobParticles[i] = new BlobParticle();
276 } else {
277 blobParticles[i].draw();
278 }
279 }
280
281 for (let i = cotyledonParticles.length - 1; i >= 0; i--) {
282 const shouldRemove = cotyledonParticles[i].update();
283 if (shouldRemove) {
284 cotyledonParticles[i] = new CotyledonParticle("edge");
285 } else {
286 cotyledonParticles[i].draw();
287 }
288 }
289
290 if (blobParticleTop.update(deltaTime)) {
291 blobParticleTop = new BlobParticle();
292 }
293 blobParticleTop.draw();
294
295 requestAnimationFrame(animate);
296 }
297
298 let clickedButton = false;
299 const enterButton = panel.querySelector("button#enter")!;
300 enterButton.addEventListener("click", () => {
301 if (clickedButton) return;
302 clickedButton = true;
303 const first = panel.querySelector("#first")! as HTMLElement;
304 const second = panel.querySelector("#captcha")! as HTMLElement;
305 first.style.transition = second.style.transition = "opacity 1s ease-in-out";
306 first.style.opacity = "0";
307 second.style.opacity = "0";
308 setTimeout(() => {
309 first.style.display = "none";
310 second.style.display = "block";
311 setTimeout(() => {
312 second.style.opacity = "1";
313
314 document.getElementById("enter2")?.addEventListener("click", () => {
315 second.style.opacity = "0";
316 let p = fetch("/file/cotyledon", {
317 method: "POST",
318 body: "I AGREE",
319 });
320 setTimeout(() => {
321 p.then(() => {
322 location.reload();
323 });
324 }, 1000);
325 });
326 }, 10);
327 }, 1000);
328 });
329 const imageButtons = panel.querySelectorAll(".image-grid button")!;
330 imageButtons.forEach((button) => {
331 let canClick = true;
332 button.addEventListener("click", () => {
333 if (!canClick) return;
334 canClick = false;
335 const image = button.querySelector("img")!;
336 image.style.transition = "opacity 0.05s linear";
337 image.style.opacity = "0";
338 setTimeout(() => {
339 image.style.transition = "opacity 2s linear";
340 let newNum;
341 do {
342 newNum = Math.floor(Math.random() * 18); // 0-17 inclusive
343 } while (
344 document.querySelector(`img[src="/captcha/image/${newNum}.jpeg"]`)
345 );
346 image.setAttribute("src", `/captcha/image/${newNum}.jpeg`);
347 setTimeout(() => {
348 image.style.opacity = "0.75";
349 canClick = true;
350 }, 50);
351 }, 300);
352 });
353 });
354
355 // Start animation
356 animate(performance.now());
357 return () => {
358 window.removeEventListener("resize", resizeCanvas);
359 running = false;
360 };
361};