add a subtle background animation
All checks were successful
Build and Publish Docker Image / build (push) Successful in 4m15s

This commit is contained in:
2026-06-17 20:27:17 -06:00
parent 4209d13ae1
commit 2e1225ea90
3 changed files with 164 additions and 0 deletions

153
assets/js/fireflies.js Normal file
View File

@@ -0,0 +1,153 @@
(function () {
'use strict'
var COUNT = 20
var MAX_RADIUS = 3
var PALETTE = [
[255, 255, 230],
[255, 245, 200],
[255, 230, 180],
[220, 255, 220],
[200, 230, 255],
]
var canvas, ctx, w, h, flies, id, running
function rand(a, b) { return a + Math.random() * (b - a) }
function init() {
canvas = document.createElement('canvas')
canvas.id = 'fireflies-canvas'
canvas.setAttribute('aria-hidden', 'true')
document.body.prepend(canvas)
ctx = canvas.getContext('2d')
resize()
spawn()
running = true
tick()
addEventListener('resize', onResize)
document.addEventListener('visibilitychange', onVisibility)
}
function resize() {
w = canvas.width = innerWidth
h = canvas.height = innerHeight
}
function onResize() {
resize()
if (!flies) return
for (var i = 0; i < flies.length; i++) {
var f = flies[i]
if (f.x > w) f.x = w * 0.9
if (f.y > h) f.y = h * 0.9
}
}
function spawn() {
flies = []
for (var i = 0; i < COUNT; i++) flies.push(makeFly())
}
function makeFly() {
var c = PALETTE[Math.floor(Math.random() * PALETTE.length)]
return {
x: rand(0, w),
y: rand(0, h),
vx: rand(-0.08, 0.08),
vy: rand(-0.08, 0.08),
r: rand(1.5, MAX_RADIUS),
cr: c[0], cg: c[1], cb: c[2],
a: rand(0.15, 0.35),
phase: rand(0, 6.2832),
flashSpeed: rand(0.002, 0.006),
wanderPhase: rand(0, 6.2832),
wanderSpeed: rand(0.002, 0.008),
driftX: rand(-0.002, 0.002),
driftY: rand(-0.002, 0.002),
}
}
function tick() {
if (!running) return
update()
draw()
id = requestAnimationFrame(tick)
}
function update() {
for (var i = 0; i < flies.length; i++) {
var f = flies[i]
f.wanderPhase += f.wanderSpeed
var angle = Math.sin(f.wanderPhase) * 1.5
f.vx += Math.cos(angle) * 0.004 + f.driftX
f.vy += Math.sin(angle) * 0.004 + f.driftY
f.vx *= 0.98
f.vy *= 0.98
var sp = Math.sqrt(f.vx * f.vx + f.vy * f.vy)
if (sp > 0.3) {
f.vx = (f.vx / sp) * 0.3
f.vy = (f.vy / sp) * 0.3
}
f.x += f.vx
f.y += f.vy
if (f.x < -40) f.x = w + 40
if (f.x > w + 40) f.x = -40
if (f.y < -40) f.y = h + 40
if (f.y > h + 40) f.y = -40
}
}
function draw() {
ctx.clearRect(0, 0, w, h)
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return
for (var i = 0; i < flies.length; i++) {
var f = flies[i]
f.phase += f.flashSpeed
var pulse = 0.5 + Math.sin(f.phase) * 0.5
var alpha = f.a * pulse
if (alpha < 0.01) continue
ctx.save()
var glowR = f.r * 4
var g = ctx.createRadialGradient(f.x, f.y, 0, f.x, f.y, glowR)
g.addColorStop(0, 'rgba(' + f.cr + ',' + f.cg + ',' + f.cb + ',' + alpha + ')')
g.addColorStop(0.15, 'rgba(' + f.cr + ',' + f.cg + ',' + f.cb + ',' + (alpha * 0.25) + ')')
g.addColorStop(1, 'rgba(' + f.cr + ',' + f.cg + ',' + f.cb + ',0)')
ctx.beginPath()
ctx.arc(f.x, f.y, glowR, 0, 6.2832)
ctx.fillStyle = g
ctx.fill()
var coreR = f.r * 1.2
var g2 = ctx.createRadialGradient(f.x, f.y, 0, f.x, f.y, coreR)
g2.addColorStop(0, 'rgba(' + f.cr + ',' + f.cg + ',' + f.cb + ',' + (alpha * 0.85) + ')')
g2.addColorStop(1, 'rgba(' + f.cr + ',' + f.cg + ',' + f.cb + ',0)')
ctx.beginPath()
ctx.arc(f.x, f.y, coreR, 0, 6.2832)
ctx.fillStyle = g2
ctx.fill()
ctx.restore()
}
}
function onVisibility() {
if (document.hidden) {
running = false
if (id) { cancelAnimationFrame(id); id = null }
} else {
running = true
if (!id) tick()
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init)
} else {
init()
}
})()