From ebaa217dda7bb1b686ce3584c63c48cd14b361cc Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Tue, 24 Mar 2026 00:10:00 -0400 Subject: [PATCH] feat: add sovereignty Easter egg animation (#126) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Typing 'sovereignty' anywhere on the page (or sending it via chat) triggers a golden star burst: stars and constellation lines pulse to gold then fade back, with a brief '⚡ SOVEREIGNTY ⚡' overlay message. Fixes #126 --- app.js | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 2 ++ style.css | 33 ++++++++++++++++++ 3 files changed, 134 insertions(+) diff --git a/app.js b/app.js index 4902f2f..711ae6c 100644 --- a/app.js +++ b/app.js @@ -297,6 +297,105 @@ window.addEventListener('player-left', (/** @type {CustomEvent} */ event) => { window.addEventListener('chat-message', (/** @type {CustomEvent} */ event) => { console.log('Chat message:', event.detail); + if (typeof event.detail?.text === 'string' && event.detail.text.toLowerCase().includes('sovereignty')) { + triggerSovereigntyEasterEgg(); + } +}); + +// === SOVEREIGNTY EASTER EGG === +const SOVEREIGNTY_WORD = 'sovereignty'; +let sovereigntyBuffer = ''; +let sovereigntyBufferTimer = /** @type {ReturnType|null} */ (null); + +const sovereigntyMsg = document.getElementById('sovereignty-msg'); + +/** + * Triggers the sovereignty Easter egg: stars pulse gold, message flashes. + */ +function triggerSovereigntyEasterEgg() { + // Flash constellation lines gold + const originalLineColor = constellationLines.material.color.getHex(); + constellationLines.material.color.setHex(0xffd700); + constellationLines.material.opacity = 0.9; + + // Stars burst gold + const originalStarColor = starMaterial.color.getHex(); + const originalStarOpacity = starMaterial.opacity; + starMaterial.color.setHex(0xffd700); + starMaterial.opacity = 1.0; + + // Show overlay message + if (sovereigntyMsg) { + sovereigntyMsg.classList.remove('visible'); + // Force reflow so animation restarts + void sovereigntyMsg.offsetWidth; + sovereigntyMsg.classList.add('visible'); + } + + // Animate gold fade-out over 2.5s + const startTime = performance.now(); + const DURATION = 2500; + + function fadeBack() { + const t = Math.min((performance.now() - startTime) / DURATION, 1); + const eased = t * t; // ease in: slow start, fast end + + // Interpolate star color back + const goldR = 1.0, goldG = 0.843, goldB = 0; + const origColor = new THREE.Color(originalStarColor); + starMaterial.color.setRGB( + goldR + (origColor.r - goldR) * eased, + goldG + (origColor.g - goldG) * eased, + goldB + (origColor.b - goldB) * eased + ); + starMaterial.opacity = 1.0 + (originalStarOpacity - 1.0) * eased; + + // Interpolate line color back + const origLineColor = new THREE.Color(originalLineColor); + constellationLines.material.color.setRGB( + 1.0 + (origLineColor.r - 1.0) * eased, + 0.843 + (origLineColor.g - 0.843) * eased, + 0 + origLineColor.b * eased + ); + + if (t < 1) { + requestAnimationFrame(fadeBack); + } else { + // Restore originals exactly + starMaterial.color.setHex(originalStarColor); + starMaterial.opacity = originalStarOpacity; + constellationLines.material.color.setHex(originalLineColor); + if (sovereigntyMsg) sovereigntyMsg.classList.remove('visible'); + } + } + + requestAnimationFrame(fadeBack); +} + +// Detect 'sovereignty' typed anywhere on the page (cheat-code style) +document.addEventListener('keydown', (e) => { + if (e.metaKey || e.ctrlKey || e.altKey) return; + if (e.key.length !== 1) { + // Non-printable key resets buffer + sovereigntyBuffer = ''; + return; + } + + sovereigntyBuffer += e.key.toLowerCase(); + + // Keep only the last N chars needed + if (sovereigntyBuffer.length > SOVEREIGNTY_WORD.length) { + sovereigntyBuffer = sovereigntyBuffer.slice(-SOVEREIGNTY_WORD.length); + } + + if (sovereigntyBuffer === SOVEREIGNTY_WORD) { + sovereigntyBuffer = ''; + triggerSovereigntyEasterEgg(); + } + + // Reset buffer after 3s of inactivity + if (sovereigntyBufferTimer) clearTimeout(sovereigntyBufferTimer); + sovereigntyBufferTimer = setTimeout(() => { sovereigntyBuffer = ''; }, 3000); }); window.addEventListener('beforeunload', () => { diff --git a/index.html b/index.html index 6d4000f..5e67846 100644 --- a/index.html +++ b/index.html @@ -46,6 +46,8 @@ [P] exit  |  [[] focus-   []] focus+   focus: 5.0 +
⚡ SOVEREIGNTY ⚡
+ diff --git a/style.css b/style.css index 92029bb..7dae8ca 100644 --- a/style.css +++ b/style.css @@ -150,3 +150,36 @@ body.photo-mode #overview-indicator { #photo-focus { color: var(--color-primary); } + +/* === SOVEREIGNTY EASTER EGG === */ +#sovereignty-msg { + display: none; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #ffd700; + font-family: var(--font-body); + font-size: 13px; + letter-spacing: 0.3em; + text-transform: uppercase; + pointer-events: none; + z-index: 30; + border: 1px solid #ffd700; + padding: 8px 20px; + background: rgba(0, 0, 8, 0.7); + white-space: nowrap; + text-align: center; +} + +#sovereignty-msg.visible { + display: block; + animation: sovereignty-flash 2.5s ease-out forwards; +} + +@keyframes sovereignty-flash { + 0% { opacity: 0; transform: translate(-50%, -50%) scale(0.85); } + 15% { opacity: 1; transform: translate(-50%, -50%) scale(1.05); } + 40% { opacity: 1; transform: translate(-50%, -50%) scale(1); } + 100% { opacity: 0; transform: translate(-50%, -50%) scale(1); } +} -- 2.43.0