.lane-label{position:absolute;left:6%;font:700 clamp(10px,2.8vmin,13px)/1 ui-monospace,monospace;color:var(--muted);z-index:2}
.l1{top:10%}
.l2{top:50%}
#ac{position:absolute;inset:0;width:100%;height:100%;display:block}
const canvas = document.getElementById('ac');
const ctx = canvas.getContext('2d');
const cs = getComputedStyle(document.documentElement);
function col(name, fb) { const v = cs.getPropertyValue(name).trim(); return v || fb; }
const LINE = col('--line', '#e2e0d8');
const ACCENT = col('--accent', '#5b5bf7');
const ACCENT2 = col('--accent-2', '#f25c8a');
let w = 0, h = 0;
function resize() {
const dpr = Math.min(devicePixelRatio || 1, 2);
w = innerWidth; h = innerHeight;
canvas.width = w * dpr; canvas.height = h * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
addEventListener('resize', resize);
resize();
function ball(x, y, r, color, alpha) {
ctx.globalAlpha = alpha === undefined ? 1 : alpha;
ctx.beginPath(); ctx.arc(x, y, Math.max(r, 1), 0, Math.PI * 2);
ctx.fillStyle = color; ctx.fill(); ctx.globalAlpha = 1;
}
const DUR = 2400;
const startT = performance.now();
function loop(now) {
const t = ((now - startT) % DUR) / DUR;
ctx.clearRect(0, 0, w, h);
const x0 = w * 0.12, x1 = w * 0.88;
const y1 = h * 0.32, y2 = h * 0.78, apex = h * 0.36;
const r = h * 0.055;
for (let k = 6; k >= 1; k--) {
const tt = ((t - k * 0.03) + 1) % 1;
ball(x0 + (x1 - x0) * tt, y1, r * 0.75, ACCENT, 0.07 * (7 - k));
}
ball(x0 + (x1 - x0) * t, y1, r, ACCENT);
ctx.strokeStyle = LINE; ctx.lineWidth = 1;
ctx.setLineDash([2, 4]);
ctx.beginPath(); ctx.moveTo(x0, y2); ctx.lineTo(x1, y2); ctx.stroke();
ctx.setLineDash([]);
ctx.beginPath();
for (let i = 0; i <= 30; i++) {
const tt = i / 30;
const px = x0 + (x1 - x0) * tt;
const py = y2 - Math.sin(tt * Math.PI) * (y2 - apex);
if (i === 0) ctx.moveTo(px, py); else ctx.lineTo(px, py);
}
ctx.stroke();
for (let k = 6; k >= 1; k--) {
const tt = ((t - k * 0.03) + 1) % 1;
const px = x0 + (x1 - x0) * tt;
const py = y2 - Math.sin(tt * Math.PI) * (y2 - apex);
ball(px, py, r * 0.75, ACCENT2, 0.07 * (7 - k));
}
const px = x0 + (x1 - x0) * t;
const py = y2 - Math.sin(t * Math.PI) * (y2 - apex);
ball(px, py, r, ACCENT2);
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);