Monorepo for Tangled
0

Configure Feed

Select the types of activity you want to include in your feed.

Vaporware commits

authored by

Evelyn Osman and committed by
Tangled
(Jul 14, 2026, 2:23 PM +0300) 34927976 1d4f0247

+236
+236
appview/pages/profile-fx/kandake.js
··· 1 + // neon-terrain.js — profile fx: renders the punchcard as a polycss-style 3D 2 + // terrain (perspective + rotateX/rotateZ + translateZ elevation, as in the 3 + // terrain demo) lit with twinkl "y2kringe" neon — cyan/magenta pulses sweeping 4 + // across the grid. One self-contained ES module, vanilla JS, no imports. 5 + // 6 + // CSS is loaded by injecting a <style> element via the DOM at runtime, so the 7 + // module stays a single file with no build step. 8 + 9 + const STYLE_ID = "npx-neon-terrain-style"; 10 + const GRID_CLASS = "npx-grid"; 11 + 12 + // twinkl y2kringe palette 13 + const VOID_BG = "#0a001a"; 14 + const HUE_CYAN = 185; 15 + const HUE_MAGENTA = 300; 16 + 17 + const WIDE_Q = "(min-width: 768px)"; 18 + const REDUCED_Q = "(prefers-reduced-motion: reduce)"; 19 + const COLS_NARROW = 28; 20 + const COLS_WIDE = 14; 21 + 22 + const MAX_LIFT = 22; // px of translateZ at full energy 23 + const WAVE_SPEED = 1.7; // rad/s of the diagonal pulse 24 + const SHIMMER_SPEED = 0.6; 25 + const ROT_SPEED = 9; // deg/s turntable rotation of the whole terrain 26 + const TILT = 55; // deg rotateX of the board 27 + const BOARD_SCALE = 0.72; 28 + const QUANT = 48; // energy quantization levels — skip redundant style writes 29 + 30 + const clamp01 = (n) => Math.max(0, Math.min(1, n)); 31 + 32 + const css = ` 33 + [data-punchcard].${GRID_CLASS} { 34 + transform-style: preserve-3d; 35 + background-color: ${VOID_BG}; 36 + background-image: 37 + radial-gradient(1px 1px at 10% 20%, rgb(255 255 255 / 0.5) 0%, transparent 100%), 38 + radial-gradient(1px 1px at 30% 80%, rgb(255 255 255 / 0.4) 0%, transparent 100%), 39 + radial-gradient(1.5px 1.5px at 65% 85%, rgb(255 255 255 / 0.45) 0%, transparent 100%), 40 + radial-gradient(1px 1px at 85% 15%, rgb(255 255 255 / 0.4) 0%, transparent 100%), 41 + radial-gradient(1.5px 1.5px at 15% 55%, rgb(255 255 255 / 0.5) 0%, transparent 100%); 42 + background-size: 200px 200px; 43 + border-radius: 12px; 44 + box-shadow: 0 0 24px rgb(255 0 255 / 0.25), inset 0 0 40px rgb(0 255 255 / 0.06); 45 + } 46 + [data-punchcard].${GRID_CLASS} > * { 47 + transform-style: preserve-3d; 48 + position: relative; 49 + } 50 + /* ground shadow under each lifted dot — cheap depth cue at floor level */ 51 + [data-punchcard].${GRID_CLASS} > *::after { 52 + content: ""; 53 + position: absolute; 54 + inset: 20%; 55 + border-radius: 50%; 56 + background: rgb(255 0 255 / 0.14); 57 + filter: blur(1.5px); 58 + transform: translateZ(0.5px); 59 + pointer-events: none; 60 + } 61 + [data-punchcard].${GRID_CLASS} > * > :first-child { 62 + transform-style: preserve-3d; 63 + transition: none; 64 + will-change: transform, background-color, box-shadow; 65 + } 66 + `; 67 + 68 + const injectStyle = () => { 69 + let el = document.getElementById(STYLE_ID); 70 + if (!el) { 71 + el = document.createElement("style"); 72 + el.id = STYLE_ID; 73 + el.textContent = css; 74 + document.head.appendChild(el); 75 + } 76 + return el; 77 + }; 78 + 79 + // Luminance of the dot's current color -> base terrain height, so activity 80 + // level (darker/lighter contribution dots) becomes elevation. 81 + const luminance = (el) => { 82 + const m = getComputedStyle(el).backgroundColor.match(/[\d.]+/g); 83 + if (!m || m.length < 3) return 0; 84 + const [r, g, b] = m.map(Number); 85 + return clamp01((0.2126 * r + 0.7152 * g + 0.0722 * b) / 255); 86 + }; 87 + 88 + const collectDots = (grid) => 89 + Array.from(grid.children, (c) => c.firstElementChild).filter(Boolean); 90 + 91 + const clearDot = (el) => { 92 + el.style.backgroundColor = ""; 93 + el.style.transform = ""; 94 + el.style.boxShadow = ""; 95 + el.style.transition = ""; 96 + el.style.transformOrigin = ""; 97 + el.style.willChange = ""; 98 + }; 99 + 100 + const paintDot = (el, energy, hueMix) => { 101 + const hue = HUE_CYAN + (HUE_MAGENTA - HUE_CYAN) * hueMix; 102 + const light = 55 + energy * 20; 103 + const color = `hsl(${hue.toFixed(1)} 100% ${light.toFixed(1)}%)`; 104 + const z = (energy * MAX_LIFT).toFixed(2); 105 + const s = (1 + energy * 1.2).toFixed(3); 106 + el.style.backgroundColor = color; 107 + el.style.transform = `translateZ(${z}px) scale(${s})`; 108 + el.style.boxShadow = 109 + `0 0 ${(2 + energy * 8).toFixed(1)}px ${color}, ` + 110 + `0 0 ${(6 + energy * 16).toFixed(1)}px hsl(${hue.toFixed(1)} 100% 50% / 0.55)`; 111 + }; 112 + 113 + const start = (grid) => { 114 + const ac = new AbortController(); 115 + const opts = { signal: ac.signal }; 116 + const wideMq = matchMedia(WIDE_Q); 117 + const reduced = matchMedia(REDUCED_Q).matches; 118 + 119 + const styleEl = injectStyle(); 120 + grid.classList.add(GRID_CLASS); 121 + 122 + const boardTransform = (deg) => 123 + `perspective(1200px) rotateX(${TILT}deg) rotateZ(${deg.toFixed(2)}deg) scale(${BOARD_SCALE})`; 124 + grid.style.transform = boardTransform(45); 125 + 126 + let dots = collectDots(grid); 127 + // Inline overrides beat any Tailwind transition/duration classes on the dots. 128 + dots.forEach((el) => { 129 + el.style.transition = "none"; 130 + el.style.transformOrigin = "center"; 131 + el.style.willChange = "transform, background-color, box-shadow"; 132 + }); 133 + // Base heights are read from the pre-fx dot colors, before we repaint them. 134 + // In dark mode empty dots are darker than active ones; in light mode it is 135 + // inverted, so normalize against the dominant (most common) luminance. 136 + let base = dots.map(luminance); 137 + { 138 + const counts = new Map(); 139 + base.forEach((l) => { 140 + const k = Math.round(l * 20); 141 + counts.set(k, (counts.get(k) || 0) + 1); 142 + }); 143 + const idle = [...counts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] / 20 ?? 0; 144 + base = base.map((l) => clamp01(Math.abs(l - idle) * 1.6)); 145 + } 146 + 147 + let cols = wideMq.matches ? COLS_WIDE : COLS_NARROW; 148 + let lastQ = new Int16Array(dots.length).fill(-1); 149 + let lastHue = new Float32Array(dots.length).fill(-1); 150 + 151 + const relayout = () => { 152 + cols = wideMq.matches ? COLS_WIDE : COLS_NARROW; 153 + lastQ.fill(-1); 154 + }; 155 + wideMq.addEventListener("change", relayout, opts); 156 + 157 + const frame = (t) => { 158 + // Turntable: rotate the entire terrain around its Z axis. 159 + grid.style.transform = boardTransform(45 + t * ROT_SPEED); 160 + for (let i = 0; i < dots.length; i++) { 161 + const col = i % cols; 162 + const row = (i / cols) | 0; 163 + const wave = 0.5 + 0.5 * Math.sin(t * WAVE_SPEED - (col + row) * 0.35); 164 + const shimmer = 0.5 + 0.5 * Math.sin(t * SHIMMER_SPEED + col * 0.5 - row * 0.3); 165 + const energy = clamp01(0.12 + base[i] * 0.5 + wave * 0.45); 166 + const q = Math.round(energy * QUANT); 167 + const hueQ = Math.round(shimmer * 24) / 24; 168 + if (q === lastQ[i] && hueQ === lastHue[i]) continue; 169 + lastQ[i] = q; 170 + lastHue[i] = hueQ; 171 + paintDot(dots[i], q / QUANT, hueQ); 172 + } 173 + }; 174 + 175 + let raf = 0; 176 + let visible = true; 177 + 178 + const loop = (ms) => { 179 + frame(ms / 1000); 180 + if (visible) raf = requestAnimationFrame(loop); 181 + }; 182 + 183 + if (reduced) { 184 + // Static 3D render: elevation from activity, fixed neon tint, no motion. 185 + dots.forEach((el, i) => { 186 + const col = i % cols; 187 + const row = (i / cols) | 0; 188 + paintDot(el, clamp01(0.15 + base[i] * 0.85), ((col + row) % 8) / 8); 189 + }); 190 + wideMq.addEventListener( 191 + "change", 192 + () => dots.forEach((el, i) => paintDot(el, clamp01(0.15 + base[i] * 0.85), (((i % cols) + ((i / cols) | 0)) % 8) / 8)), 193 + opts, 194 + ); 195 + } else { 196 + const io = new IntersectionObserver((entries) => { 197 + const vis = entries.some((e) => e.isIntersecting); 198 + if (vis === visible) return; 199 + visible = vis; 200 + if (visible) raf = requestAnimationFrame(loop); 201 + else cancelAnimationFrame(raf); 202 + }); 203 + io.observe(grid); 204 + raf = requestAnimationFrame(loop); 205 + ac.signal.addEventListener("abort", () => { 206 + cancelAnimationFrame(raf); 207 + io.disconnect(); 208 + }); 209 + } 210 + 211 + return () => { 212 + ac.abort(); 213 + grid.classList.remove(GRID_CLASS); 214 + grid.style.transform = ""; 215 + dots.forEach(clearDot); 216 + styleEl.remove(); 217 + }; 218 + }; 219 + 220 + let cleanup = null; 221 + let current = null; 222 + const init = () => { 223 + const grid = document.querySelector("[data-punchcard]"); 224 + if (grid === current) return; 225 + if (cleanup) cleanup(); 226 + current = grid; 227 + cleanup = grid ? start(grid) : null; 228 + }; 229 + // Guard against import before the DOM is parsed — without this, a script tag 230 + // missing `defer` finds no punchcard and plain page loads never fire htmx:load. 231 + if (document.readyState === "loading") { 232 + document.addEventListener("DOMContentLoaded", init, { once: true }); 233 + } else { 234 + init(); 235 + } 236 + document.addEventListener("htmx:load", init);