From f8b8b48a27d832698c9de9a764c1425fe35f71ba Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Tue, 24 Mar 2026 01:05:29 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20animated=20Timmy=20sigil=20on=20the=20f?= =?UTF-8?q?loor=20=E2=80=94=20sacred=20geometry=20that=20glows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a Metatron's Cube / Flower of Life floor sigil on the glass platform. Three layers: - Canvas-painted base (Flower of Life circles, dual hexagram, concentric rings, 12-spoke radials, gold/cyan colour scheme) - Three TorusGeometry rings that counter-rotate at different speeds - Subtle PointLight below the sigil for floor glow All elements pulse and breathe in the animate loop via sin-wave opacity and intensity modulation. Fixes #260 Co-Authored-By: Claude Sonnet 4.6 --- app.js | 231 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) diff --git a/app.js b/app.js index 540375d..3a63624 100644 --- a/app.js +++ b/app.js @@ -618,6 +618,226 @@ async function updateHeatmap() { updateHeatmap(); setInterval(updateHeatmap, HEATMAP_REFRESH_MS); +// === TIMMY SIGIL === +// Animated sacred-geometry floor sigil — Metatron's Cube / Flower of Life. +// Three layers: painted canvas base + three counter-rotating glow rings. + +const SIGIL_CANVAS_SIZE = 512; +const SIGIL_RADIUS = 3.8; // world-space radius (fits inside the glass platform) + +/** + * Draws the Timmy sigil onto a canvas and returns it. + * Pattern: Flower of Life circles, dual hexagram triangles, concentric rings, 12-spoke radials. + * @returns {HTMLCanvasElement} + */ +function drawSigilCanvas() { + const canvas = document.createElement('canvas'); + canvas.width = SIGIL_CANVAS_SIZE; + canvas.height = SIGIL_CANVAS_SIZE; + const ctx = canvas.getContext('2d'); + const cx = SIGIL_CANVAS_SIZE / 2; + const cy = SIGIL_CANVAS_SIZE / 2; + const r = cx * 0.88; // outer drawing radius + + ctx.clearRect(0, 0, SIGIL_CANVAS_SIZE, SIGIL_CANVAS_SIZE); + + // Faint radial background glow + const bgGrad = ctx.createRadialGradient(cx, cy, 0, cx, cy, r); + bgGrad.addColorStop(0, 'rgba(0, 200, 255, 0.10)'); + bgGrad.addColorStop(0.5, 'rgba(0, 100, 200, 0.04)'); + bgGrad.addColorStop(1, 'rgba(0, 0, 0, 0)'); + ctx.fillStyle = bgGrad; + ctx.fillRect(0, 0, SIGIL_CANVAS_SIZE, SIGIL_CANVAS_SIZE); + + /** Draws a single circle outline with glow. */ + function glowCircle(x, y, radius, color, alpha, lineW) { + ctx.save(); + ctx.globalAlpha = alpha; + ctx.strokeStyle = color; + ctx.lineWidth = lineW; + ctx.shadowColor = color; + ctx.shadowBlur = 12; + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.stroke(); + ctx.restore(); + } + + /** Draws two interlocking triangles (Star of David). */ + function hexagram(ox, oy, hr, color, alpha) { + ctx.save(); + ctx.globalAlpha = alpha; + ctx.strokeStyle = color; + ctx.lineWidth = 1.4; + ctx.shadowColor = color; + ctx.shadowBlur = 10; + // Upward-pointing triangle + ctx.beginPath(); + for (let i = 0; i < 3; i++) { + const a = (i / 3) * Math.PI * 2 - Math.PI / 2; + const px = ox + Math.cos(a) * hr; + const py = oy + Math.sin(a) * hr; + i === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py); + } + ctx.closePath(); + ctx.stroke(); + // Downward-pointing triangle + ctx.beginPath(); + for (let i = 0; i < 3; i++) { + const a = (i / 3) * Math.PI * 2 + Math.PI / 2; + const px = ox + Math.cos(a) * hr; + const py = oy + Math.sin(a) * hr; + i === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py); + } + ctx.closePath(); + ctx.stroke(); + ctx.restore(); + } + + const petalR = r * 0.32; // radius of each Flower-of-Life petal + + // Flower of Life — center circle + glowCircle(cx, cy, petalR, '#00ccff', 0.65, 1.0); + + // First ring of 6 petals + for (let i = 0; i < 6; i++) { + const a = (i / 6) * Math.PI * 2; + glowCircle(cx + Math.cos(a) * petalR, cy + Math.sin(a) * petalR, petalR, '#00aadd', 0.50, 0.8); + } + + // Second ring of 6 petals (rotated 30°) + for (let i = 0; i < 6; i++) { + const a = (i / 6) * Math.PI * 2 + Math.PI / 6; + glowCircle(cx + Math.cos(a) * petalR * 1.73, cy + Math.sin(a) * petalR * 1.73, petalR, '#0077aa', 0.25, 0.6); + } + + // Dual hexagram — outer (gold) and inner (amber) + hexagram(cx, cy, r * 0.62, '#ffd700', 0.75); + hexagram(cx, cy, r * 0.41, '#ffaa00', 0.50); + + // Concentric rings + glowCircle(cx, cy, r * 0.92, '#0055aa', 0.40, 0.8); + glowCircle(cx, cy, r * 0.72, '#0099cc', 0.38, 0.8); + glowCircle(cx, cy, r * 0.52, '#00ccff', 0.42, 0.9); + glowCircle(cx, cy, r * 0.18, '#ffd700', 0.65, 1.2); + + // 12-spoke radial lines + ctx.save(); + ctx.globalAlpha = 0.28; + ctx.strokeStyle = '#00aaff'; + ctx.lineWidth = 0.6; + ctx.shadowColor = '#00aaff'; + ctx.shadowBlur = 5; + for (let i = 0; i < 12; i++) { + const a = (i / 12) * Math.PI * 2; + ctx.beginPath(); + ctx.moveTo(cx + Math.cos(a) * r * 0.18, cy + Math.sin(a) * r * 0.18); + ctx.lineTo(cx + Math.cos(a) * r * 0.91, cy + Math.sin(a) * r * 0.91); + ctx.stroke(); + } + ctx.restore(); + + // Dot marks at spoke tips + ctx.save(); + ctx.fillStyle = '#00ffcc'; + ctx.shadowColor = '#00ffcc'; + ctx.shadowBlur = 9; + for (let i = 0; i < 12; i++) { + const a = (i / 12) * Math.PI * 2; + ctx.globalAlpha = i % 2 === 0 ? 0.80 : 0.50; + ctx.beginPath(); + ctx.arc(cx + Math.cos(a) * r * 0.91, cy + Math.sin(a) * r * 0.91, i % 2 === 0 ? 4 : 2.5, 0, Math.PI * 2); + ctx.fill(); + } + ctx.restore(); + + // Center point of light + ctx.save(); + ctx.globalAlpha = 1.0; + ctx.fillStyle = '#ffffff'; + ctx.shadowColor = '#88ddff'; + ctx.shadowBlur = 18; + ctx.beginPath(); + ctx.arc(cx, cy, 5, 0, Math.PI * 2); + ctx.fill(); + ctx.restore(); + + return canvas; +} + +const sigilTexture = new THREE.CanvasTexture(drawSigilCanvas()); + +const sigilMat = new THREE.MeshBasicMaterial({ + map: sigilTexture, + transparent: true, + opacity: 0.80, + depthWrite: false, + blending: THREE.AdditiveBlending, + side: THREE.DoubleSide, +}); + +const sigilMesh = new THREE.Mesh( + new THREE.CircleGeometry(SIGIL_RADIUS, 128), + sigilMat +); +sigilMesh.rotation.x = -Math.PI / 2; +sigilMesh.position.y = 0.010; +sigilMesh.userData.zoomLabel = 'Timmy Sigil'; +scene.add(sigilMesh); + +// Outer glow ring — rotates clockwise +const sigilRing1Mat = new THREE.MeshBasicMaterial({ + color: 0x00ccff, + transparent: true, + opacity: 0.45, + depthWrite: false, + blending: THREE.AdditiveBlending, +}); +const sigilRing1 = new THREE.Mesh( + new THREE.TorusGeometry(SIGIL_RADIUS * 0.965, 0.025, 6, 96), + sigilRing1Mat +); +sigilRing1.rotation.x = Math.PI / 2; +sigilRing1.position.y = 0.012; +scene.add(sigilRing1); + +// Middle ring — rotates counter-clockwise, gold +const sigilRing2Mat = new THREE.MeshBasicMaterial({ + color: 0xffd700, + transparent: true, + opacity: 0.40, + depthWrite: false, + blending: THREE.AdditiveBlending, +}); +const sigilRing2 = new THREE.Mesh( + new THREE.TorusGeometry(SIGIL_RADIUS * 0.62, 0.020, 6, 72), + sigilRing2Mat +); +sigilRing2.rotation.x = Math.PI / 2; +sigilRing2.position.y = 0.013; +scene.add(sigilRing2); + +// Inner ring — rotates clockwise, teal +const sigilRing3Mat = new THREE.MeshBasicMaterial({ + color: 0x00ffcc, + transparent: true, + opacity: 0.35, + depthWrite: false, + blending: THREE.AdditiveBlending, +}); +const sigilRing3 = new THREE.Mesh( + new THREE.TorusGeometry(SIGIL_RADIUS * 0.78, 0.018, 6, 80), + sigilRing3Mat +); +sigilRing3.rotation.x = Math.PI / 2; +sigilRing3.position.y = 0.011; +scene.add(sigilRing3); + +// Subtle floor glow light below the sigil +const sigilLight = new THREE.PointLight(0x0088ff, 0.4, 8); +sigilLight.position.set(0, 0.5, 0); +scene.add(sigilLight); + // === MOUSE-DRIVEN ROTATION === let mouseX = 0; let mouseY = 0; @@ -1078,6 +1298,17 @@ function animate() { // Heatmap floor: subtle breathing glow heatmapMat.opacity = 0.75 + Math.sin(elapsed * 0.6) * 0.2; + // Animate Timmy sigil — base pulses, rings counter-rotate + sigilMesh.rotation.z = elapsed * 0.04; + sigilRing1.rotation.z = elapsed * 0.06; + sigilRing2.rotation.z = -elapsed * 0.10; + sigilRing3.rotation.z = elapsed * 0.08; + sigilMat.opacity = 0.65 + Math.sin(elapsed * 1.3) * 0.18; + sigilRing1Mat.opacity = 0.38 + Math.sin(elapsed * 0.9) * 0.14; + sigilRing2Mat.opacity = 0.32 + Math.sin(elapsed * 1.6 + 1.2) * 0.12; + sigilRing3Mat.opacity = 0.28 + Math.sin(elapsed * 0.7 + 2.4) * 0.10; + sigilLight.intensity = 0.30 + Math.sin(elapsed * 1.1) * 0.15; + // Animate procedural clouds cloudMaterial.uniforms.uTime.value = elapsed; -- 2.43.0