From 29f48e124e9b6b10bc49d3d8b3bef35adb94bd3e Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Sat, 11 Apr 2026 00:20:06 +0000 Subject: [PATCH] fix: [PORTAL] Add honest local Bannerlord readiness/status to the Nexus Closes #1193 Automated squash merge by mimo swarm. --- app.js | 79 ++++++++++++++++++++++++++++-- index.html | 1 + portals.json | 13 +++-- style.css | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 223 insertions(+), 6 deletions(-) diff --git a/app.js b/app.js index 10156247..a1b05585 100644 --- a/app.js +++ b/app.js @@ -2498,6 +2498,15 @@ function activatePortal(portal) { overlay.style.display = 'flex'; + // Readiness detail for game-world portals + const readinessEl = document.getElementById('portal-readiness-detail'); + if (portal.config.portal_type === 'game-world' && portal.config.readiness_steps) { + renderReadinessDetail(readinessEl, portal.config); + readinessEl.style.display = 'block'; + } else { + readinessEl.style.display = 'none'; + } + if (portal.config.destination && portal.config.destination.url) { redirectBox.style.display = 'block'; errorBox.style.display = 'none'; @@ -2519,6 +2528,37 @@ function activatePortal(portal) { } } +// ═══ READINESS RENDERING ═══ +function renderReadinessDetail(container, config) { + const steps = config.readiness_steps || {}; + const stepKeys = ['downloaded', 'runtime_ready', 'launched', 'harness_bridged']; + let html = '
READINESS PIPELINE
'; + + let firstUndone = true; + stepKeys.forEach(key => { + const step = steps[key]; + if (!step) return; + const cls = step.done ? 'done' : (firstUndone ? 'current' : ''); + if (!step.done) firstUndone = false; + html += `
+ + ${step.label || key} +
`; + }); + + if (config.blocked_reason) { + html += `
⚠ ${config.blocked_reason}
`; + } + + const doneCount = stepKeys.filter(k => steps[k]?.done).length; + const canEnter = doneCount === stepKeys.length && config.destination?.url; + if (!canEnter) { + html += `
Cannot enter yet — ${stepKeys.length - doneCount} step${stepKeys.length - doneCount > 1 ? 's' : ''} remaining.
`; + } + + container.innerHTML = html; +} + function closePortalOverlay() { portalOverlayActive = false; document.getElementById('portal-overlay').style.display = 'none'; @@ -2599,12 +2639,42 @@ function populateAtlas() { const statusClass = `status-${config.status || 'online'}`; + // Build readiness section for game-world portals + let readinessHtml = ''; + if (config.portal_type === 'game-world' && config.readiness_steps) { + const stepKeys = ['downloaded', 'runtime_ready', 'launched', 'harness_bridged']; + const steps = config.readiness_steps; + const doneCount = stepKeys.filter(k => steps[k]?.done).length; + const pct = Math.round((doneCount / stepKeys.length) * 100); + const barColor = config.color || '#ffd700'; + + readinessHtml = `
+
+
+
+
`; + let firstUndone = true; + stepKeys.forEach(key => { + const step = steps[key]; + if (!step) return; + const cls = step.done ? 'done' : (firstUndone ? 'current' : ''); + if (!step.done) firstUndone = false; + readinessHtml += `${step.label || key}`; + }); + readinessHtml += '
'; + if (config.blocked_reason) { + readinessHtml += `
⚠ ${config.blocked_reason}
`; + } + readinessHtml += '
'; + } + card.innerHTML = `
${config.name}
-
${config.status || 'ONLINE'}
+
${config.readiness_state || config.status || 'ONLINE'}
${config.description}
+ ${readinessHtml}

MORROWIND

The Vvardenfell harness. Ash storms and ancient mysteries.

