From 2e1225ea9034327432d68ed0504d4421dce02956 Mon Sep 17 00:00:00 2001 From: taco Date: Wed, 17 Jun 2026 20:27:17 -0600 Subject: [PATCH] add a subtle background animation --- assets/css/custom.css | 8 ++ assets/js/fireflies.js | 153 ++++++++++++++++++++++++++++++ layouts/partials/extend-head.html | 3 + 3 files changed, 164 insertions(+) create mode 100644 assets/css/custom.css create mode 100644 assets/js/fireflies.js create mode 100644 layouts/partials/extend-head.html diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..a98aabd --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,8 @@ +#fireflies-canvas { + position: fixed; + inset: 0; + z-index: -1; + width: 100%; + height: 100%; + pointer-events: none; +} diff --git a/assets/js/fireflies.js b/assets/js/fireflies.js new file mode 100644 index 0000000..5275fbc --- /dev/null +++ b/assets/js/fireflies.js @@ -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() + } +})() diff --git a/layouts/partials/extend-head.html b/layouts/partials/extend-head.html new file mode 100644 index 0000000..1f969cb --- /dev/null +++ b/layouts/partials/extend-head.html @@ -0,0 +1,3 @@ +{{ with resources.Get "js/fireflies.js" | resources.Minify | resources.Fingerprint }} + +{{ end }}