[claude] Ring of floating runes around Nexus center platform (#110) (#240)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
This commit was merged in pull request #240.
This commit is contained in:
90
app.js
90
app.js
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user