PageSourceSearch

http://www.liuzhou.gov.cn/material/2026gqph/app.js

js liuzhou.gov.cn collected 2026-10-02 03:27:59 UTC 11,800 bytes, 325 lines download raw bytes

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.