| 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 | }; |