From 797b0cce291e4052af61025e7a49f0e7bbee91da Mon Sep 17 00:00:00 2001
From: Lewis
Date: Tue, 30 Jun 2026 17:11:08 +0300
Subject: [PATCH] appview/pages: punchcard easter eggs
Lewis: May this revision serve well!
---
.gitignore | 1 +
appview/pages/pages.go | 1 +
appview/pages/static/profile-fx/orrery.js | 404 ++++++++++++++++++
.../pages/templates/layouts/profilebase.html | 5 +-
appview/state/profile.go | 7 +-
5 files changed, 416 insertions(+), 2 deletions(-)
create mode 100644 appview/pages/static/profile-fx/orrery.js
diff --git a/.gitignore b/.gitignore
index 3b80065c..3bc4f6b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ spindle/spindle-microvm-run
result
!.gitkeep
!appview/pages/static/topbar-search.js
+!appview/pages/static/profile-fx/
out/
node_modules/
patches
diff --git a/appview/pages/pages.go b/appview/pages/pages.go
index c6df6954..8fa6ad95 100644
--- a/appview/pages/pages.go
+++ b/appview/pages/pages.go
@@ -681,6 +681,7 @@ type ProfileCard struct {
Profile *models.Profile
Stats ProfileStats
Active string
+ ProfileScript string
}
type ProfileStats struct {
diff --git a/appview/pages/static/profile-fx/orrery.js b/appview/pages/static/profile-fx/orrery.js
new file mode 100644
index 00000000..41837549
--- /dev/null
+++ b/appview/pages/static/profile-fx/orrery.js
@@ -0,0 +1,404 @@
+const P = [
+ { r: 0.22, hue: 350, phase: 0.0 },
+ { r: 0.37, hue: 25, phase: 1.4 },
+ { r: 0.53, hue: 140, phase: 2.8 },
+ { r: 0.7, hue: 205, phase: 4.1 },
+ { r: 0.88, hue: 280, phase: 5.4 },
+];
+
+const H = [320, 50, 170, 230, 95, 285];
+
+const G = 0.15;
+const m = 0.0016;
+const mn = 0.002;
+const mx = 0.08;
+const M = 0.03;
+const e = 0.006;
+const S = 0.92;
+const s = 1.1;
+const h = 1 / 120;
+const X = 0.1;
+const b = 0.08;
+const k = 0.6;
+const z = 0.25;
+const d = 0.8;
+const fd = 0.92;
+const l = 0.12;
+const F = 0.05;
+const E = 0.12;
+const Q = 1.8;
+const R = 1.7;
+const T = 1.4;
+const K = 2.0;
+const L = 0.16;
+const V = 1.5;
+const B = 12;
+const W = [-2, -1, 0, 1, 2].flatMap((ox) => [-2, -1, 0, 1, 2].map((oy) => [ox, oy]));
+
+const cn = 28;
+const cw = 14;
+const wq = "(min-width: 768px)";
+const dq = "(prefers-color-scheme: dark)";
+const rq = "(prefers-reduced-motion: reduce)";
+const q = 64;
+const hz = 60;
+
+const c = (f) => Math.max(0, Math.min(1, f));
+const r = (a, b) => Array.from({ length: b - a + 1 }, (f, i) => a + i);
+const C = (x, y, f) => {
+ const m = Math.hypot(x, y);
+ return m > f ? { x: (x / m) * f, y: (y / m) * f } : { x, y };
+};
+const v = (r) => Math.sqrt((G * r * r) / Math.pow(r * r + e, 1.5));
+
+const p = (b, tx, ty, gm) => {
+ const dx = tx - b.x;
+ const dy = ty - b.y;
+ const r2 = dx * dx + dy * dy + e;
+ const inv = gm / (r2 * Math.sqrt(r2));
+ return [dx * inv, dy * inv];
+};
+
+const U = (b, all, k) =>
+ all.reduce(
+ (acc, o, j) => {
+ if (j === k) return acc;
+ const f = p(b, o.x, o.y, o.mass);
+ return [acc[0] + f[0], acc[1] + f[1]];
+ },
+ [0, 0],
+ );
+
+const a = (b, i, all, n, u) => {
+ const s = p(b, n.x, n.y, G);
+ const m = u.mass > 0 ? p(b, u.x, u.y, u.mass) : [0, 0];
+ const f = U(b, all, i);
+ return [s[0] + m[0] + f[0], s[1] + m[1] + f[1]];
+};
+
+const A = (n, all) => {
+ const g = U(n, all, -1);
+ return [b * g[0] - k * n.x - z * n.vx, b * g[1] - k * n.y - z * n.vy];
+};
+
+const I = (sim, u, dt) => {
+ const n = sim.sun;
+ const o = sim.bodies.map((b, i) => {
+ const f = a(b, i, sim.bodies, n, u);
+ const vx = b.vx + f[0] * dt;
+ const vy = b.vy + f[1] * dt;
+ return { ...b, vx, vy, x: b.x + vx * dt, y: b.y + vy * dt };
+ });
+ const j = A(n, sim.bodies);
+ const vx = n.vx + j[0] * dt;
+ const vy = n.vy + j[1] * dt;
+ return { bodies: o, sun: { x: n.x + vx * dt, y: n.y + vy * dt, vx, vy } };
+};
+
+const O = (sim, u, t) => (t >= h ? O(I(sim, u, h), u, t - h) : { sim, rem: t });
+
+const y = (b, n) => {
+ const f = Math.hypot(b.x - n.x, b.y - n.y);
+ if (f >= E && Math.hypot(b.x, b.y) <= Q) return { keep: b, ate: 0 };
+ return { keep: null, ate: f < E ? 1 : 0 };
+};
+
+const D = () =>
+ P.map((p) => {
+ const f = v(p.r);
+ return {
+ x: p.r * Math.cos(p.phase),
+ y: p.r * Math.sin(p.phase),
+ vx: -Math.sin(p.phase) * f,
+ vy: Math.cos(p.phase) * f,
+ hue: p.hue,
+ mass: m,
+ size: 1,
+ };
+ });
+
+const w = (arr) => (arr.length <= B ? arr : arr.slice(arr.length - B));
+
+const o = (N, j) => {
+ const f = Array.from(N.children, (c) => c.firstElementChild).filter(Boolean);
+ f.forEach((el) => {
+ el.style.transition = "none";
+ el.style.transformOrigin = "center";
+ el.style.willChange = "background-color, transform";
+ });
+ return { cells: f, cols: j, rows: Math.max(1, Math.ceil(f.length / j)) };
+};
+
+const x = (el) => {
+ el.style.backgroundColor = "";
+ el.style.transform = "";
+ el.style.border = "";
+};
+
+const g = (N) => {
+ const ac = new AbortController();
+ const op = { signal: ac.signal };
+ const mq = matchMedia(wq);
+ const dm = matchMedia(dq);
+ const rd = matchMedia(rq).matches;
+ const cl = () => (mq.matches ? cw : cn);
+
+ N.style.userSelect = "none";
+ N.style.WebkitUserSelect = "none";
+ N.style.cursor = "crosshair";
+
+ let v = o(N, cl());
+ let dk = dm.matches;
+ let bs = dk ? "rgb(55 65 81)" : "rgb(229 231 235)";
+ let dl = dk ? 60 : 45;
+
+ const al = (i) => ({ energy: new Float32Array(i), hue: new Float32Array(i) });
+ let f = al(v.cells.length);
+ let G = al(v.cells.length);
+ let dr = new Float32Array(v.cells.length).fill(-1);
+ let dh = new Float32Array(v.cells.length);
+
+ let sim = { bodies: D(), sun: { x: 0, y: 0, vx: 0, vy: 0 } };
+ let u = { x: 0, y: 0, over: false, mass: 0 };
+ let pd = null;
+ let vel = { x: 0, y: 0 };
+ let lm = 0;
+ let cu = 0;
+ let hp = 0;
+ let fl = 0;
+ let acc = 0;
+ let lt = 0;
+ let raf = 0;
+ let vs = true;
+
+ const rl = () => {
+ v = o(N, cl());
+ v.cells.forEach(x);
+ f = al(v.cells.length);
+ G = al(v.cells.length);
+ dr = new Float32Array(v.cells.length).fill(-1);
+ dh = new Float32Array(v.cells.length);
+ };
+
+ const os = () => {
+ dk = dm.matches;
+ bs = dk ? "rgb(55 65 81)" : "rgb(229 231 235)";
+ dl = dk ? 60 : 45;
+ dr.fill(-1);
+ };
+
+ mq.addEventListener("change", rl, op);
+ dm.addEventListener("change", os, op);
+
+ const ts = (a, e) => {
+ const cx = (v.cols - 1) / 2;
+ const cy = (v.rows - 1) / 2;
+ if (cx === 0 || cy === 0) return { x: 0, y: 0 };
+ const rc = N.getBoundingClientRect();
+ const p = ((a - rc.left) / rc.width) * v.cols - 0.5;
+ const q = ((e - rc.top) / rc.height) * v.rows - 0.5;
+ return { x: (p - cx) / (S * cx), y: (q - cy) / (S * cy) };
+ };
+
+ const mv = (E) => {
+ const tt = E.timeStamp / 1000;
+ const s = ts(E.clientX, E.clientY);
+ const dtm = tt - lm;
+ if (dtm > 0 && dtm < 0.1) {
+ vel = {
+ x: vel.x * 0.5 + ((s.x - u.x) / dtm) * 0.5,
+ y: vel.y * 0.5 + ((s.y - u.y) / dtm) * 0.5,
+ };
+ }
+ lm = tt;
+ u = { ...u, x: s.x, y: s.y, over: true };
+ };
+
+ const dn = (E) => {
+ if (E.pointerType === "touch") return;
+ E.preventDefault();
+ N.setPointerCapture(E.pointerId);
+ mv(E);
+ vel = { x: 0, y: 0 };
+ pd = { start: E.timeStamp / 1000, hue: H[hp % H.length] };
+ hp += 1;
+ };
+
+ const up = (E) => {
+ if (!pd) return;
+ if (N.hasPointerCapture(E.pointerId)) N.releasePointerCapture(E.pointerId);
+ const tt = E.timeStamp / 1000;
+ const c2 = c((tt - pd.start) / T);
+ const fs = tt - lm < 0.09;
+ const lc = C(fs ? vel.x * L : 0, fs ? vel.y * L : 0, V);
+ sim = {
+ ...sim,
+ bodies: w([
+ ...sim.bodies,
+ {
+ x: u.x,
+ y: u.y,
+ vx: lc.x,
+ vy: lc.y,
+ hue: pd.hue,
+ mass: mn + c2 * (mx - mn),
+ size: 0.8 + c2 * 2.2,
+ },
+ ]),
+ };
+ cu = tt + K;
+ pd = null;
+ };
+
+ N.addEventListener("pointermove", mv, { passive: true, signal: ac.signal });
+ N.addEventListener("pointerdown", dn, op);
+ N.addEventListener("pointerup", up, op);
+ N.addEventListener(
+ "pointerleave",
+ () => {
+ if (!pd) u = { ...u, over: false };
+ },
+ op,
+ );
+
+ const add = (buf, x, y, hue, e) => {
+ if (x < 0 || x >= v.cols || y < 0 || y >= v.rows) return;
+ const i = y * v.cols + x;
+ if (i >= 0 && i < buf.energy.length && e > buf.energy[i]) {
+ buf.energy[i] = e;
+ buf.hue[i] = hue;
+ }
+ };
+
+ const bl = (buf, fx, fy, hue, e, rad) => {
+ const bx = Math.round(fx);
+ const by = Math.round(fy);
+ const sp = r(-Math.ceil(rad), Math.ceil(rad));
+ sp.forEach((ox) =>
+ sp.forEach((oy) => {
+ const px = bx + ox;
+ const py = by + oy;
+ const dt = Math.hypot(px - fx, py - fy);
+ if (dt > rad) return;
+ add(buf, px, py, hue, e * (1 - dt / rad));
+ }),
+ );
+ };
+
+ const fm = (ms) => {
+ const t = ms / 1000;
+ const re = lt ? Math.min(X, (ms - lt) / 1000) : 0;
+ lt = ms;
+ const k = re * hz;
+ const dK = Math.pow(d, k);
+ const fK = Math.pow(fd, k);
+ const uK = 1 - Math.pow(1 - l, k);
+
+ const tg = u.over && t >= cu ? M : 0;
+ u = { ...u, mass: u.mass + (tg - u.mass) * uK };
+
+ acc += re * s;
+ const ot = O(sim, u, acc);
+ acc = ot.rem;
+ const rs = ot.sim.bodies.map((b) => y(b, ot.sim.sun));
+ sim = { bodies: rs.map((j) => j.keep).filter(Boolean), sun: ot.sim.sun };
+ const ea = rs.reduce((a, j) => a + j.ate, 0);
+ if (ea > 0) fl = Math.min(1.6, fl + ea * 0.9);
+ fl *= fK;
+
+ const cx = (v.cols - 1) / 2;
+ const cy = (v.rows - 1) / 2;
+ const n = sim.sun;
+
+ f.energy.forEach((j, i) => {
+ G.energy[i] = j * dK;
+ G.hue[i] = f.hue[i];
+ });
+
+ const br = 0.88 + 0.12 * Math.sin(t) + fl;
+ const Fx = cx + n.x * S * cx;
+ const Fy = cy + n.y * S * cy;
+ const bX = Math.round(Fx);
+ const bY = Math.round(Fy);
+ W.forEach(([ox, oy]) => {
+ const px = bX + ox;
+ const py = bY + oy;
+ const dt = Math.hypot(px - Fx, py - Fy);
+ if (dt > R) return;
+ add(G, px, py, 45, br * (0.45 + 0.55 * (1 - dt / R)));
+ });
+
+ sim.bodies.forEach((b) => {
+ const dt = Math.hypot(b.x - n.x, b.y - n.y);
+ const ht = c((0.45 - dt) / 0.45);
+ bl(G, cx + b.x * S * cx, cy + b.y * S * cy, b.hue, 0.85 + 0.15 * ht, 0.4 + b.size * 0.5);
+ });
+
+ if (pd) {
+ const c2 = c((t - pd.start) / T);
+ const sz = 0.8 + c2 * 2.2;
+ bl(G, cx + u.x * S * cx, cy + u.y * S * cy, pd.hue, 0.55 + 0.45 * c2, 0.4 + sz * 0.5);
+ } else if (u.over) {
+ add(G, Math.round(cx + u.x * S * cx), Math.round(cy + u.y * S * cy), 200, 0.5);
+ }
+
+ v.cells.forEach((el, i) => {
+ const e = G.energy[i];
+ if (e >= F) {
+ const Q = Math.round(Math.min(1, e) * q);
+ const hue = G.hue[i];
+ if (Q !== dr[i] || hue !== dh[i]) {
+ const qe = Q / q;
+ const mi = Math.min(100, qe * 170).toFixed(1);
+ el.style.backgroundColor = `color-mix(in srgb, hsl(${hue} 95% ${dl}%) ${mi}%, ${bs})`;
+ el.style.transform = `scale(${(1 + qe * 0.8).toFixed(3)})`;
+ el.style.border = "0";
+ dr[i] = Q;
+ dh[i] = hue;
+ }
+ } else if (dr[i] !== -1) {
+ x(el);
+ dr[i] = -1;
+ }
+ });
+
+ const tp = f;
+ f = G;
+ G = tp;
+
+ if (!rd && vs) raf = requestAnimationFrame(fm);
+ };
+
+ const io = new IntersectionObserver((en) => {
+ const vi = en.some((E) => E.isIntersecting);
+ if (vi === vs) return;
+ vs = vi;
+ if (vs) {
+ lt = 0;
+ raf = requestAnimationFrame(fm);
+ } else {
+ cancelAnimationFrame(raf);
+ }
+ });
+ io.observe(N);
+
+ raf = requestAnimationFrame(fm);
+
+ return () => {
+ cancelAnimationFrame(raf);
+ io.disconnect();
+ ac.abort();
+ };
+};
+
+let Y = null;
+let Z = null;
+const J = () => {
+ const t = document.querySelector("[data-punchcard]");
+ if (t === Z) return;
+ if (Y) Y();
+ Z = t;
+ Y = t ? g(t) : null;
+};
+J();
+document.addEventListener("htmx:load", J);
diff --git a/appview/pages/templates/layouts/profilebase.html b/appview/pages/templates/layouts/profilebase.html
index 2f199999..0ee3c31b 100644
--- a/appview/pages/templates/layouts/profilebase.html
+++ b/appview/pages/templates/layouts/profilebase.html
@@ -54,6 +54,9 @@
{{ template "user/fragments/profileCard" .Card }}
{{ if .Card.Punchcard }}
{{ block "punchcard" .Card.Punchcard }} {{ end }}
+ {{ with .Card.ProfileScript }}
+
+ {{ end }}
{{ end }}
@@ -110,7 +113,7 @@
{{ .Total | int64 | commaFmt }} commits
-
+
{{ range .Punches }}
{{ $count := .Count }}
{{ $theme := "bg-gray-200 dark:bg-gray-700 size-[4px]" }}
diff --git a/appview/state/profile.go b/appview/state/profile.go
index 70b3d7c1..a1a23d71 100644
--- a/appview/state/profile.go
+++ b/appview/state/profile.go
@@ -53,6 +53,10 @@ func (s *State) Profile(w http.ResponseWriter, r *http.Request) {
}
}
+var profileScripts = map[string]string{
+ "did:plc:3fwecdnvtcscjnrx2p4n7alz": "/static/profile-fx/orrery.js",
+}
+
func (s *State) profile(r *http.Request) (*pages.ProfileCard, error) {
didOrHandle := chi.URLParam(r, "user")
if didOrHandle == "" {
@@ -136,7 +140,8 @@ func (s *State) profile(r *http.Request) (*pages.ProfileCard, error) {
FollowersCount: followStats.Followers,
FollowingCount: followStats.Following,
},
- Punchcard: punchcard,
+ Punchcard: punchcard,
+ ProfileScript: profileScripts[did],
}, nil
}
--
2.51.2