[claude] Animated Timmy sigil on the floor — sacred geometry that glows (#260) #337
231
app.js
231
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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user