Snapshot: full project state

This commit is contained in:
2026-10-06 23:43:23 -07:00
commit ed212cdbd9
21 changed files with 2090 additions and 0 deletions

303
static/cosmic-bg.js Normal file
View File

@@ -0,0 +1,303 @@
/* cosmic-bg.js — self-contained animated backgrounds for the fleet.
* Drop in with: <script src="cosmic-bg.js" data-theme="blackhole"></script>
* Themes: starfield, cosmos, blackhole, geometric, nebula, particles, waves, grid
* Config (data attrs): data-theme, data-density (low/med/high), data-speed (0.5-2)
* All motion is slow + smooth by design. Visibility tuned to be clearly visible.
*/
(function () {
"use strict";
var s = document.currentScript;
var theme = (s && s.dataset.theme) || "cosmos";
var density = (s && s.dataset.density) || "med";
var speed = parseFloat((s && s.dataset.speed) || "1");
var D = density === "low" ? 0.5 : density === "high" ? 1.7 : 1;
var c = document.createElement("canvas");
c.style.cssText = "position:fixed;inset:0;z-index:0;pointer-events:none;display:block";
function mount() {
if (c.parentNode) return;
(document.body || document.documentElement).appendChild(c);
var st = document.createElement("style");
st.textContent = "body > *:not(canvas){position:relative;z-index:1}";
(document.head || document.documentElement).appendChild(st);
}
if (document.body) mount();
else document.addEventListener("DOMContentLoaded", mount);
var ctx = c.getContext("2d");
var W, H, DPR = Math.min(window.devicePixelRatio || 1, 2);
function resize() {
W = window.innerWidth; H = window.innerHeight;
c.width = W * DPR; c.height = H * DPR;
c.style.width = W + "px"; c.style.height = H + "px";
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
}
resize();
window.addEventListener("resize", resize);
function rnd(a, b) { return a + Math.random() * (b - a); }
function pick(arr) { return arr[Math.floor(Math.random() * arr.length)]; }
var PALETTES = {
cosmic: ["#a06bff", "#5b8cff", "#ff5ab5", "#e8f0ff"],
warm: ["#ff8a8a", "#ffd93d", "#ffb06a", "#fff"],
cool: ["#4fd8ff", "#6fa8ff", "#bff3f2", "#fff"],
green: ["#9fffd0", "#4de68a", "#c6ff7a", "#fff"],
};
function hexA(hex, a) {
var r = parseInt(hex.slice(1, 3), 16), g = parseInt(hex.slice(3, 5), 16), b = parseInt(hex.slice(5, 7), 16);
return "rgba(" + r + "," + g + "," + b + "," + Math.max(0, Math.min(1, a)).toFixed(3) + ")";
}
function starField(pal, drift) {
var stars = [];
var n = Math.floor(240 * D);
for (var i = 0; i < n; i++) stars.push({
x: Math.random() * W, y: Math.random() * H,
r: rnd(0.4, 2.1), tw: rnd(0, 6.28), tws: rnd(0.3, 1.2),
dr: drift * rnd(0.3, 1), c: pick(pal)
});
return function (dt, t) {
for (var j = 0; j < stars.length; j++) {
var st = stars[j];
st.y += st.dr; st.x += st.dr * 0.12;
if (st.y > H + 4) { st.y = -4; st.x = Math.random() * W; }
var a = (0.6 + 0.4 * Math.sin(t * st.tws + st.tw)) * (0.5 + 0.5 * st.r / 2.1);
ctx.globalAlpha = a; ctx.fillStyle = st.c;
ctx.beginPath(); ctx.arc(st.x, st.y, st.r, 0, 6.28); ctx.fill();
}
ctx.globalAlpha = 1;
};
}
function nebulaBlobs(count, pal) {
var blobs = [];
for (var i = 0; i < count; i++) blobs.push({
x: Math.random() * W, y: Math.random() * H,
r: rnd(140, 360), dx: rnd(-0.3, 0.3), dy: rnd(-0.2, 0.2),
c: pick(pal), ph: rnd(0, 6.28)
});
return function (dt, t) {
for (var j = 0; j < blobs.length; j++) {
var b = blobs[j];
b.x += b.dx; b.y += b.dy;
if (b.x < -b.r) b.x = W + b.r; if (b.x > W + b.r) b.x = -b.r;
if (b.y < -b.r) b.y = H + b.r; if (b.y > H + b.r) b.y = -b.r;
var g = ctx.createRadialGradient(b.x, b.y, 0, b.x, b.y, b.r);
var pulse = 0.16 + 0.06 * Math.sin(t * 0.5 + b.ph);
g.addColorStop(0, hexA(b.c, pulse));
g.addColorStop(1, hexA(b.c, 0));
ctx.fillStyle = g;
ctx.fillRect(b.x - b.r, b.y - b.r, b.r * 2, b.r * 2);
}
};
}
var themes = {};
themes.starfield = function () {
var draw = starField(PALETTES.cosmic, 0.45);
return function (dt, t) { draw(dt, t); };
};
themes.cosmos = function () {
var stars = starField(PALETTES.cosmic, 0.35);
var neb = nebulaBlobs(Math.floor(4 * D), PALETTES.cosmic);
var arms = 3, particles = [];
for (var i = 0; i < Math.floor(360 * D); i++) {
var arm = Math.floor(Math.random() * arms);
var rr = Math.pow(Math.random(), 0.6) * Math.min(W, H) * 0.44;
var aa = (arm / arms) * 6.28 + rr * 0.02 + rnd(-0.2, 0.2);
particles.push({ rr: rr, aa: aa, r: rnd(0.5, 1.8), c: pick(PALETTES.cosmic) });
}
return function (dt, t) {
neb(dt, t);
var cx = W * 0.5, cy = H * 0.5;
for (var j = 0; j < particles.length; j++) {
var p = particles[j];
p.aa += 0.0016 * speed;
var x = cx + Math.cos(p.aa) * p.rr, y = cy + Math.sin(p.aa) * p.rr;
ctx.globalAlpha = 0.75; ctx.fillStyle = p.c;
ctx.beginPath(); ctx.arc(x, y, p.r, 0, 6.28); ctx.fill();
}
ctx.globalAlpha = 1;
stars(dt, t);
};
};
themes.blackhole = function () {
var cx = W * 0.5, cy = H * 0.5;
var bhR = Math.min(W, H) * 0.14;
var acc = [];
for (var i = 0; i < Math.floor(560 * D); i++) {
var ang = rnd(0, 6.28), rad = rnd(bhR * 1.15, Math.min(W, H) * 0.5);
acc.push({ ang: ang, rad: rad, speed: 0.006 + 0.016 * (bhR / rad), r: rnd(0.6, 2.2),
c: pick(["#ffc06a", "#ff8a6a", "#ffd93d", "#ff5ab5", "#fff"]) });
}
return function (dt, t) {
// broad accretion glow
var glow = ctx.createRadialGradient(cx, cy, bhR * 0.7, cx, cy, bhR * 3.6);
glow.addColorStop(0, "rgba(255,150,60,0.5)");
glow.addColorStop(0.45, "rgba(255,45,149,0.22)");
glow.addColorStop(1, "rgba(0,0,0,0)");
ctx.fillStyle = glow; ctx.fillRect(0, 0, W, H);
// particles spiralling in
for (var j = 0; j < acc.length; j++) {
var p = acc[j];
p.ang += p.speed * speed; p.rad -= 0.05 * speed;
if (p.rad < bhR * 1.02) { p.rad = rnd(bhR * 1.5, Math.min(W, H) * 0.5); p.ang = rnd(0, 6.28); }
var x = cx + Math.cos(p.ang) * p.rad, y = cy + Math.sin(p.ang) * p.rad;
var stretch = Math.min(1, (p.rad - bhR) / (bhR * 1.6));
ctx.globalAlpha = 0.5 + 0.5 * stretch;
ctx.fillStyle = p.c;
ctx.beginPath(); ctx.arc(x, y, p.r * (0.5 + stretch), 0, 6.28); ctx.fill();
}
ctx.globalAlpha = 1;
// event horizon + bright photon ring
ctx.fillStyle = "#000"; ctx.beginPath(); ctx.arc(cx, cy, bhR, 0, 6.28); ctx.fill();
var ring = ctx.createRadialGradient(cx, cy, bhR, cx, cy, bhR + 10);
ring.addColorStop(0, "rgba(255,220,150,0.95)"); ring.addColorStop(1, "rgba(255,220,150,0)");
ctx.strokeStyle = "rgba(255,220,150,0.95)"; ctx.lineWidth = 2.5;
ctx.beginPath(); ctx.arc(cx, cy, bhR + 1, 0, 6.28); ctx.stroke();
};
};
themes.geometric = function () {
var polys = [];
var n = Math.floor(22 * D);
for (var i = 0; i < n; i++) polys.push({
x: Math.random() * W, y: Math.random() * H,
sides: pick([3, 4, 6]), size: rnd(22, 60),
rot: rnd(0, 6.28), vr: rnd(-0.0025, 0.0025),
dx: rnd(-0.28, 0.28), dy: rnd(-0.22, 0.22),
c: pick(PALETTES.cosmic)
});
function poly(x, y, r, sides, rot) {
ctx.beginPath();
for (var k = 0; k < sides; k++) {
var a = rot + (k / sides) * 6.28;
var px = x + Math.cos(a) * r, py = y + Math.sin(a) * r;
k === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
}
ctx.closePath();
}
return function (dt, t) {
for (var j = 0; j < polys.length; j++) {
var p = polys[j];
p.x += p.dx; p.y += p.dy; p.rot += p.vr;
if (p.x < -70) p.x = W + 70; if (p.x > W + 70) p.x = -70;
if (p.y < -70) p.y = H + 70; if (p.y > H + 70) p.y = -70;
ctx.strokeStyle = hexA(p.c, 0.65); ctx.lineWidth = 1.4;
poly(p.x, p.y, p.size, p.sides, p.rot); ctx.stroke();
ctx.fillStyle = hexA(p.c, 0.12); ctx.fill();
}
for (var a = 0; a < polys.length; a++) {
for (var b = a + 1; b < polys.length; b++) {
var dx = polys[a].x - polys[b].x, dy = polys[a].y - polys[b].y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < 170) {
ctx.strokeStyle = "rgba(190,200,255," + (0.22 * (1 - d / 170)).toFixed(3) + ")";
ctx.lineWidth = 0.8;
ctx.beginPath(); ctx.moveTo(polys[a].x, polys[a].y); ctx.lineTo(polys[b].x, polys[b].y); ctx.stroke();
}
}
}
};
};
themes.nebula = function () {
var neb = nebulaBlobs(Math.floor(9 * D), PALETTES.cosmic.concat(PALETTES.cool));
var stars = starField(PALETTES.cosmic, 0.3);
return function (dt, t) { neb(dt, t); stars(dt, t); };
};
themes.particles = function () {
var ps = [];
var n = Math.floor(130 * D);
for (var i = 0; i < n; i++) ps.push({
x: Math.random() * W, y: Math.random() * H,
vx: rnd(-0.35, 0.35), vy: rnd(-0.35, 0.35), r: rnd(1.2, 3), c: pick(PALETTES.cosmic)
});
return function (dt, t) {
for (var j = 0; j < ps.length; j++) {
var p = ps[j];
p.x += p.vx; p.y += p.vy;
if (p.x < 0 || p.x > W) p.vx *= -1;
if (p.y < 0 || p.y > H) p.vy *= -1;
ctx.fillStyle = p.c; ctx.globalAlpha = 0.95;
ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, 6.28); ctx.fill();
}
ctx.globalAlpha = 1;
for (var a = 0; a < ps.length; a++) {
for (var b = a + 1; b < ps.length; b++) {
var dx = ps[a].x - ps[b].x, dy = ps[a].y - ps[b].y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < 150) {
ctx.strokeStyle = "rgba(190,200,255," + (0.25 * (1 - d / 150)).toFixed(3) + ")";
ctx.lineWidth = 0.8;
ctx.beginPath(); ctx.moveTo(ps[a].x, ps[a].y); ctx.lineTo(ps[b].x, ps[b].y); ctx.stroke();
}
}
}
};
};
themes.waves = function () {
var layers = [
{ amp: 46, len: 260, speed: 0.010, y: 0.65, c: "rgba(160,80,255,0.30)" },
{ amp: 60, len: 320, speed: 0.013, y: 0.72, c: "rgba(70,120,255,0.26)" },
{ amp: 76, len: 400, speed: 0.016, y: 0.80, c: "rgba(255,70,170,0.22)" },
];
return function (dt, t) {
for (var j = 0; j < layers.length; j++) {
var L = layers[j];
ctx.fillStyle = L.c; ctx.beginPath(); ctx.moveTo(0, H);
for (var x = 0; x <= W; x += 8) {
var y = H * L.y + Math.sin(x / L.len + t * L.speed * speed) * L.amp;
ctx.lineTo(x, y);
}
ctx.lineTo(W, H); ctx.closePath(); ctx.fill();
}
};
};
themes.grid = function () {
var horizon = H * 0.55;
return function (dt, t) {
var off = (t * 30 * speed) % 40;
ctx.strokeStyle = "rgba(150,80,255,0.45)"; ctx.lineWidth = 1.2;
for (var i = -12; i <= 12; i++) {
var x0 = W / 2 + i * 55;
ctx.beginPath(); ctx.moveTo(W / 2, horizon); ctx.lineTo(x0, H); ctx.stroke();
}
for (var j = 0; j < 16; j++) {
var yy = horizon + Math.pow((j + off / 40) % 16 / 16, 2) * (H - horizon);
ctx.strokeStyle = "rgba(255,70,170," + (0.10 + 0.35 * ((yy - horizon) / (H - horizon))).toFixed(3) + ")";
ctx.beginPath(); ctx.moveTo(0, yy); ctx.lineTo(W, yy); ctx.stroke();
}
var g = ctx.createRadialGradient(W / 2, horizon, 0, W / 2, horizon, 170);
g.addColorStop(0, "rgba(255,70,170,0.5)"); g.addColorStop(1, "rgba(0,0,0,0)");
ctx.fillStyle = g; ctx.fillRect(W / 2 - 170, horizon - 170, 340, 340);
};
};
var active = themes[theme] ? themes[theme]() : themes.cosmos();
var last = performance.now();
var raf;
function loop(now) {
var dt = (now - last) / 1000; last = now;
ctx.clearRect(0, 0, W, H);
active(dt, now / 1000);
raf = requestAnimationFrame(loop);
}
raf = requestAnimationFrame(loop);
document.addEventListener("visibilitychange", function () {
if (document.hidden) { cancelAnimationFrame(raf); }
else { last = performance.now(); raf = requestAnimationFrame(loop); }
});
})();