1const scene = document.querySelector('#scene'), 2 fx = document.querySelector('#particles'), 3 ctx = fx.getContext('2d'), 4 overlay = document.querySelector('#celebration'), 5 closeButton = document.querySelector('#close'), 6 replay = document.querySelector('#replay'); 7const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches; 8let W, H, scale, ox, oy, start = performance.now(), 9 running = true, 10 frame = 0, 11 ready = false; 12// ========= æ°å¢æ è®°ï¼æ¯å¦å·²ç»èªå¨å ³é ========= 13let autoClosed = false; 14// å¨ç»æ»æ¶é¿ï¼æ ¹æ®ä½ çè±å¨æ11.7sï¼é¢çä¸ç¹ç¼å²ï¼åä½ç§ 15const ANIM_DURATION = 11.7; 16 17function resize() { 18 W = innerWidth; 19 H = innerHeight; 20 const d = Math.min(devicePixelRatio, 2); 21 for (const c of [scene, fx]) { 22 c.width = W * d; 23 c.height = H * d; 24 } 25 ctx.setTransform(d, 0, 0, d, 0, 0); 26 scale = Math.max(W / 1920, H / 911); 27 if (W / H < 1.15) scale = W / 1120; 28 ox = (W - 1920 * scale) / 2; 29 oy = H - 911 * scale; 30} 31resize(); 32addEventListener('resize', resize); 33const gl = scene.getContext('webgl', { 34 alpha: true, 35 premultipliedAlpha: false, 36 antialias: true 37}); 38const layers = [ 39 ['pillar', 595, 382, 267, 316, 2, 1], 40 ['ribbon', 878, 479, 475, 190, 15, 1], 41 ['palace', 558, 552, 804, 280, 0, 1], 42 ['dove1', 1137, 268, 94, 105, 1, 1.25], 43 ['dove2', 1191, 393, 63, 68, 1, 1.55], 44 ['silk', 0, 0, 1920, 1013, 21, .05], 45 ['star1', 274, 646, 59, 60, 0, 1.1], 46 ['star2', 451, 809, 82, 85, 0, 1.3], 47 ['star3', 1552, 678, 64, 57, 0, 1.5] 48]; 49let program, buffer, position, uv, uniforms; 50if (gl) { 51 const vs = 52 `attribute vec2 a;uniform vec2 screen;uniform vec4 rect;uniform float t,wave,phase,bird,bank,cloud;varying vec2 v;void main(){v=a;vec2 q=a;if(bird>.5){float beat=sin(t*7.8+phase);float flap=.25+.75*(beat*.5+.5);float wing=smoothstep(.28,.55,a.x)*(1.-smoothstep(.58,.72,a.y));q.y=.64+(a.y-.64)*(1.-wing*(1.-flap));q.x+=(a.y-.64)*wing*.18*cos(t*7.8+phase);float tail=smoothstep(.52,.72,a.x)*smoothstep(.72,.88,a.y);q.y-=tail*.075*(beat*.5+.5);vec2 r=q-vec2(.46,.63);q=vec2(r.x*cos(bank)-r.y*sin(bank),r.x*sin(bank)+r.y*cos(bank))+vec2(.46,.63);}if(cloud>.5){float left=(1.-smoothstep(.17,.32,a.x))*smoothstep(.22,.5,a.y);float right=smoothstep(.7,.85,a.x)*smoothstep(.18,.46,a.y);float low=smoothstep(.68,.83,a.y)*(1.-smoothstep(.88,1.,a.y));q.x+=left*.012*sin(t*.65)+right*.01*sin(t*.58+2.)+low*.004*sin(t*.4+a.x*5.);q.y+=left*.019*sin(t*.8+a.x*5.)+right*.018*sin(t*.72+2.3)+low*.005*sin(t*.7+a.x*6.);}vec2 p=rect.xy+q*rect.zw;float edge=sin(a.x*3.14159265);p.y+=wave*(sin(a.x*9.0-t*1.55+phase)*.65+sin(a.x*17.0+t*.85+phase)*.35)*sin(a.y*3.14159265)*edge;p.x+=wave*.24*sin(a.y*7.0+t*1.1)*edge;gl_Position=vec4(p.x/screen.x*2.-1.,1.-p.y/screen.y*2.,0,1);}`; 53 const fs = 54 `precision highp float;varying vec2 v;uniform sampler2D tex;uniform float alpha,t,wave;void main(){vec4 c=texture2D(tex,v);float light=1.+min(wave,20.)*.0025*sin(v.x*12.-t*1.55)*sin(v.y*3.14159);gl_FragColor=vec4(c.rgb*light,c.a*alpha);}`; 55 56 function shader(type, source) { 57 let s = gl.createShader(type); 58 gl.shaderSource(s, source); 59 gl.compileShader(s); 60 if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw Error(gl.getShaderInfoLog(s)); 61 return s; 62 } 63 program = gl.createProgram(); 64 gl.attachShader(program, shader(gl.VERTEX_SHADER, vs)); 65 gl.attachShader(program, shader(gl.FRAGMENT_SHADER, fs)); 66 gl.linkProgram(program); 67 if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw Error(gl.getProgramInfoLog(program)); 68 gl.useProgram(program); 69 let verts = []; 70 for (let y = 0; y < 28; y++) 71 for (let x = 0; x < 100; x++) { 72 let a = x / 100, 73 b = y / 28, 74 c = (x + 1) / 100, 75 d = (y + 1) / 28; 76 verts.push(a, b, c, b, a, d, a, d, c, b, c, d); 77 } 78 buffer = gl.createBuffer(); 79 gl.bindBuffer(gl.ARRAY_BUFFER, buffer); 80 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW); 81 position = gl.getAttribLocation(program, 'a'); 82 gl.enableVertexAttribArray(position); 83 gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0); 84 uniforms = {}; 85 for (const n of ['screen', 'rect', 't', 'wave', 'phase', 'alpha', 'bird', 'bank', 'cloud']) uniforms[n] = gl 86 .getUniformLocation(program, n); 87 gl.enable(gl.BLEND); 88 gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); 89} 90Promise.all(layers.map(async l => { 91 const img = new Image(); 92 // ========== ä¿®æ¹ä¸ºç¸å¯¹è·¯å¾ ========== 93 img.src = '/material/2026gqph/images/' + l[0] + '.png'; 94 await img.decode(); 95 l.img = img; 96 if (gl) { 97 l.tex = gl.createTexture(); 98 gl.bindTexture(gl.TEXTURE_2D, l.tex); 99 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); 100 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 101 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 102 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 103 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 104 } 105})).then(() => { 106 ready = true; 107 start = performance.now(); 108 // 馿¬¡è¿å ¥ï¼ç§»é¤ closed éèç±»ï¼æ¾ç¤ºå¨ç» 109 overlay.classList.remove('closed'); 110 replay.hidden = true; 111 // 馿¬¡ææ¾é宿»å¨ 112 document.body.style.overflow = 'hidden'; 113 document.documentElement.style.overflow = 'hidden'; 114 animate(start); 115}).catch(() => { 116 overlay.classList.add('closed'); 117 replay.hidden = false;
118}); 119const sparks = Array.from({ 120 length: 155 121}, () => ({ 122 x: Math.random(), 123 y: Math.random(), 124 r: Math.random() * 2.3 + .4, 125 s: Math.random() * .5 + .3, 126 p: Math.random() * 6.28 127})); 128 129function ease(v) { 130 return 1 - Math.pow(1 - Math.max(0, Math.min(1, v)), 3); 131} 132 133function animate(now) { 134 if (!running) return; 135 const raw = (now - start) / 1000, 136 t = reduced ? 6 : raw; 137 // ==========ãæ°å¢ã夿å¨ç»ææ¾å®æ¯ï¼èªå¨å ³é ========== 138 if (!autoClosed && raw >= ANIM_DURATION) { 139 autoClosed = true; 140 close(); 141 } 142 143 ctx.clearRect(0, 0, W, H); 144 if (gl) { 145 gl.viewport(0, 0, scene.width, scene.height); 146 gl.clearColor(0, 0, 0, 0); 147 gl.clear(gl.COLOR_BUFFER_BIT); 148 gl.uniform2f(uniforms.screen, W, H); 149 gl.uniform1f(uniforms.t, t); 150 } 151 for (let i = 0; i < layers.length; i++) { 152 const l = layers[i], 153 e = ease((t - l[6]) / 2.15); 154 let [name, x, y, w, h, wave] = l; 155 let dy = (1 - e) * (name === 'silk' ? 470 : 130); 156 let dx = (1 - e) * (name === 'ribbon' ? 400 : name === 'pillar' ? -180 : 0); 157 let z = 1 + (1 - e) * .1; 158 if (name.startsWith('dove')) { 159 dy += Math.sin(t * .85 + i) * 24 + Math.sin(t * 7.8 + i * .65) * 2.5; 160 dx += Math.sin(t * .48 + i) * 46; 161 } 162 if (name.startsWith('star')) dy += Math.sin(t * .9 + i) * 8; 163 const rect = [ox + (x + dx - w * (z - 1) / 2) * scale, oy + (y + dy - h * (z - 1) / 2) * scale, w * z * scale, 164 h * z * scale 165 ]; 166 if (gl) { 167 gl.uniform4f(uniforms.rect, ...rect); 168 gl.uniform1f(uniforms.wave, reduced ? 0 : wave * scale); 169 gl.uniform1f(uniforms.phase, i * .65); 170 gl.uniform1f(uniforms.bird, !reduced && name.startsWith('dove') ? 1 : 0); 171 gl.uniform1f(uniforms.bank, Math.sin(t * .85 + i) * .07); 172 gl.uniform1f(uniforms.cloud, 0); 173 gl.uniform1f(uniforms.alpha, e); 174 gl.bindTexture(gl.TEXTURE_2D, l.tex); 175 gl.drawArrays(gl.TRIANGLES, 0, 100 * 28 * 6); 176 } else { 177 ctx.globalAlpha = e; 178 ctx.drawImage(l.img, ...rect); 179 ctx.globalAlpha = 1; 180 } 181 } 182 ctx.save(); 183 ctx.translate(ox, oy); 184 ctx.scale(scale, scale); 185 ctx.globalCompositeOperation = 'lighter'; 186 const opacity = ease(t / 2); 187 for (const p of sparks) { 188 const x = p.x * 2100 - 90 + Math.sin(t * .3 + p.p) * 35, 189 y = 990 - ((p.y * 800 + t * 32 * p.s) % 870); 190 const a = (.35 + .3 * Math.sin(t * 2 + p.p)) * Math.min(1, (990 - y) / 120) * Math.min(1, (y - 100) / 150) * 191 opacity; 192 ctx.fillStyle = `rgba(255,196,81,${Math.max(0,a)})`; 193 ctx.beginPath(); 194 ctx.arc(x, y, p.r, 0, Math.PI * 2); 195 ctx.fill(); 196 } 197 drawFireworks(t); 198 for (let k = 0; k < 3; k++) { 199 const progress = (t * .12 + k / 3) % 1, 200 x = -180 + progress * 2300, 201 y = 815 - 260 * Math.pow((x - 960) / 960, 2) + Math.sin(t + k) * 14; 202 const gradient = ctx.createRadialGradient(x, y, 0, x, y, 65); 203 gradient.addColorStop(0, 'rgba(255,248,215,.8)'); 204 gradient.addColorStop(.12, 'rgba(255,219,130,.35)'); 205 gradient.addColorStop(1, 'rgba(255,180,55,0)'); 206 ctx.fillStyle = gradient; 207 ctx.fillRect(x - 65, y - 65, 130, 130); 208 } 209 ctx.restore(); 210 frame = requestAnimationFrame(animate); 211 212} 213 214function close() { 215 overlay.classList.add('closed'); 216 replay.hidden = false; 217 document.body.style.overflow = ''; 218 document.documentElement.style.overflow = ''; 219 setTimeout(() => { 220 if (overlay.classList.contains('closed')) { 221 running = false; 222 cancelAnimationFrame(frame); 223 } 224 }, 850); 225 replay.focus({ 226 preventScroll: true 227 }); 228} 229closeButton.addEventListener('click', close); 230addEventListener('keydown', e => { 231 if (e.key === 'Escape' && !overlay.classList.contains('closed')) close(); 232}); 233replay.addEventListener('click', () => { 234 cancelAnimationFrame(frame); 235 overlay.classList.remove('closed'); 236 replay.hidden = true; 237 start = performance.now(); 238 running = true; 239 // éæéç½®èªå¨å ³éæ è®°ï¼éæåä¼å次计æ¶èªå¨å ³é 240 autoClosed = false; 241 // é使»å¨ 242 document.body.style.overflow = 'hidden'; 243 document.documentElement.style.overflow = 'hidden'; 244 if (ready) animate(start); 245 closeButton.focus({ 246 preventScroll: true 247 }); 248}); 249document.addEventListener('visibilitychange', () => { 250 if (document.hidden) cancelAnimationFrame(frame); 251 else if (running && ready) frame = requestAnimationFrame(animate); 252}); 253 254function drawFireworks(t) { 255 const shows = [ 256 [860, 390, 0, 1], 257 [1110, 320, 2.05, .85], 258 [620, 310, 4.2, .9], 259 [1350, 415, 6.4, .85], 260 [965, 240, 8.5, 1.08] 261 ]; 262 for (let k = 0; k < shows.length; k++) { 263 const [cx, cy, delay, size] = shows[k], age = ((t + delay) % 11.7) - 1.7; 264 if (age < 0 && age > -.95) { 265 const q = (age + .95) / .95, 266 y = cy + (1 - q) * 260; 267 const g = ctx.createLinearGradient(cx, y, cx, y + 70); 268 g.addColorStop(0, 'rgba(255,240,178,.95)'); 269 g.addColorStop(1, 'rgba(255,156,56,0)'); 270 ctx.strokeStyle = g; 271 ctx.lineWidth = 2; 272 ctx.beginPath(); 273 ctx.moveTo(cx, y); 274 ctx.lineTo(cx - 4, y + 65); 275 ctx.stroke(); 276 } 277 if (age < 0 || age > 3.7) continue; 278 const life = Math.pow(1 - age / 3.7, 1.15), 279 n = 96; 280 if (age < .25) { 281 const r = 16 + age * 100,
282 g = ctx.createRadialGradient(cx, cy, 0, cx, cy, r); 283 g.addColorStop(0, `rgba(255,250,219,${(1-age/.25)*.85})`); 284 g.addColorStop(1, 'rgba(255,184,73,0)'); 285 ctx.fillStyle = g; 286 ctx.fillRect(cx - r, cy - r, r * 2, r * 2); 287 } 288 for (let j = 0; j < n; j++) { 289 const angle = j * Math.PI * 2 / n + .08 * Math.sin(j * 15), 290 shell = j % 3 === 0 ? .62 : 1, 291 speed = (68 + 17 * Math.sin(j * 8.31 + k)) * size * shell; 292 const color = j % 9 === 0 ? '255,105,85' : j % 4 === 0 ? '255,244,210' : '255,194,74'; 293 const point = a => { 294 const travel = (1 - Math.exp(-a * .85)) / .85 * speed; 295 return [cx + Math.cos(angle) * travel, cy + Math.sin(angle) * travel + a * a * 16]; 296 }; 297 for (let trail = 5; trail >= 0; trail--) { 298 const a = Math.max(0, age - trail * .04), 299 b = Math.max(0, a - .05), 300 v = point(a), 301 u = point(b); 302 ctx.strokeStyle = `rgba(${color},${life*(1-trail/7)*.82})`; 303 ctx.lineWidth = trail === 0 ? 1.7 : 1.05; 304 ctx.beginPath(); 305 ctx.moveTo(...u); 306 ctx.lineTo(...v); 307 ctx.stroke(); 308 } 309 const v = point(age), 310 twinkle = .5 + .5 * Math.sin(age * 19 + j * 4.7); 311 ctx.fillStyle = `rgba(255,246,210,${life*twinkle})`; 312 ctx.beginPath(); 313 ctx.arc(v[0], v[1], 1.2, 0, Math.PI * 2); 314 ctx.fill(); 315 if (j % 8 === 0 && age > 1.05) { 316 const a = age - 1.05; 317 for (let z = 0; z < 4; z++) { 318 const an = z * Math.PI / 2 + j; 319 ctx.fillStyle = `rgba(255,216,136,${life*.65})`; 320 ctx.fillRect(v[0] + Math.cos(an) * a * 12, v[1] + Math.sin(an) * a * 12, 1.3, 1.3); 321 } 322 } 323 } 324 } 325}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.