1// Ported from CanvasAPI, allegedly written on 2019-08-26.
2(globalThis as any).canvas_2019 = function(canvas: HTMLCanvasElement) {
3 const isStandalone = canvas.getAttribute("data-standalone") === "true";
4 if (isStandalone) {
5 canvas.parentElement!.style.backgroundColor = "#121013";
6 }
7 // Canvas.tsx
8 abstract class CanvasAPI {
9 canvas: HTMLCanvasElement;
10 ctx: CanvasRenderingContext2D;
11 width = 0;
12 height = 0;
13 private _disposed = false;
14 private _running = false;
15 private _last = 0;
16
17 constructor(canvas: HTMLCanvasElement) {
18 this.canvas = canvas;
19 this.width = canvas.width = canvas.clientWidth;
20 this.height = canvas.height = canvas.clientHeight;
21 const ctx = this.canvas.getContext("2d");
22 if (!ctx) {
23 throw new Error("Canvas2D Not Supported!");
24 }
25 this.ctx = ctx;
26 }
27
28 stopRenderLoop() {
29 this._running = false;
30 }
31
32 startRenderLoop() {
33 if (this._disposed) return;
34 this._running = true;
35 this._last = performance.now();
36 requestAnimationFrame(this._renderLoop);
37 }
38
39 private _renderLoop = (delta: number) => {
40 if (!this._running) return;
41 this.render(delta - this._last);
42 this._last = delta;
43 requestAnimationFrame(this._renderLoop);
44 };
45
46 abstract render(delta: number): void;
47 }
48
49 // VaultBackground.ts
50 function addGSHelper(
51 grad: CanvasGradient,
52 color: string,
53 dotOpacity: number,
54 gradStop: number,
55 gradOpacity: number,
56 ) {
57 grad.addColorStop(gradStop, `rgba(${color},${dotOpacity * gradOpacity})`);
58 }
59
60 function randAround(target: number, dist: number) {
61 return (Math.random() - 0.5) * dist * 2 + target;
62 }
63
64 class Dot {
65 x = Math.random() * 1.1 - 0.05;
66 y = Math.random() / 4 + 0.9;
67 size = Math.random() * 200 + 50;
68 opacity = 0;
69 opacityRandom = Math.random() / 3 + 0.3;
70 fadeInOpacity = 1;
71 color = `${randAround(217, 30)}, ${randAround(170, 30)}, ${randAround(255, 20)}`;
72
73 life = 0;
74
75 ySpeed_1 = 0;
76 ySpeed_2 = -0.0000063;
77 ySpeed_3 = 0.000000016;
78 ySpeed_4 = 0.000000000009;
79
80 seed = Math.random();
81
82 delete = false;
83
84 update(init: boolean) {
85 this.life += 0.8;
86 if (this.life < 115) {
87 this.opacity = this.life / 230;
88 } else if (this.life > 450) {
89 this.delete = true;
90 } else if (this.life > 300) {
91 this.opacity = (150 + 300 - this.life) / 300;
92 }
93
94 this.ySpeed_3 += this.ySpeed_4;
95 this.ySpeed_2 += this.ySpeed_3;
96 this.ySpeed_1 += this.ySpeed_2;
97 this.y += this.ySpeed_1 * 0.5;
98
99 this.size -= 0.08;
100
101 if (this.delete) {
102 Object.assign(this, new Dot());
103 }
104 }
105
106 render(scene: VaultBackground) {
107 const ctx = scene.ctx;
108
109 if (this.fadeInOpacity < 1) {
110 this.fadeInOpacity += 0.0075;
111 }
112
113 const finalX = this.x
114 + Math.sin(this.seed * Math.PI * 2 + Date.now() / 15000) * 0.2;
115
116 const drawX = scene.shakeX
117 + finalX * Math.max(700, scene.width)
118 - (Math.max(700, scene.width) - scene.width) / 2;
119 const drawY = scene.shakeY + (this.y * 1.5 - 0.5) * scene.height;
120
121 const opacity = this.opacity * this.opacityRandom * this.fadeInOpacity;
122
123 const grad = ctx.createRadialGradient(
124 drawX,
125 drawY,
126 0,
127 drawX,
128 drawY,
129 this.size,
130 );
131 addGSHelper(grad, this.color, opacity, 0, 1);
132 addGSHelper(grad, this.color, opacity, 0.8, 0.7);
133 addGSHelper(grad, this.color, opacity, 0.87, 0.5);
134 addGSHelper(grad, this.color, opacity, 0.93, 0.3);
135 addGSHelper(grad, this.color, opacity, 1, 0);
136
137 ctx.fillStyle = grad;
138 ctx.fillRect(
139 drawX - this.size,
140 drawY - this.size,
141 this.size * 2,
142 this.size * 2,
143 );
144 }
145 }
146
147 class VaultBackground extends CanvasAPI {
148 private items = new Set<Dot>();
149
150 private shakeVar = 0;
151 private dom?: HTMLElement;
152 shakeX = 0;
153 shakeY = 0;
154
155 constructor(canvas: HTMLCanvasElement) {
156 super(canvas);
157 for (let i = 0; i < 450; i++) {
158 if (i % 7 === 0) {
159 this.items.add(new Dot());
160 }
161 this.items.forEach((x) => x.update(true));
162 }
163 this.items.forEach((x) => x.fadeInOpacity = 0);
164 }
165
166 render(): void {
167 this.ctx.clearRect(0, 0, this.width, this.height);
168
169 this.items.forEach((x) => (x.update(false), x.render(this)));
170
171 if (this.shakeVar >= 0.0001) {
172 this.shakeVar *= 0.97 - 0.22 * this.shakeVar;
173
174 if (this.shakeVar >= 0.0001) {
175 this.shakeX = (Math.random() * 2 - 1) * this.shakeVar * 65;
176 this.shakeY = (Math.random() * 2 - 1) * this.shakeVar * 65;
177 if (this.dom) {
178 this.dom.style.transform = `translate(${this.shakeX}px,${this.shakeY}px)`;
179 }
180 } else {
181 this.shakeX = 0;
182 this.shakeY = 0;
183 if (this.dom) this.dom.style.removeProperty("transform");
184 this.dom = undefined;
185 }
186 }
187 }
188
189 shake(dom?: HTMLElement | null) {
190 this.dom = dom || document.body;
191 this.shakeVar = 1;
192 }
193 }
194
195 // Binding code
196 let bg = new VaultBackground(canvas);
197 bg.startRenderLoop();
198 canvas.style.opacity = "0.2";
199 function onResize() {
200 bg.width = canvas.width = canvas.clientWidth;
201 bg.height = canvas.height = canvas.clientHeight;
202 }
203 window.addEventListener("resize", onResize);
204 onResize();
205 (globalThis as any).vault = bg;
206 return () => {
207 bg.stopRenderLoop();
208 window.removeEventListener("resize", onResize);
209 };
210};