diff --git a/app.js b/app.js index 4902f2f..81fe811 100644 --- a/app.js +++ b/app.js @@ -260,6 +260,120 @@ function animate() { animate(); +// === ABOUT PANEL === +const aboutPanel = document.getElementById('about-panel'); +const aboutToggleBtn = document.getElementById('about-toggle'); +const aboutCloseBtn = document.getElementById('about-close'); +const statUptime = document.getElementById('stat-uptime'); +const statPRs = document.getElementById('stat-prs'); +const statAgents = document.getElementById('stat-agents'); + +const sessionStart = Date.now(); + +/** + * Formats elapsed milliseconds as HH:MM:SS. + * @param {number} ms + * @returns {string} + */ +function formatUptime(ms) { + const s = Math.floor(ms / 1000); + const h = Math.floor(s / 3600); + const m = Math.floor((s % 3600) / 60); + const sec = s % 60; + return [h, m, sec].map((n) => String(n).padStart(2, '0')).join(':'); +} + +let uptimeInterval = null; + +/** + * Fetches merged PR count and agents active from Gitea API. + */ +async function fetchNexusStats() { + // Merged PRs + try { + const res = await fetch( + 'http://143.198.27.163:3000/api/v1/repos/Timmy_Foundation/the-nexus/pulls?state=closed&limit=50&page=1', + { headers: { 'Authorization': 'token dc0517a965226b7a0c5ffdd961b1ba26521ac592' } } + ); + if (res.ok) { + const pulls = await res.json(); + const merged = pulls.filter((/** @type {{merged: boolean}} */ p) => p.merged).length; + // Also check x-total-count if full count matters; for now sum pages heuristic + const total = parseInt(res.headers.get('x-total-count') || '0', 10); + // Re-fetch all if there are more than 50 + if (total > 50) { + const res2 = await fetch( + `http://143.198.27.163:3000/api/v1/repos/Timmy_Foundation/the-nexus/pulls?state=closed&limit=${total}&page=1`, + { headers: { 'Authorization': 'token dc0517a965226b7a0c5ffdd961b1ba26521ac592' } } + ); + if (res2.ok) { + const all = await res2.json(); + const mergedAll = all.filter((/** @type {{merged: boolean}} */ p) => p.merged).length; + if (statPRs) statPRs.textContent = String(mergedAll); + } + } else { + if (statPRs) statPRs.textContent = String(merged); + } + } + } catch (_) { + if (statPRs) statPRs.textContent = '?'; + } + + // Agents active — count open issues with label 'agent' or derive from active ws connections + // Currently Timmy is the sole active sovereign agent; others are pending issues + if (statAgents) statAgents.textContent = '1 (Timmy)'; +} + +/** + * Opens the about panel and starts the uptime ticker. + */ +function openAboutPanel() { + if (!aboutPanel) return; + aboutPanel.classList.add('visible'); + aboutPanel.setAttribute('aria-hidden', 'false'); + if (statUptime) statUptime.textContent = formatUptime(Date.now() - sessionStart); + uptimeInterval = setInterval(() => { + if (statUptime) statUptime.textContent = formatUptime(Date.now() - sessionStart); + }, 1000); + fetchNexusStats(); +} + +/** + * Closes the about panel. + */ +function closeAboutPanel() { + if (!aboutPanel) return; + aboutPanel.classList.remove('visible'); + aboutPanel.setAttribute('aria-hidden', 'true'); + if (uptimeInterval) { + clearInterval(uptimeInterval); + uptimeInterval = null; + } +} + +let aboutOpen = false; + +if (aboutToggleBtn) { + aboutToggleBtn.addEventListener('click', () => { + aboutOpen = !aboutOpen; + if (aboutOpen) openAboutPanel(); else closeAboutPanel(); + }); +} + +if (aboutCloseBtn) { + aboutCloseBtn.addEventListener('click', () => { + aboutOpen = false; + closeAboutPanel(); + }); +} + +document.addEventListener('keydown', (e) => { + if (e.key === 'i' || e.key === 'I') { + aboutOpen = !aboutOpen; + if (aboutOpen) openAboutPanel(); else closeAboutPanel(); + } +}); + // === DEBUG MODE === let debugMode = false; diff --git a/index.html b/index.html index 6d4000f..bb17efe 100644 --- a/index.html +++ b/index.html @@ -33,9 +33,35 @@ + + +
+