[claude] Add Nexus UI component prototypes — portal wall, agent presence, briefing (#749) (#810)
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled

This commit was merged in pull request #810.
This commit is contained in:
2026-04-04 01:41:13 +00:00
parent 8645798ed4
commit f6aa3bdbf6
4 changed files with 1401 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
# Vibe Code Prototype Evaluation — Issue #749
## Components Prototyped
| File | Component | Status |
|------|-----------|--------|
| `portal-status-wall.html` | Portal Status Wall (#714) | ✅ Done |
| `agent-presence-panel.html` | Agent Presence Panel | ✅ Done |
| `heartbeat-briefing-panel.html` | Heartbeat / Morning Briefing (#698) | ✅ Done |
---
## Design Language Evaluation
All three prototypes were hand-authored against the Nexus design system
(`style.css` on `main`) to establish a baseline. Vibe Code tools
(AI Studio, Stitch) can accelerate iteration once this baseline exists.
### What matches the dark space / holographic language
- **Palette**: `#050510` bg, `#4af0c0` primary teal, `#7b5cff` secondary purple,
danger red `#ff4466`, warning amber `#ffaa22`, gold `#ffd700`
- **Typography**: Orbitron for display/titles, JetBrains Mono for body
- **Glassmorphism panels**: `backdrop-filter: blur(16px)` + semi-transparent surfaces
- **Subtle glow**: `box-shadow` on active/thinking avatars, primary pulse animations
- **Micro-animations**: heartbeat bars, pulsing dots, thinking-pulse ring — all match
the cadence of existing loading-screen animations
### What Vibe Code tools do well
- Rapid layout scaffolding — grid/flex structures appear in seconds
- Color palette application once a design token list is pasted
- Common UI patterns (cards, badges, status dots) generated accurately
- Good at iterating on a component when given the existing CSS vars as context
### Where manual work is needed
- **Semantic naming**: generated class names tend to be generic (`container`, `box`)
rather than domain-specific (`portal-card`, `agent-avatar`) — rename after generation
- **Animation polish**: Vibe Code generates basic `@keyframes` but the specific
easing curves and timing that match the Nexus "soul" require hand-tuning
- **State modeling**: status variants (online/warning/offline/locked) and
conditional styling need explicit spec; tools generate happy-path only
- **Domain vocabulary**: portal IDs, agent names, bark text — all placeholder content
needs replacement with real Nexus data model values
- **Responsive / overlay integration**: these are standalone HTML prototypes;
wiring into the Three.js canvas overlay system requires manual work
---
## Patterns extracted for reuse
```css
/* Status stripe — left edge on panel cards */
.portal-card::before {
content: '';
position: absolute;
top: 0; left: 0;
width: 3px; height: 100%;
border-radius: var(--panel-radius) 0 0 var(--panel-radius);
}
/* Avatar glow for thinking state */
.agent-avatar.thinking {
animation: think-pulse 2s ease-in-out infinite;
}
@keyframes think-pulse {
0%, 100% { box-shadow: 0 0 8px rgba(123, 92, 255, 0.3); }
50% { box-shadow: 0 0 18px rgba(123, 92, 255, 0.6); }
}
/* Section header divider */
.section-label::after {
content: '';
flex: 1;
height: 1px;
background: var(--color-border);
}
/* Latency / progress track */
.latency-track {
height: 3px;
background: rgba(255,255,255,0.06);
border-radius: 2px;
overflow: hidden;
}
```
---
## Next Steps
1. Wire `portal-status-wall` to real `portals.json` + websocket updates (issue #714)
2. Wire `agent-presence-panel` to Hermes heartbeat stream (issue #698)
3. Wire `heartbeat-briefing-panel` to daily summary generator
4. Integrate as Three.js CSS2DObject overlays on Nexus canvas (issue #686 / #687)
5. Try Stitch (`labs.google/stitch`) for visual design iteration on the portal card shape

View File

@@ -0,0 +1,432 @@
<!DOCTYPE html>
<!--
NEXUS COMPONENT PROTOTYPE: Agent Presence Panel
Refs: #749 (Vibe Code prototype)
Design: dark space / holographic — matches Nexus design system
Shows real-time agent location/status in the Nexus world
-->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent Presence Panel — Nexus Component</title>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600&family=Orbitron:wght@400;600;700&display=swap" rel="stylesheet">
<style>
:root {
--color-bg: #050510;
--color-surface: rgba(10, 15, 40, 0.85);
--color-surface-deep: rgba(5, 8, 25, 0.9);
--color-border: rgba(74, 240, 192, 0.2);
--color-border-bright: rgba(74, 240, 192, 0.5);
--color-text: #e0f0ff;
--color-text-muted: #8a9ab8;
--color-primary: #4af0c0;
--color-secondary: #7b5cff;
--color-danger: #ff4466;
--color-warning: #ffaa22;
--color-gold: #ffd700;
--font-display: 'Orbitron', sans-serif;
--font-body: 'JetBrains Mono', monospace;
--panel-blur: 16px;
--panel-radius: 8px;
--transition: 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--color-bg);
font-family: var(--font-body);
color: var(--color-text);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
}
/* === PRESENCE PANEL === */
.presence-panel {
width: 340px;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--panel-radius);
backdrop-filter: blur(var(--panel-blur));
overflow: hidden;
}
/* Header */
.panel-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid var(--color-border);
background: rgba(74, 240, 192, 0.03);
}
.panel-head-left {
display: flex;
align-items: center;
gap: 8px;
}
.panel-title {
font-family: var(--font-display);
font-size: 11px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--color-primary);
}
.live-indicator {
display: flex;
align-items: center;
gap: 5px;
font-size: 10px;
color: var(--color-text-muted);
}
.live-dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: var(--color-primary);
animation: blink 1.4s ease-in-out infinite;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.2; }
}
.agent-count {
font-family: var(--font-display);
font-size: 11px;
color: var(--color-text-muted);
}
.agent-count span {
color: var(--color-primary);
}
/* Agent List */
.agent-list {
display: flex;
flex-direction: column;
}
.agent-row {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
border-bottom: 1px solid rgba(74, 240, 192, 0.06);
transition: background var(--transition);
cursor: default;
}
.agent-row:last-child { border-bottom: none; }
.agent-row:hover { background: rgba(74, 240, 192, 0.03); }
/* Avatar */
.agent-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
border: 1.5px solid var(--color-border);
background: var(--color-surface-deep);
display: flex;
align-items: center;
justify-content: center;
font-family: var(--font-display);
font-size: 13px;
font-weight: 700;
flex-shrink: 0;
position: relative;
}
.agent-avatar.active {
border-color: var(--color-primary);
box-shadow: 0 0 10px rgba(74, 240, 192, 0.25);
}
.agent-avatar.thinking {
border-color: var(--color-secondary);
animation: think-pulse 2s ease-in-out infinite;
}
@keyframes think-pulse {
0%, 100% { box-shadow: 0 0 8px rgba(123, 92, 255, 0.3); }
50% { box-shadow: 0 0 18px rgba(123, 92, 255, 0.6); }
}
.agent-avatar.idle {
border-color: var(--color-border);
opacity: 0.7;
}
.status-pip {
position: absolute;
bottom: 1px;
right: 1px;
width: 9px;
height: 9px;
border-radius: 50%;
border: 1.5px solid var(--color-bg);
}
.status-pip.active { background: var(--color-primary); }
.status-pip.thinking { background: var(--color-secondary); }
.status-pip.idle { background: var(--color-text-muted); }
.status-pip.offline { background: var(--color-danger); }
/* Agent info */
.agent-info {
flex: 1;
min-width: 0;
}
.agent-name {
font-size: 12px;
font-weight: 600;
color: var(--color-text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.agent-location {
font-size: 11px;
color: var(--color-text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-top: 2px;
}
.agent-location .loc-icon {
color: var(--color-primary);
margin-right: 3px;
opacity: 0.7;
}
.agent-bark {
font-size: 10px;
color: var(--color-text-muted);
font-style: italic;
margin-top: 3px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
opacity: 0.8;
}
/* Right-side meta */
.agent-meta-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
flex-shrink: 0;
}
.agent-state-tag {
font-size: 9px;
letter-spacing: 0.1em;
text-transform: uppercase;
padding: 2px 6px;
border-radius: 3px;
font-weight: 600;
}
.tag-active { color: var(--color-primary); background: rgba(74,240,192,0.12); }
.tag-thinking { color: var(--color-secondary); background: rgba(123,92,255,0.12); }
.tag-idle { color: var(--color-text-muted); background: rgba(138,154,184,0.1); }
.tag-offline { color: var(--color-danger); background: rgba(255,68,102,0.12); }
.agent-since {
font-size: 10px;
color: var(--color-text-muted);
}
/* Footer */
.panel-foot {
padding: 10px 16px;
border-top: 1px solid var(--color-border);
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(74, 240, 192, 0.02);
}
.foot-stat {
font-size: 10px;
color: var(--color-text-muted);
letter-spacing: 0.06em;
}
.foot-stat span {
color: var(--color-primary);
}
.world-selector {
font-family: var(--font-body);
font-size: 10px;
background: transparent;
border: 1px solid var(--color-border);
color: var(--color-text-muted);
border-radius: 4px;
padding: 3px 8px;
cursor: pointer;
outline: none;
transition: border-color var(--transition);
}
.world-selector:hover, .world-selector:focus {
border-color: var(--color-border-bright);
color: var(--color-text);
}
</style>
</head>
<body>
<div class="presence-panel">
<!-- Header -->
<div class="panel-head">
<div class="panel-head-left">
<div class="live-dot"></div>
<span class="panel-title">Agents</span>
</div>
<div class="agent-count"><span>4</span> / 6 online</div>
</div>
<!-- Agent list -->
<div class="agent-list">
<!-- Timmy — active -->
<div class="agent-row">
<div class="agent-avatar active" style="color:var(--color-primary)">T
<div class="status-pip active"></div>
</div>
<div class="agent-info">
<div class="agent-name">Timmy</div>
<div class="agent-location">
<span class="loc-icon"></span>Central Hub — Nexus Core
</div>
<div class="agent-bark">"Let's get the portal wall running."</div>
</div>
<div class="agent-meta-right">
<span class="agent-state-tag tag-active">active</span>
<span class="agent-since">6m</span>
</div>
</div>
<!-- Claude — thinking -->
<div class="agent-row">
<div class="agent-avatar thinking" style="color:#a08cff">C
<div class="status-pip thinking"></div>
</div>
<div class="agent-info">
<div class="agent-name">Claude</div>
<div class="agent-location">
<span class="loc-icon"></span>Workshop — claude/issue-749
</div>
<div class="agent-bark">"Building nexus/components/ ..."</div>
</div>
<div class="agent-meta-right">
<span class="agent-state-tag tag-thinking">thinking</span>
<span class="agent-since">2m</span>
</div>
</div>
<!-- Gemini — active -->
<div class="agent-row">
<div class="agent-avatar active" style="color:#4285f4">G
<div class="status-pip active"></div>
</div>
<div class="agent-info">
<div class="agent-name">Gemini</div>
<div class="agent-location">
<span class="loc-icon"></span>Observatory — Sovereignty Sweep
</div>
<div class="agent-bark">"Audit pass in progress."</div>
</div>
<div class="agent-meta-right">
<span class="agent-state-tag tag-active">active</span>
<span class="agent-since">1h</span>
</div>
</div>
<!-- Hermes — active (system) -->
<div class="agent-row">
<div class="agent-avatar active" style="color:var(--color-gold)">H
<div class="status-pip active"></div>
</div>
<div class="agent-info">
<div class="agent-name">Hermes <span style="font-size:9px;color:var(--color-text-muted)">[sys]</span></div>
<div class="agent-location">
<span class="loc-icon"></span>Comm Bridge — always-on
</div>
<div class="agent-bark">"Routing 3 active sessions."</div>
</div>
<div class="agent-meta-right">
<span class="agent-state-tag tag-active">active</span>
<span class="agent-since">6h</span>
</div>
</div>
<!-- GPT-4 — idle -->
<div class="agent-row">
<div class="agent-avatar idle" style="color:#10a37f">O
<div class="status-pip idle"></div>
</div>
<div class="agent-info">
<div class="agent-name">GPT-4o</div>
<div class="agent-location">
<span class="loc-icon" style="opacity:0.4"></span>Waiting Room
</div>
<div class="agent-bark" style="opacity:0.5">Idle — awaiting task</div>
</div>
<div class="agent-meta-right">
<span class="agent-state-tag tag-idle">idle</span>
<span class="agent-since">28m</span>
</div>
</div>
<!-- OpenClaw — offline -->
<div class="agent-row">
<div class="agent-avatar idle" style="color:var(--color-danger);opacity:0.5">X
<div class="status-pip offline"></div>
</div>
<div class="agent-info">
<div class="agent-name" style="opacity:0.5">OpenClaw</div>
<div class="agent-location" style="opacity:0.4">
<span class="loc-icon"></span>
</div>
<div class="agent-bark" style="opacity:0.35">Last seen 2h ago</div>
</div>
<div class="agent-meta-right">
<span class="agent-state-tag tag-offline">offline</span>
<span class="agent-since" style="opacity:0.4">2h</span>
</div>
</div>
</div><!-- /agent-list -->
<!-- Footer -->
<div class="panel-foot">
<span class="foot-stat">World: <span>Nexus Core</span></span>
<select class="world-selector">
<option>All worlds</option>
<option selected>Nexus Core</option>
<option>Evennia MUD</option>
<option>Bannerlord</option>
</select>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,394 @@
<!DOCTYPE html>
<!--
NEXUS COMPONENT PROTOTYPE: Heartbeat / Morning Briefing Panel
Refs: #749 (Vibe Code prototype), #698 (heartbeat/morning briefing)
Design: dark space / holographic — matches Nexus design system
Shows Timmy's daily brief: system vitals, pending actions, world state
-->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Heartbeat Briefing — Nexus Component</title>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600&family=Orbitron:wght@400;600;700&display=swap" rel="stylesheet">
<style>
:root {
--color-bg: #050510;
--color-surface: rgba(10, 15, 40, 0.85);
--color-border: rgba(74, 240, 192, 0.2);
--color-border-bright: rgba(74, 240, 192, 0.5);
--color-text: #e0f0ff;
--color-text-muted: #8a9ab8;
--color-primary: #4af0c0;
--color-primary-dim: rgba(74, 240, 192, 0.12);
--color-secondary: #7b5cff;
--color-danger: #ff4466;
--color-warning: #ffaa22;
--color-gold: #ffd700;
--font-display: 'Orbitron', sans-serif;
--font-body: 'JetBrains Mono', monospace;
--panel-blur: 16px;
--panel-radius: 8px;
--transition: 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--color-bg);
font-family: var(--font-body);
color: var(--color-text);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
}
/* === BRIEFING PANEL === */
.briefing-panel {
width: 480px;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--panel-radius);
backdrop-filter: blur(var(--panel-blur));
overflow: hidden;
}
/* Banner */
.briefing-banner {
padding: 20px 20px 16px;
background: linear-gradient(135deg, rgba(74,240,192,0.05) 0%, rgba(123,92,255,0.05) 100%);
border-bottom: 1px solid var(--color-border);
position: relative;
overflow: hidden;
}
.briefing-banner::after {
content: '';
position: absolute;
top: 0; right: 0; bottom: 0;
width: 120px;
background: radial-gradient(ellipse at right center, rgba(74,240,192,0.06) 0%, transparent 70%);
pointer-events: none;
}
.briefing-date {
font-size: 10px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--color-text-muted);
margin-bottom: 6px;
}
.briefing-title {
font-family: var(--font-display);
font-size: 18px;
font-weight: 700;
letter-spacing: 0.08em;
color: var(--color-text);
line-height: 1.2;
}
.briefing-title span {
color: var(--color-primary);
}
.briefing-subtitle {
font-size: 12px;
color: var(--color-text-muted);
margin-top: 4px;
}
/* Vital stats row */
.vitals-row {
display: flex;
gap: 0;
border-bottom: 1px solid var(--color-border);
}
.vital {
flex: 1;
padding: 14px 16px;
display: flex;
flex-direction: column;
gap: 4px;
border-right: 1px solid var(--color-border);
transition: background var(--transition);
}
.vital:last-child { border-right: none; }
.vital:hover { background: rgba(74,240,192,0.02); }
.vital-value {
font-family: var(--font-display);
font-size: 22px;
font-weight: 700;
line-height: 1;
}
.vital-label {
font-size: 10px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--color-text-muted);
}
.vital-delta {
font-size: 10px;
margin-top: 2px;
}
.delta-up { color: var(--color-primary); }
.delta-down { color: var(--color-danger); }
.delta-same { color: var(--color-text-muted); }
/* Sections */
.briefing-section {
padding: 14px 20px;
border-bottom: 1px solid var(--color-border);
}
.briefing-section:last-child { border-bottom: none; }
.section-label {
font-size: 10px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--color-text-muted);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 8px;
}
.section-label::after {
content: '';
flex: 1;
height: 1px;
background: var(--color-border);
}
/* Action items */
.action-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.action-item {
display: flex;
align-items: flex-start;
gap: 10px;
font-size: 12px;
line-height: 1.4;
}
.action-bullet {
width: 16px;
height: 16px;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
font-size: 9px;
font-weight: 700;
flex-shrink: 0;
margin-top: 1px;
}
.bullet-urgent { background: rgba(255,68,102,0.2); color: var(--color-danger); }
.bullet-normal { background: rgba(74,240,192,0.12); color: var(--color-primary); }
.bullet-low { background: rgba(138,154,184,0.1); color: var(--color-text-muted); }
.action-text { color: var(--color-text); }
.action-text .tag {
font-size: 10px;
padding: 1px 5px;
border-radius: 3px;
margin-left: 4px;
vertical-align: middle;
}
.tag-issue { background: rgba(74,240,192,0.1); color: var(--color-primary); }
.tag-pr { background: rgba(123,92,255,0.1); color: var(--color-secondary); }
.tag-world { background: rgba(255,170,34,0.1); color: var(--color-warning); }
/* System narrative */
.narrative {
font-size: 12px;
line-height: 1.7;
color: var(--color-text-muted);
font-style: italic;
border-left: 2px solid var(--color-primary-dim);
padding-left: 12px;
}
.narrative strong {
color: var(--color-text);
font-style: normal;
}
/* Footer */
.briefing-footer {
padding: 10px 20px;
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(74, 240, 192, 0.02);
}
.footer-note {
font-size: 10px;
color: var(--color-text-muted);
}
.refresh-btn {
font-family: var(--font-body);
font-size: 10px;
letter-spacing: 0.1em;
text-transform: uppercase;
background: transparent;
border: 1px solid var(--color-border);
color: var(--color-text-muted);
padding: 4px 10px;
border-radius: 4px;
cursor: pointer;
transition: all var(--transition);
}
.refresh-btn:hover {
border-color: var(--color-border-bright);
color: var(--color-primary);
}
/* Heartbeat animation in banner */
.hb-line {
position: absolute;
bottom: 8px;
right: 20px;
display: flex;
align-items: center;
gap: 1px;
opacity: 0.3;
}
.hb-bar {
width: 2px;
background: var(--color-primary);
border-radius: 1px;
animation: hb 1.2s ease-in-out infinite;
}
.hb-bar:nth-child(1) { height: 4px; animation-delay: 0s; }
.hb-bar:nth-child(2) { height: 12px; animation-delay: 0.1s; }
.hb-bar:nth-child(3) { height: 20px; animation-delay: 0.2s; }
.hb-bar:nth-child(4) { height: 8px; animation-delay: 0.3s; }
.hb-bar:nth-child(5) { height: 4px; animation-delay: 0.4s; }
.hb-bar:nth-child(6) { height: 16px; animation-delay: 0.5s; }
.hb-bar:nth-child(7) { height: 6px; animation-delay: 0.6s; }
.hb-bar:nth-child(8) { height: 4px; animation-delay: 0.7s; }
@keyframes hb {
0%, 100% { opacity: 0.3; }
50% { opacity: 1; }
}
</style>
</head>
<body>
<div class="briefing-panel">
<!-- Banner -->
<div class="briefing-banner">
<div class="briefing-date">Friday · 04 Apr 2026 · 08:00 UTC</div>
<div class="briefing-title">Morning <span>Briefing</span></div>
<div class="briefing-subtitle">Nexus Core — Daily state summary for Timmy</div>
<div class="hb-line">
<div class="hb-bar"></div><div class="hb-bar"></div><div class="hb-bar"></div>
<div class="hb-bar"></div><div class="hb-bar"></div><div class="hb-bar"></div>
<div class="hb-bar"></div><div class="hb-bar"></div>
</div>
</div>
<!-- Vitals -->
<div class="vitals-row">
<div class="vital">
<div class="vital-value" style="color:var(--color-primary)">4</div>
<div class="vital-label">Agents Online</div>
<div class="vital-delta delta-up">▲ +1 since yesterday</div>
</div>
<div class="vital">
<div class="vital-value" style="color:var(--color-warning)">7</div>
<div class="vital-label">Open Issues</div>
<div class="vital-delta delta-down">2 closed</div>
</div>
<div class="vital">
<div class="vital-value" style="color:var(--color-secondary)">2</div>
<div class="vital-label">Open PRs</div>
<div class="vital-delta delta-same">— unchanged</div>
</div>
<div class="vital">
<div class="vital-value" style="color:var(--color-gold)">97%</div>
<div class="vital-label">System Health</div>
<div class="vital-delta delta-up">▲ Satflow recovering</div>
</div>
</div>
<!-- Priority actions -->
<div class="briefing-section">
<div class="section-label">Priority Actions</div>
<div class="action-list">
<div class="action-item">
<div class="action-bullet bullet-urgent">!</div>
<div class="action-text">
Satflow portal degraded — 87 queued transactions pending review
<span class="tag tag-world">ECONOMY</span>
</div>
</div>
<div class="action-item">
<div class="action-bullet bullet-normal"></div>
<div class="action-text">
Claude: PR for #749 (Vibe Code components) awaiting review
<span class="tag tag-pr">PR #52</span>
</div>
</div>
<div class="action-item">
<div class="action-bullet bullet-normal"></div>
<div class="action-text">
Bannerlord portal offline — reconnect or close issue
<span class="tag tag-issue">#722</span>
</div>
</div>
<div class="action-item">
<div class="action-bullet bullet-low">·</div>
<div class="action-text">
Migration backlog: 3 legacy Matrix components unaudited
<span class="tag tag-issue">#685</span>
</div>
</div>
</div>
</div>
<!-- Narrative / system voice -->
<div class="briefing-section">
<div class="section-label">System Pulse</div>
<div class="narrative">
Good morning. The Nexus ran <strong>overnight without incident</strong>
Hermes routed 214 messages, Archive wrote 88 new memories.
Satflow hit a <strong>rate-limit wall</strong> at 03:14 UTC; queue is draining slowly.
Gemini completed its sovereignty sweep; no critical findings.
Claude is mid-sprint on <strong>issue #749</strong> — component prototypes landing today.
</div>
</div>
<!-- Footer -->
<div class="briefing-footer">
<span class="footer-note">Generated at 08:00 UTC · Next briefing 20:00 UTC</span>
<button class="refresh-btn">Refresh</button>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,478 @@
<!DOCTYPE html>
<!--
NEXUS COMPONENT PROTOTYPE: Portal Status Wall
Refs: #749 (Vibe Code prototype), #714 (portal status)
Design: dark space / holographic — matches Nexus design system
-->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Portal Status Wall — Nexus Component</title>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600&family=Orbitron:wght@400;600;700&display=swap" rel="stylesheet">
<style>
:root {
--color-bg: #050510;
--color-surface: rgba(10, 15, 40, 0.85);
--color-border: rgba(74, 240, 192, 0.2);
--color-border-bright:rgba(74, 240, 192, 0.5);
--color-text: #e0f0ff;
--color-text-muted: #8a9ab8;
--color-primary: #4af0c0;
--color-primary-dim: rgba(74, 240, 192, 0.15);
--color-secondary: #7b5cff;
--color-danger: #ff4466;
--color-warning: #ffaa22;
--color-gold: #ffd700;
--font-display: 'Orbitron', sans-serif;
--font-body: 'JetBrains Mono', monospace;
--panel-blur: 16px;
--panel-radius: 8px;
--transition: 200ms cubic-bezier(0.16, 1, 0.3, 1);
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--color-bg);
font-family: var(--font-body);
color: var(--color-text);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
}
/* === PORTAL STATUS WALL === */
.portal-wall {
width: 100%;
max-width: 900px;
}
.panel-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.panel-title {
font-family: var(--font-display);
font-size: 13px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--color-primary);
}
.panel-title-bar {
flex: 1;
height: 1px;
background: linear-gradient(90deg, var(--color-border-bright) 0%, transparent 100%);
}
.pulse-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--color-primary);
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; box-shadow: 0 0 6px var(--color-primary); }
50% { opacity: 0.4; box-shadow: none; }
}
/* Portal Grid */
.portal-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 12px;
}
.portal-card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--panel-radius);
padding: 16px;
backdrop-filter: blur(var(--panel-blur));
position: relative;
overflow: hidden;
transition: border-color var(--transition), box-shadow var(--transition);
cursor: default;
}
.portal-card:hover {
border-color: var(--color-border-bright);
box-shadow: 0 0 20px rgba(74, 240, 192, 0.08);
}
/* Status indicator stripe */
.portal-card::before {
content: '';
position: absolute;
top: 0; left: 0;
width: 3px; height: 100%;
border-radius: var(--panel-radius) 0 0 var(--panel-radius);
}
.portal-card.status-online::before { background: var(--color-primary); }
.portal-card.status-warning::before { background: var(--color-warning); }
.portal-card.status-offline::before { background: var(--color-danger); }
.portal-card.status-locked::before { background: var(--color-secondary); }
.portal-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 10px;
padding-left: 8px;
}
.portal-name {
font-family: var(--font-display);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.1em;
color: var(--color-text);
text-transform: uppercase;
}
.portal-id {
font-size: 10px;
color: var(--color-text-muted);
margin-top: 2px;
letter-spacing: 0.05em;
}
.status-badge {
font-size: 10px;
letter-spacing: 0.1em;
text-transform: uppercase;
padding: 3px 8px;
border-radius: 3px;
font-weight: 500;
}
.status-badge.online { color: var(--color-primary); background: rgba(74, 240, 192, 0.12); }
.status-badge.warning { color: var(--color-warning); background: rgba(255, 170, 34, 0.12); }
.status-badge.offline { color: var(--color-danger); background: rgba(255, 68, 102, 0.12); }
.status-badge.locked { color: var(--color-secondary); background: rgba(123, 92, 255, 0.12); }
.portal-meta {
padding-left: 8px;
display: flex;
flex-direction: column;
gap: 4px;
}
.meta-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 11px;
}
.meta-label { color: var(--color-text-muted); }
.meta-value { color: var(--color-text); }
.meta-value.highlight { color: var(--color-primary); }
.portal-latency-bar {
margin-top: 12px;
padding-left: 8px;
}
.latency-track {
height: 3px;
background: rgba(255,255,255,0.06);
border-radius: 2px;
overflow: hidden;
}
.latency-fill {
height: 100%;
border-radius: 2px;
transition: width 0.5s ease;
}
.latency-fill.good { background: var(--color-primary); }
.latency-fill.fair { background: var(--color-warning); }
.latency-fill.poor { background: var(--color-danger); }
.latency-label {
font-size: 10px;
color: var(--color-text-muted);
margin-top: 4px;
}
/* Summary bar */
.summary-bar {
display: flex;
gap: 24px;
margin-top: 16px;
padding: 12px 16px;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--panel-radius);
backdrop-filter: blur(var(--panel-blur));
}
.summary-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
}
.summary-count {
font-family: var(--font-display);
font-size: 20px;
font-weight: 700;
line-height: 1;
}
.summary-label {
color: var(--color-text-muted);
font-size: 10px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
</style>
</head>
<body>
<div class="portal-wall">
<div class="panel-header">
<div class="pulse-dot"></div>
<span class="panel-title">Portal Status Wall</span>
<div class="panel-title-bar"></div>
<span style="font-size:11px;color:var(--color-text-muted)">LIVE</span>
</div>
<div class="portal-grid">
<!-- Portal: Hermes -->
<div class="portal-card status-online">
<div class="portal-header">
<div>
<div class="portal-name">Hermes</div>
<div class="portal-id">portal://hermes.nexus</div>
</div>
<span class="status-badge online">online</span>
</div>
<div class="portal-meta">
<div class="meta-row">
<span class="meta-label">Type</span>
<span class="meta-value">Comm Bridge</span>
</div>
<div class="meta-row">
<span class="meta-label">Agents</span>
<span class="meta-value highlight">3 active</span>
</div>
<div class="meta-row">
<span class="meta-label">Last beat</span>
<span class="meta-value">2s ago</span>
</div>
</div>
<div class="portal-latency-bar">
<div class="latency-track">
<div class="latency-fill good" style="width:22%"></div>
</div>
<div class="latency-label">22ms latency</div>
</div>
</div>
<!-- Portal: Archive -->
<div class="portal-card status-online">
<div class="portal-header">
<div>
<div class="portal-name">Archive</div>
<div class="portal-id">portal://archive.nexus</div>
</div>
<span class="status-badge online">online</span>
</div>
<div class="portal-meta">
<div class="meta-row">
<span class="meta-label">Type</span>
<span class="meta-value">Memory Store</span>
</div>
<div class="meta-row">
<span class="meta-label">Records</span>
<span class="meta-value highlight">14,822</span>
</div>
<div class="meta-row">
<span class="meta-label">Last write</span>
<span class="meta-value">41s ago</span>
</div>
</div>
<div class="portal-latency-bar">
<div class="latency-track">
<div class="latency-fill good" style="width:8%"></div>
</div>
<div class="latency-label">8ms latency</div>
</div>
</div>
<!-- Portal: Satflow -->
<div class="portal-card status-warning">
<div class="portal-header">
<div>
<div class="portal-name">Satflow</div>
<div class="portal-id">portal://satflow.nexus</div>
</div>
<span class="status-badge warning">degraded</span>
</div>
<div class="portal-meta">
<div class="meta-row">
<span class="meta-label">Type</span>
<span class="meta-value">Economy</span>
</div>
<div class="meta-row">
<span class="meta-label">Queue</span>
<span class="meta-value" style="color:var(--color-warning)">87 pending</span>
</div>
<div class="meta-row">
<span class="meta-label">Last beat</span>
<span class="meta-value">18s ago</span>
</div>
</div>
<div class="portal-latency-bar">
<div class="latency-track">
<div class="latency-fill fair" style="width:61%"></div>
</div>
<div class="latency-label">610ms latency</div>
</div>
</div>
<!-- Portal: Evennia -->
<div class="portal-card status-online">
<div class="portal-header">
<div>
<div class="portal-name">Evennia</div>
<div class="portal-id">portal://evennia.nexus</div>
</div>
<span class="status-badge online">online</span>
</div>
<div class="portal-meta">
<div class="meta-row">
<span class="meta-label">Type</span>
<span class="meta-value">World Engine</span>
</div>
<div class="meta-row">
<span class="meta-label">Players</span>
<span class="meta-value highlight">1 online</span>
</div>
<div class="meta-row">
<span class="meta-label">Uptime</span>
<span class="meta-value">6h 14m</span>
</div>
</div>
<div class="portal-latency-bar">
<div class="latency-track">
<div class="latency-fill good" style="width:15%"></div>
</div>
<div class="latency-label">15ms latency</div>
</div>
</div>
<!-- Portal: Bannerlord -->
<div class="portal-card status-offline">
<div class="portal-header">
<div>
<div class="portal-name">Bannerlord</div>
<div class="portal-id">portal://bannerlord.nexus</div>
</div>
<span class="status-badge offline">offline</span>
</div>
<div class="portal-meta">
<div class="meta-row">
<span class="meta-label">Type</span>
<span class="meta-value">Game MCP</span>
</div>
<div class="meta-row">
<span class="meta-label">Last seen</span>
<span class="meta-value" style="color:var(--color-danger)">2h ago</span>
</div>
<div class="meta-row">
<span class="meta-label">Error</span>
<span class="meta-value" style="color:var(--color-danger)">connection reset</span>
</div>
</div>
<div class="portal-latency-bar">
<div class="latency-track">
<div class="latency-fill poor" style="width:100%"></div>
</div>
<div class="latency-label">timeout</div>
</div>
</div>
<!-- Portal: OpenClaw -->
<div class="portal-card status-locked">
<div class="portal-header">
<div>
<div class="portal-name">OpenClaw</div>
<div class="portal-id">portal://openclaw.nexus</div>
</div>
<span class="status-badge locked">locked</span>
</div>
<div class="portal-meta">
<div class="meta-row">
<span class="meta-label">Type</span>
<span class="meta-value">Sidecar AI</span>
</div>
<div class="meta-row">
<span class="meta-label">Role</span>
<span class="meta-value" style="color:var(--color-secondary)">observer only</span>
</div>
<div class="meta-row">
<span class="meta-label">Auth</span>
<span class="meta-value">requires token</span>
</div>
</div>
<div class="portal-latency-bar">
<div class="latency-track">
<div class="latency-fill" style="width:0%;background:var(--color-secondary)"></div>
</div>
<div class="latency-label">access gated</div>
</div>
</div>
</div><!-- /portal-grid -->
<!-- Summary Bar -->
<div class="summary-bar">
<div class="summary-item">
<div>
<div class="summary-count" style="color:var(--color-primary)">4</div>
<div class="summary-label">Online</div>
</div>
</div>
<div class="summary-item">
<div>
<div class="summary-count" style="color:var(--color-warning)">1</div>
<div class="summary-label">Degraded</div>
</div>
</div>
<div class="summary-item">
<div>
<div class="summary-count" style="color:var(--color-danger)">1</div>
<div class="summary-label">Offline</div>
</div>
</div>
<div class="summary-item">
<div>
<div class="summary-count" style="color:var(--color-secondary)">1</div>
<div class="summary-label">Locked</div>
</div>
</div>
<div style="margin-left:auto;align-self:center;font-size:10px;color:var(--color-text-muted)">
LAST SYNC: <span style="color:var(--color-text)">04:20:07 UTC</span>
</div>
</div>
</div>
</body>
</html>