92 lines
2.6 KiB
JavaScript
Executable File
92 lines
2.6 KiB
JavaScript
Executable File
fetch('./ddate-now')
|
|
.then(r => r.text())
|
|
.then(d => { document.getElementById('ddate').textContent = d.trim(); })
|
|
.catch(() => { document.getElementById('ddate').textContent = ''; });
|
|
|
|
const canvas = document.getElementById('warp');
|
|
const ctx = canvas.getContext('2d');
|
|
let w, h, cx, cy, stars = [], animId = null, active = false;
|
|
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
const isMobile = window.innerWidth < 768 || window.innerHeight < 600 || /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
const starCount = isMobile ? 200 : 400;
|
|
const speed = prefersReduced ? 0.01 : 0.02;
|
|
|
|
function resize() {
|
|
w = canvas.width = window.innerWidth;
|
|
h = canvas.height = window.innerHeight;
|
|
cx = w / 2;
|
|
cy = h / 2;
|
|
}
|
|
|
|
class Star {
|
|
constructor() { this.reset(); }
|
|
reset() {
|
|
const angle = Math.random() * Math.PI * 2;
|
|
const radius = Math.random() * Math.max(w, h);
|
|
this.x = Math.cos(angle) * radius;
|
|
this.y = Math.sin(angle) * radius;
|
|
this.z = Math.random() * w;
|
|
this.pz = this.z;
|
|
}
|
|
update() {
|
|
this.pz = this.z;
|
|
this.z -= speed * this.z;
|
|
if (this.z < 1) this.reset();
|
|
}
|
|
draw() {
|
|
const sz = 1 / this.z, spz = 1 / this.pz;
|
|
const sx = this.x * sz * w + cx, sy = this.y * sz * h + cy;
|
|
const px = this.x * spz * w + cx, py = this.y * spz * h + cy;
|
|
const r = Math.max(0, (1 - this.z / w) * 2);
|
|
ctx.beginPath();
|
|
ctx.strokeStyle = `rgba(255, 255, 255, ${r})`;
|
|
ctx.lineWidth = r * 2;
|
|
ctx.moveTo(px, py);
|
|
ctx.lineTo(sx, sy);
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
function init() {
|
|
resize();
|
|
stars = [];
|
|
for (let i = 0; i < starCount; i++) stars.push(new Star());
|
|
}
|
|
|
|
function animate() {
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
|
|
ctx.fillRect(0, 0, w, h);
|
|
for (let i = 0; i < stars.length; i++) {
|
|
stars[i].update();
|
|
stars[i].draw();
|
|
}
|
|
animId = requestAnimationFrame(animate);
|
|
}
|
|
|
|
function startWarp() {
|
|
if (active) return;
|
|
active = true;
|
|
canvas.classList.add('active');
|
|
init();
|
|
animate();
|
|
}
|
|
|
|
function stopWarp() {
|
|
if (!active) return;
|
|
active = false;
|
|
canvas.classList.remove('active');
|
|
if (animId) cancelAnimationFrame(animId);
|
|
}
|
|
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('warp-trigger')) {
|
|
e.preventDefault();
|
|
active ? stopWarp() : startWarp();
|
|
}
|
|
});
|
|
|
|
window.addEventListener('resize', () => {
|
|
if (active) resize();
|
|
});
|
|
|