+
REDIRECTING IN
5
diff --git a/portals.json b/portals.json index 6b7e2870..b870c62d 100644 --- a/portals.json +++ b/portals.json @@ -17,7 +17,7 @@ "id": "bannerlord", "name": "Bannerlord", "description": "Calradia battle harness. Massive armies, tactical command.", - "status": "active", + "status": "downloaded", "color": "#ffd700", "position": { "x": -15, "y": 0, "z": -10 }, "rotation": { "y": 0.5 }, @@ -25,13 +25,20 @@ "world_category": "strategy-rpg", "environment": "production", "access_mode": "operator", - "readiness_state": "active", + "readiness_state": "downloaded", + "readiness_steps": { + "downloaded": { "label": "Downloaded", "done": true }, + "runtime_ready": { "label": "Runtime Ready", "done": false }, + "launched": { "label": "Launched", "done": false }, + "harness_bridged": { "label": "Harness Bridged", "done": false } + }, + "blocked_reason": null, "telemetry_source": "hermes-harness:bannerlord", "owner": "Timmy", "app_id": 261550, "window_title": "Mount & Blade II: Bannerlord", "destination": { - "url": "https://bannerlord.timmy.foundation", + "url": null, "type": "harness", "action_label": "Enter Calradia", "params": { "world": "calradia" } diff --git a/style.css b/style.css index e171946f..1a459a7e 100644 --- a/style.css +++ b/style.css @@ -422,6 +422,142 @@ canvas#nexus-canvas { .status-online { background: rgba(74, 240, 192, 0.2); color: var(--color-primary); border: 1px solid var(--color-primary); } .status-standby { background: rgba(255, 215, 0, 0.2); color: var(--color-gold); border: 1px solid var(--color-gold); } .status-offline { background: rgba(255, 68, 102, 0.2); color: var(--color-danger); border: 1px solid var(--color-danger); } +.status-active { background: rgba(74, 240, 192, 0.2); color: var(--color-primary); border: 1px solid var(--color-primary); } +.status-blocked { background: rgba(255, 68, 102, 0.3); color: #ff4466; border: 1px solid #ff4466; } +.status-downloaded { background: rgba(100, 149, 237, 0.2); color: #6495ed; border: 1px solid #6495ed; } +.status-runtime_ready { background: rgba(255, 165, 0, 0.2); color: #ffa500; border: 1px solid #ffa500; } +.status-launched { background: rgba(255, 215, 0, 0.2); color: var(--color-gold); border: 1px solid var(--color-gold); } +.status-harness_bridged { background: rgba(74, 240, 192, 0.2); color: var(--color-primary); border: 1px solid var(--color-primary); } + +/* Readiness Progress Bar (atlas card) */ +.atlas-card-readiness { + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid rgba(255,255,255,0.06); +} +.readiness-bar-track { + width: 100%; + height: 4px; + background: rgba(255,255,255,0.08); + border-radius: 2px; + overflow: hidden; + margin-bottom: 6px; +} +.readiness-bar-fill { + height: 100%; + border-radius: 2px; + transition: width 0.4s ease; +} +.readiness-steps-mini { + display: flex; + gap: 6px; + font-size: 9px; + font-family: var(--font-body); + letter-spacing: 0.05em; + color: var(--color-text-muted); +} +.readiness-step { + padding: 1px 5px; + border-radius: 2px; + background: rgba(255,255,255,0.04); +} +.readiness-step.done { + background: rgba(74, 240, 192, 0.15); + color: var(--color-primary); +} +.readiness-step.current { + background: rgba(255, 215, 0, 0.15); + color: var(--color-gold); +} +.atlas-card-blocked { + margin-top: 6px; + font-size: 10px; + color: #ff4466; + font-family: var(--font-body); +} + +/* Readiness Detail (portal overlay) */ +.portal-readiness-detail { + margin-top: 16px; + padding: 12px 16px; + background: rgba(0,0,0,0.3); + border: 1px solid rgba(255,255,255,0.08); + border-radius: 4px; +} +.portal-readiness-title { + font-family: var(--font-display); + font-size: 10px; + letter-spacing: 0.15em; + color: var(--color-text-muted); + margin-bottom: 10px; + text-transform: uppercase; +} +.portal-readiness-step { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 0; + font-family: var(--font-body); + font-size: 11px; + color: rgba(255,255,255,0.4); +} +.portal-readiness-step .step-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: rgba(255,255,255,0.15); + flex-shrink: 0; +} +.portal-readiness-step.done .step-dot { + background: var(--color-primary); + box-shadow: 0 0 6px var(--color-primary); +} +.portal-readiness-step.done { + color: var(--color-primary); +} +.portal-readiness-step.current .step-dot { + background: var(--color-gold); + box-shadow: 0 0 6px var(--color-gold); + animation: pulse-dot 1.5s ease-in-out infinite; +} +.portal-readiness-step.current { + color: #fff; +} +@keyframes pulse-dot { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} +.portal-readiness-blocked { + margin-top: 8px; + padding: 6px 10px; + background: rgba(255, 68, 102, 0.1); + border: 1px solid rgba(255, 68, 102, 0.3); + border-radius: 3px; + font-size: 11px; + color: #ff4466; + font-family: var(--font-body); +} +.portal-readiness-hint { + margin-top: 8px; + font-size: 10px; + color: var(--color-text-muted); + font-family: var(--font-body); + font-style: italic; +} + +/* HUD Status for readiness states */ +.hud-status-item.downloaded .status-dot { background: #6495ed; box-shadow: 0 0 5px #6495ed; } +.hud-status-item.runtime_ready .status-dot { background: #ffa500; box-shadow: 0 0 5px #ffa500; } +.hud-status-item.launched .status-dot { background: var(--color-gold); box-shadow: 0 0 5px var(--color-gold); } +.hud-status-item.harness_bridged .status-dot { background: var(--color-primary); box-shadow: 0 0 5px var(--color-primary); } +.hud-status-item.blocked .status-dot { background: #ff4466; box-shadow: 0 0 5px #ff4466; } +.hud-status-item.downloaded .status-label, +.hud-status-item.runtime_ready .status-label, +.hud-status-item.launched .status-label, +.hud-status-item.harness_bridged .status-label, +.hud-status-item.blocked .status-label { + color: #fff; +} .atlas-card-desc { font-size: 12px;