From c481a3b083e8828fc9e897b7794d722656871cdd Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Tue, 24 Mar 2026 00:46:45 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20neon=20sign=20above=20Batcave=20?= =?UTF-8?q?=E2=80=94=20Sovereignty=20and=20Service=20Always?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a glowing neon sign sprite positioned above the workshop / agent board area (the Batcave). Features amber glow text on line 1 ("SOVEREIGNTY AND SERVICE") and magenta neon on line 2 ("A · L · W · A · Y · S"), with a pulsing PointLight for scene ambiance and a gentle float + flicker animation each frame. Fixes #265 --- app.js | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/app.js b/app.js index b396094..7e21806 100644 --- a/app.js +++ b/app.js @@ -656,6 +656,79 @@ for (let i = 0; i < RUNE_COUNT; i++) { runeSprites.push({ sprite, baseAngle, floatPhase: (i / RUNE_COUNT) * Math.PI * 2 }); } +// === BATCAVE NEON SIGN === +// Glowing neon sign hovering above the workshop / agent area (Batcave). +// Position mirrors the centre of the agent board arc (angle PI = negative-X). + +const BATCAVE_SIGN_POS = new THREE.Vector3(-9.5, 8.8, 0); + +/** + * Renders the "Sovereignty and Service Always" neon sign onto a canvas texture. + * @returns {THREE.CanvasTexture} + */ +function createBatcaveNeonSignTexture() { + const W = 512, H = 128; + const canvas = document.createElement('canvas'); + canvas.width = W; + canvas.height = H; + const ctx = canvas.getContext('2d'); + + ctx.clearRect(0, 0, W, H); + + // Dark background panel + ctx.fillStyle = 'rgba(4, 6, 20, 0.88)'; + ctx.fillRect(4, 4, W - 8, H - 8); + + // Outer neon border — amber glow + ctx.shadowColor = '#ffaa22'; + ctx.shadowBlur = 20; + ctx.strokeStyle = '#ffcc44'; + ctx.lineWidth = 2.5; + ctx.strokeRect(6, 6, W - 12, H - 12); + + // Inner accent border + ctx.shadowBlur = 0; + ctx.strokeStyle = 'rgba(255, 160, 30, 0.28)'; + ctx.lineWidth = 1; + ctx.strokeRect(12, 12, W - 24, H - 24); + + // Line 1 — SOVEREIGNTY AND SERVICE + ctx.shadowColor = '#ffcc44'; + ctx.shadowBlur = 24; + ctx.font = 'bold 28px "Courier New", monospace'; + ctx.fillStyle = '#ffe066'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText('SOVEREIGNTY AND SERVICE', W / 2, H / 2 - 20); + + // Line 2 — A L W A Y S (spaced for neon tube effect) + ctx.shadowColor = '#ff44ff'; + ctx.shadowBlur = 30; + ctx.font = 'bold 26px "Courier New", monospace'; + ctx.fillStyle = '#ff88ff'; + ctx.fillText('A \u00b7 L \u00b7 W \u00b7 A \u00b7 Y \u00b7 S', W / 2, H / 2 + 26); + + ctx.shadowBlur = 0; + return new THREE.CanvasTexture(canvas); +} + +const batcaveSignMat = new THREE.SpriteMaterial({ + map: createBatcaveNeonSignTexture(), + transparent: true, + opacity: 0.95, + depthWrite: false, + blending: THREE.AdditiveBlending, +}); +const batcaveSignSprite = new THREE.Sprite(batcaveSignMat); +batcaveSignSprite.scale.set(8.5, 2.1, 1); +batcaveSignSprite.position.copy(BATCAVE_SIGN_POS); +scene.add(batcaveSignSprite); + +// Warm point light to cast neon glow onto surrounding geometry +const batcaveSignLight = new THREE.PointLight(0xffaa22, 0.6, 8); +batcaveSignLight.position.copy(BATCAVE_SIGN_POS); +scene.add(batcaveSignLight); + // === ANIMATION LOOP === const clock = new THREE.Clock(); @@ -761,6 +834,15 @@ function animate() { } } + // Animate Batcave neon sign — gentle hover + subtle neon flicker + batcaveSignSprite.position.y = BATCAVE_SIGN_POS.y + Math.sin(elapsed * 0.45) * 0.18; + { + // Occasional flicker: mostly steady, brief dimming ~every 7 s + const flicker = 0.85 + Math.sin(elapsed * 2.3) * 0.05 + Math.sin(elapsed * 17.1) * 0.04; + batcaveSignMat.opacity = Math.max(0.72, Math.min(1.0, flicker)); + batcaveSignLight.intensity = 0.4 + Math.sin(elapsed * 1.7) * 0.2; + } + // Animate rune ring — orbit and vertical float for (const rune of runeSprites) { const angle = rune.baseAngle + elapsed * RUNE_ORBIT_SPEED; -- 2.43.0