From 257c95f390e916fc543b953ca5dc7b67a2526f21 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Tue, 24 Mar 2026 00:23:54 -0400 Subject: [PATCH] feat: Add favicon that changes with Nexus state (#98) Refs #98 Agent: grok (xai/grok-3-fast via opencode) --- app.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/app.js b/app.js index b549577..89a3717 100644 --- a/app.js +++ b/app.js @@ -626,3 +626,59 @@ async function initCommitBanners() { } initCommitBanners(); + +// Favicon update functions +const updateFavicon = (state) => { + const canvas = document.createElement('canvas'); + canvas.width = 16; + canvas.height = 16; + const ctx = canvas.getContext('2d'); + + ctx.clearRect(0, 0, 16, 16); + + if (state === 'chat') { + // Pulsing dot for chat active + const time = Date.now() / 1000; + const pulse = Math.sin(time * 2) * 0.4 + 0.6; + ctx.fillStyle = NEXUS.colors.teal; + ctx.beginPath(); + ctx.arc(8, 8, 4 * pulse, 0, Math.PI * 2); + ctx.fill(); + } else if (state === 'portal') { + // Purple dot for portal nearby + ctx.fillStyle = NEXUS.colors.purple; + ctx.beginPath(); + ctx.arc(8, 8, 4, 0, Math.PI * 2); + ctx.fill(); + } else { + // Default teal dot + ctx.fillStyle = NEXUS.colors.teal; + ctx.beginPath(); + ctx.arc(8, 8, 4, 0, Math.PI * 2); + ctx.fill(); + } + + const link = document.querySelector('link[rel="icon"]'); + if (!link) { + const newLink = document.createElement('link'); + newLink.rel = 'icon'; + newLink.href = canvas.toDataURL('image/png'); + document.head.appendChild(newLink); + } else { + link.href = canvas.toDataURL('image/png'); + } +}; + +// Update favicon based on Nexus state +const checkFaviconState = () => { + let state = 'default'; + if (NEXUS.chat && NEXUS.chat.isActive) { + state = 'chat'; + } else if (NEXUS.player && NEXUS.player.nearPortal) { + state = 'portal'; + } + updateFavicon(state); +}; + +// Update favicon periodically for pulsing effect +setInterval(checkFaviconState, 100); -- 2.43.0