[claude] Ring of floating runes around Nexus center platform (#110) (#240)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled

This commit was merged in pull request #240.
This commit is contained in:
2026-03-24 04:42:29 +00:00
parent d193a89262
commit 39e0eecb9e

90
app.js
View File

@@ -575,6 +575,87 @@ async function loadSovereigntyStatus() {
loadSovereigntyStatus();
// === RUNE RING ===
// 12 Elder Futhark rune sprites in a slow-orbiting ring around the center platform.
const RUNE_COUNT = 12;
const RUNE_RING_RADIUS = 7.0;
const RUNE_RING_Y = 1.5; // base height above platform
const RUNE_ORBIT_SPEED = 0.08; // radians per second
const ELDER_FUTHARK = ['ᚠ','ᚢ','ᚦ','ᚨ','ᚱ','','','ᚹ','ᚺ','ᚾ','','ᛃ'];
const RUNE_GLOW_COLORS = ['#00ffcc', '#ff44ff']; // alternating cyan / magenta
/**
* Creates a canvas texture for a single glowing rune glyph.
* @param {string} glyph
* @param {string} color
* @returns {THREE.CanvasTexture}
*/
function createRuneTexture(glyph, color) {
const W = 128, H = 128;
const canvas = document.createElement('canvas');
canvas.width = W;
canvas.height = H;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, W, H);
// Outer glow
ctx.shadowColor = color;
ctx.shadowBlur = 28;
ctx.font = 'bold 78px serif';
ctx.fillStyle = color;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(glyph, W / 2, H / 2);
return new THREE.CanvasTexture(canvas);
}
// Faint torus marking the orbit height
const runeOrbitRingGeo = new THREE.TorusGeometry(RUNE_RING_RADIUS, 0.03, 6, 64);
const runeOrbitRingMat = new THREE.MeshBasicMaterial({
color: 0x224466,
transparent: true,
opacity: 0.22,
});
const runeOrbitRingMesh = new THREE.Mesh(runeOrbitRingGeo, runeOrbitRingMat);
runeOrbitRingMesh.rotation.x = Math.PI / 2;
runeOrbitRingMesh.position.y = RUNE_RING_Y;
scene.add(runeOrbitRingMesh);
/**
* @type {Array<{sprite: THREE.Sprite, baseAngle: number, floatPhase: number}>}
*/
const runeSprites = [];
for (let i = 0; i < RUNE_COUNT; i++) {
const glyph = ELDER_FUTHARK[i % ELDER_FUTHARK.length];
const color = RUNE_GLOW_COLORS[i % RUNE_GLOW_COLORS.length];
const texture = createRuneTexture(glyph, color);
const runeMat = new THREE.SpriteMaterial({
map: texture,
transparent: true,
opacity: 0.85,
depthWrite: false,
blending: THREE.AdditiveBlending,
});
const sprite = new THREE.Sprite(runeMat);
sprite.scale.set(1.3, 1.3, 1);
const baseAngle = (i / RUNE_COUNT) * Math.PI * 2;
sprite.position.set(
Math.cos(baseAngle) * RUNE_RING_RADIUS,
RUNE_RING_Y,
Math.sin(baseAngle) * RUNE_RING_RADIUS
);
scene.add(sprite);
runeSprites.push({ sprite, baseAngle, floatPhase: (i / RUNE_COUNT) * Math.PI * 2 });
}
// === ANIMATION LOOP ===
const clock = new THREE.Clock();
@@ -680,6 +761,15 @@ function animate() {
}
}
// Animate rune ring — orbit and vertical float
for (const rune of runeSprites) {
const angle = rune.baseAngle + elapsed * RUNE_ORBIT_SPEED;
rune.sprite.position.x = Math.cos(angle) * RUNE_RING_RADIUS;
rune.sprite.position.z = Math.sin(angle) * RUNE_RING_RADIUS;
rune.sprite.position.y = RUNE_RING_Y + Math.sin(elapsed * 0.7 + rune.floatPhase) * 0.4;
rune.sprite.material.opacity = 0.65 + Math.sin(elapsed * 1.2 + rune.floatPhase) * 0.2;
}
composer.render();
}