{
const options = {
width: 960,
height: 500,
dpi: devicePixelRatio,
offsetX: .3,
offsetY: .4,
maxRadius: 200,
lineWidth: 2,
edges: 3,
lines: 15,
rotationSpeed: -.002,
shiftSpeed: .0035,
hueShift: .1,
startFrame: 200,
feedbackOpacity: 1,
feedbackScale: 1.03,
fadeOpacity: .0,
};
const TAU = Math.PI * 2;
const w = options.width, h = options.height;
const cx = w * options.offsetX;
const cy = h * options.offsetY;
const ctx = DOM.context2d(w, h, options.dpi);
ctx.canvas.style.maxWidth = '100%';
ctx.lineWidth = options.lineWidth;
ctx.lineJoin = 'round';
fill('#000');
const aStep = TAU / options.edges;
const iStep = 1 / (options.lines - 1);
const rotStep = options.rotationSpeed / (options.lines - 1);
let frame = options.startFrame;
while(true) {
copy(options.feedbackScale, options.feedbackOpacity);
fill(`rgba(0,0,0,${options.fadeOpacity})`);
const hue = (options.hueShift * frame) % 360;
const aGlobal = frame * options.rotationSpeed * Math.PI * 2;
for(let i = 1; i <= options.lines; i++) {
const aLocal = Math.PI * 2 * i * iStep * options.shiftSpeed * frame;
const r = options.maxRadius * ((i * iStep) ** 1.5);
ctx.beginPath();
for(let i = 0; i < options.edges; i++) {
const a = i * aStep + aLocal + aGlobal;
const x = cx + Math.cos(a) * r;
const y = cy + Math.sin(a) * r;
i ? ctx.lineTo(x, y) : ctx.moveTo(x, y);
}
ctx.closePath();
ctx.strokeStyle = `hsl(${hue},10%,${i / options.lines * 75 + 25}%)`;
ctx.stroke();
}
yield ctx.canvas;
frame++;
}
function fill(color) {
ctx.save();
ctx.fillStyle = color;
ctx.fillRect(0, 0, w, h);
ctx.restore();
}
function copy(scale, alpha = 1) {
const x = cx * options.dpi, y = cy * options.dpi;
ctx.save();
ctx.resetTransform();
ctx.translate(x, y);
ctx.scale(scale, scale);
ctx.translate(-x, -y);
ctx.globalAlpha = alpha;
ctx.drawImage(ctx.canvas, 0, 0);
ctx.restore();
}
}