← back to Dw War Room
src/panels/HudOverlay.ts
898 lines
// ═══════════════════════════════════════════════
// DW War Room 3D — HUD Overlay (Figma Design System)
// Professional glassmorphism command center overlay
// Design tokens: --ds-* CSS custom properties
// Stock Trader UI Kit inspired dark dashboard
// ═══════════════════════════════════════════════
import type { AgentNode, DecisionStats, GovernanceStatus, MeetingState, EscalationAlert, SystemHealth, FeedItem } from '../types';
const PHASE_LABELS: Record<string, string> = {
caucus: 'Caucus', call_to_order: 'Call to Order', old_business: 'Old Business',
current_business: 'Current Business', new_business: 'New Business',
breakout: 'Breakout', minutes: 'Minutes', adjournment: 'Adjournment',
};
// Design token references for dynamic JS styling
// Uses CSS custom property syntax so values resolve at render time
const DS = {
success: 'var(--ds-success)',
warning: 'var(--ds-warning)',
error: 'var(--ds-error)',
info: 'var(--ds-info)',
violet: 'var(--ds-violet)',
cyan: 'var(--ds-cyan)',
gray100: 'var(--ds-gray-100)',
gray400: 'var(--ds-gray-400)',
gray500: 'var(--ds-gray-500)',
};
export class HudOverlay {
private container: HTMLElement;
private tooltipTimeout: ReturnType<typeof setTimeout> | null = null;
constructor(parent: HTMLElement) {
this.container = document.createElement('div');
this.container.id = 'war-room-hud';
this.buildDom();
parent.appendChild(this.container);
this.applyStyles();
}
/* ───────────────────────────────────────────
* DOM Construction — uses safe DOM methods
* ─────────────────────────────────────────── */
private buildDom(): void {
const root = this.container;
// ── Header ──
const header = this.el('div', 'hud-header');
const headerLeft = this.el('div', 'hud-header-left');
headerLeft.appendChild(this.el('span', 'hud-logo-pulse'));
const title = this.el('span', 'hud-title');
title.textContent = 'DW WAR ROOM';
headerLeft.appendChild(title);
header.appendChild(headerLeft);
const statusGroup = this.el('div', 'hud-status-group');
const statusDot = this.el('span', 'hud-status-dot');
statusDot.id = 'hud-status-dot';
statusGroup.appendChild(statusDot);
const statusText = this.el('span', 'hud-status');
statusText.id = 'hud-connection';
statusText.textContent = 'CONNECTING...';
statusGroup.appendChild(statusText);
const govMode = this.el('span', 'hud-governance-mode');
govMode.id = 'hud-gov-mode';
statusGroup.appendChild(govMode);
header.appendChild(statusGroup);
root.appendChild(header);
// ── Meeting Panel ──
root.appendChild(this.buildPanel(
'hud-meeting', 'hud-top-left accent-violet', 'MEETING STATUS',
'hud-meeting-content', 'No active meeting'
));
// ── Decisions Panel ──
root.appendChild(this.buildPanel(
'hud-decisions', 'hud-top-right accent-cyan', 'DECISIONS',
'hud-decisions-content', 'Loading...'
));
// ── Live Feed Panel ──
const feedPanel = this.buildPanel(
'hud-feed', 'hud-bottom-left accent-amber', 'LIVE FEED',
'hud-feed-content', ''
);
// Add feed count badge to title
const feedTitle = feedPanel.querySelector('.hud-panel-title');
if (feedTitle) {
const badge = this.el('span', 'hud-feed-badge');
badge.id = 'hud-feed-count';
feedTitle.appendChild(badge);
}
// Add scroll class to body
const feedBody = feedPanel.querySelector('.hud-panel-body');
if (feedBody) feedBody.classList.add('hud-feed-scroll');
root.appendChild(feedPanel);
// ── Escalations Panel ──
root.appendChild(this.buildPanel(
'hud-escalations', 'hud-bottom-right accent-red', 'ESCALATIONS',
'hud-escalations-content', 'None active'
));
// ── System Health Panel ──
const healthPanel = this.buildPanel(
'hud-health', 'hud-mid-right accent-emerald', 'SYSTEM HEALTH',
'hud-health-content', ''
);
const healthBody = healthPanel.querySelector('.hud-panel-body')!;
['CPU', 'MEMORY', 'SERVICES', 'UPTIME'].forEach(label => {
const row = this.el('div', 'hud-metric');
const lbl = this.el('span', 'hud-metric-label');
lbl.textContent = label;
row.appendChild(lbl);
const val = this.el('span', 'hud-metric-value');
val.id = `hud-${label.toLowerCase()}`;
val.textContent = '\u2014';
row.appendChild(val);
healthBody.appendChild(row);
});
root.appendChild(healthPanel);
// ── Governance Panel ──
const govPanel = this.buildPanel(
'hud-governance', 'hud-mid-left accent-blue', 'GOVERNANCE',
'hud-governance-content', ''
);
const govBody = govPanel.querySelector('.hud-panel-body')!;
const govMetrics = [
{ label: 'MODE', id: 'hud-gov-mode-val' },
{ label: 'PENDING', id: 'hud-gov-pending' },
{ label: 'DELEGATIONS', id: 'hud-gov-delegations' },
{ label: 'ESCALATIONS', id: 'hud-gov-escalations' },
];
govMetrics.forEach(m => {
const row = this.el('div', 'hud-metric');
const lbl = this.el('span', 'hud-metric-label');
lbl.textContent = m.label;
row.appendChild(lbl);
const val = this.el('span', 'hud-metric-value');
val.id = m.id;
val.textContent = '\u2014';
row.appendChild(val);
govBody.appendChild(row);
});
root.appendChild(govPanel);
// ── Agent Tooltip ──
const tooltip = this.el('div', 'hud-agent-tooltip');
tooltip.id = 'hud-agent-tooltip';
tooltip.style.display = 'none';
['tooltip-header', 'tooltip-role', 'tooltip-dept', 'tooltip-exec', 'tooltip-status'].forEach(cls => {
const d = this.el('div', cls);
d.id = cls;
tooltip.appendChild(d);
});
root.appendChild(tooltip);
// ── Bottom Bar ──
const bottomBar = this.el('div', 'hud-bottom-bar');
const bottomNav = this.el('div', 'hud-bottom-nav');
const btnData = [
{ id: 'btn-overview', label: 'Overview', icon: '<rect x="1" y="1" width="6" height="6" rx="1"/><rect x="9" y="1" width="6" height="6" rx="1"/><rect x="1" y="9" width="6" height="6" rx="1"/><rect x="9" y="9" width="6" height="6" rx="1"/>' },
{ id: 'btn-meeting', label: 'Meeting', icon: '<circle cx="8" cy="5" r="3"/><path d="M2 15c0-3.3 2.7-6 6-6s6 2.7 6 6"/>' },
{ id: 'btn-agents', label: 'Agents', icon: '<circle cx="5" cy="5" r="2.5"/><circle cx="11" cy="5" r="2.5"/><path d="M1 14c0-2.2 1.8-4 4-4s4 1.8 4 4"/><path d="M7 14c0-2.2 1.8-4 4-4s4 1.8 4 4"/>' },
];
btnData.forEach(b => {
const btn = document.createElement('button');
btn.className = 'hud-view-btn';
btn.id = b.id;
// Create SVG icon safely via namespace
const ns = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(ns, 'svg');
svg.setAttribute('width', '13');
svg.setAttribute('height', '13');
svg.setAttribute('viewBox', '0 0 16 16');
svg.setAttribute('fill', 'none');
svg.setAttribute('stroke', 'currentColor');
svg.setAttribute('stroke-width', '1.5');
// Use a temporary container to parse SVG children safely
const tmp = document.createElementNS(ns, 'svg');
tmp.innerHTML = b.icon;
while (tmp.firstChild) svg.appendChild(tmp.firstChild);
btn.appendChild(svg);
btn.appendChild(document.createTextNode(b.label));
bottomNav.appendChild(btn);
});
bottomBar.appendChild(bottomNav);
const bottomInfo = this.el('div', 'hud-bottom-info');
const agentCount = this.el('span', 'hud-agent-count');
agentCount.id = 'hud-agent-count';
bottomInfo.appendChild(agentCount);
bottomInfo.appendChild(this.el('span', 'hud-bottom-divider'));
const clock = this.el('span', 'hud-clock');
clock.id = 'hud-clock';
bottomInfo.appendChild(clock);
bottomBar.appendChild(bottomInfo);
root.appendChild(bottomBar);
}
/** Helper: create element with class(es) */
private el(tag: string, className: string): HTMLElement {
const e = document.createElement(tag);
e.className = className;
return e;
}
/** Helper: build a standard glass panel */
private buildPanel(id: string, classes: string, title: string, bodyId: string, bodyText: string): HTMLElement {
const panel = this.el('div', `hud-panel ${classes}`);
panel.id = id;
const titleEl = this.el('div', 'hud-panel-title');
titleEl.textContent = title;
panel.appendChild(titleEl);
const body = this.el('div', 'hud-panel-body');
body.id = bodyId;
if (bodyText) body.textContent = bodyText;
panel.appendChild(body);
return panel;
}
/* ───────────────────────────────────────────
* Data Update Methods
* ─────────────────────────────────────────── */
updateConnection(govConnected: boolean, brConnected: boolean): void {
const el = document.getElementById('hud-connection');
const dot = document.getElementById('hud-status-dot');
if (!el) return;
if (govConnected && brConnected) {
el.textContent = 'ALL SYSTEMS ONLINE';
el.style.color = DS.success;
if (dot) { dot.className = 'hud-status-dot dot-success'; }
} else if (govConnected || brConnected) {
el.textContent = 'PARTIAL CONNECTION';
el.style.color = DS.warning;
if (dot) { dot.className = 'hud-status-dot dot-warning'; }
} else {
el.textContent = 'DISCONNECTED';
el.style.color = DS.error;
if (dot) { dot.className = 'hud-status-dot dot-error'; }
}
}
updateMeeting(state: MeetingState): void {
const el = document.getElementById('hud-meeting-content');
if (!el) return;
if (!state.active) {
el.textContent = 'No active meeting';
el.style.color = DS.gray500;
return;
}
const phaseLabel = PHASE_LABELS[state.phase || ''] || state.phase || '\u2014';
el.textContent = '';
const typeDiv = document.createElement('div');
typeDiv.style.cssText = `color:${DS.violet};font-weight:700;font-size:13px`;
typeDiv.textContent = (state.type || '').toUpperCase();
el.appendChild(typeDiv);
const phaseDiv = document.createElement('div');
phaseDiv.style.marginTop = '4px';
phaseDiv.textContent = 'Phase: ';
const phaseSpan = document.createElement('span');
phaseSpan.style.color = DS.cyan;
phaseSpan.textContent = phaseLabel;
phaseDiv.appendChild(phaseSpan);
el.appendChild(phaseDiv);
const statusDiv = document.createElement('div');
statusDiv.textContent = 'Status: ';
const statusSpan = document.createElement('span');
statusSpan.style.color = state.status === 'active' ? DS.success : DS.warning;
statusSpan.textContent = (state.status || 'active').toUpperCase();
statusDiv.appendChild(statusSpan);
el.appendChild(statusDiv);
const msgDiv = document.createElement('div');
msgDiv.style.cssText = `font-size:9px;color:${DS.gray500};margin-top:4px`;
msgDiv.textContent = `${state.messages.length} message${state.messages.length !== 1 ? 's' : ''}`;
el.appendChild(msgDiv);
}
updateDecisions(stats: DecisionStats): void {
const el = document.getElementById('hud-decisions-content');
if (!el) return;
const p = stats.pending || 0;
const a = stats.approved || 0;
const r = stats.rejected || 0;
const d = stats.deferred || 0;
const t = stats.total || (p + a + r + d);
el.textContent = '';
const grid = document.createElement('div');
grid.className = 'decision-grid';
const statData = [
{ val: p, color: DS.warning, label: 'Pending' },
{ val: a, color: DS.success, label: 'Approved' },
{ val: r, color: DS.error, label: 'Rejected' },
{ val: d, color: DS.gray500, label: 'Deferred' },
];
statData.forEach(s => {
const cell = document.createElement('div');
cell.className = 'decision-stat';
const count = document.createElement('span');
count.className = 'decision-count';
count.style.color = s.color;
count.textContent = String(s.val);
const label = document.createElement('span');
label.className = 'decision-label';
label.textContent = s.label;
cell.appendChild(count);
cell.appendChild(label);
grid.appendChild(cell);
});
el.appendChild(grid);
const totalDiv = document.createElement('div');
totalDiv.className = 'decision-total';
totalDiv.textContent = 'Total Decisions: ';
const totalSpan = document.createElement('span');
totalSpan.style.cssText = `color:${DS.cyan};font-weight:700`;
totalSpan.textContent = String(t);
totalDiv.appendChild(totalSpan);
el.appendChild(totalDiv);
}
updateEscalations(alerts: EscalationAlert[]): void {
const el = document.getElementById('hud-escalations-content');
if (!el) return;
el.textContent = '';
if (alerts.length === 0) {
const span = document.createElement('span');
span.style.color = DS.success;
span.textContent = 'None active';
el.appendChild(span);
return;
}
const header = document.createElement('div');
header.className = 'escalation-header';
header.textContent = `${alerts.length} ACTIVE`;
el.appendChild(header);
alerts.slice(0, 4).forEach(a => {
const row = document.createElement('div');
row.className = 'escalation-row';
const hours = document.createElement('span');
hours.className = 'escalation-hours';
hours.textContent = `${a.hours_elapsed.toFixed(1)}h`;
row.appendChild(hours);
row.appendChild(document.createTextNode(' \u2014 '));
const type = document.createElement('span');
type.style.color = DS.gray100;
type.textContent = a.escalation_type;
row.appendChild(type);
if (a.action_taken) {
const action = document.createElement('span');
action.className = 'escalation-action';
action.textContent = `[${a.action_taken}]`;
row.appendChild(action);
}
el.appendChild(row);
});
}
updateSystemHealth(health: SystemHealth): void {
const cpu = document.getElementById('hud-cpu');
const mem = document.getElementById('hud-memory');
const svc = document.getElementById('hud-services');
const up = document.getElementById('hud-uptime');
if (cpu) {
cpu.textContent = health.cpu;
cpu.style.color = parseFloat(health.cpu) > 80 ? DS.error : parseFloat(health.cpu) > 50 ? DS.warning : DS.success;
}
if (mem) {
mem.textContent = health.memory;
mem.style.color = parseFloat(health.memory) > 80 ? DS.error : parseFloat(health.memory) > 50 ? DS.warning : DS.success;
}
if (svc) {
svc.textContent = String(health.services);
svc.style.color = DS.info;
}
if (up) {
up.textContent = health.uptime;
up.style.color = DS.gray400;
}
}
updateGovernance(status: GovernanceStatus): void {
const mode = document.getElementById('hud-gov-mode-val');
const pending = document.getElementById('hud-gov-pending');
const delegations = document.getElementById('hud-gov-delegations');
const escalations = document.getElementById('hud-gov-escalations');
const headerMode = document.getElementById('hud-gov-mode');
const gm = status.governance_mode || 'unknown';
if (mode) {
mode.textContent = gm.toUpperCase();
mode.style.color = gm === 'autonomous' ? DS.success : DS.warning;
}
if (pending) {
const pd = status.pending_decisions ?? 0;
pending.textContent = String(pd);
pending.style.color = pd > 5 ? DS.error : pd > 0 ? DS.warning : DS.success;
}
if (delegations) {
delegations.textContent = String(status.active_delegations ?? 0);
delegations.style.color = DS.info;
}
if (escalations) {
const ae = status.active_escalations ?? 0;
escalations.textContent = String(ae);
escalations.style.color = ae > 0 ? DS.error : DS.success;
}
if (headerMode) {
headerMode.textContent = gm.toUpperCase();
headerMode.style.color = gm === 'autonomous' ? DS.success : DS.warning;
}
}
updateAgentCount(total: number, speaking: number): void {
const el = document.getElementById('hud-agent-count');
if (!el) return;
el.textContent = '';
const count = document.createElement('span');
count.style.color = DS.cyan;
count.textContent = String(total);
el.appendChild(count);
el.appendChild(document.createTextNode(' agents'));
if (speaking > 0) {
const sp = document.createElement('span');
sp.style.color = DS.success;
sp.textContent = ` (${speaking} speaking)`;
el.appendChild(sp);
}
}
addFeedMessage(agentName: string, message: string, type?: string): void {
const el = document.getElementById('hud-feed-content');
if (!el) return;
const div = document.createElement('div');
div.className = 'feed-item';
const typeColor = type === 'error' ? DS.error : type === 'warning' ? DS.warning : DS.violet;
const time = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', timeZone: 'America/Los_Angeles' });
const timeSpan = document.createElement('span');
timeSpan.className = 'feed-time';
timeSpan.textContent = time;
div.appendChild(timeSpan);
const nameSpan = document.createElement('span');
nameSpan.style.cssText = `color:${typeColor};font-weight:600`;
nameSpan.textContent = agentName;
div.appendChild(nameSpan);
const msgSpan = document.createElement('span');
msgSpan.style.color = DS.gray400;
msgSpan.textContent = `: ${message.slice(0, 100)}`;
div.appendChild(msgSpan);
el.appendChild(div);
el.scrollTop = el.scrollHeight;
// Update count badge
const badge = document.getElementById('hud-feed-count');
if (badge) badge.textContent = String(el.children.length);
// Keep max 30 messages
while (el.children.length > 30) {
el.removeChild(el.firstChild!);
}
}
// Bulk load feed items from API
loadFeed(items: FeedItem[]): void {
const el = document.getElementById('hud-feed-content');
if (!el) return;
if (el.children.length > 0) return;
items.slice(-15).forEach(item => {
this.addFeedMessage(item.agent_name, item.action, item.type);
});
}
// Show agent detail tooltip at screen position
showAgentTooltip(agent: AgentNode, screenX: number, screenY: number): void {
const tooltip = document.getElementById('hud-agent-tooltip');
if (!tooltip) return;
if (this.tooltipTimeout) clearTimeout(this.tooltipTimeout);
const header = document.getElementById('tooltip-header');
const role = document.getElementById('tooltip-role');
const dept = document.getElementById('tooltip-dept');
const exec = document.getElementById('tooltip-exec');
const status = document.getElementById('tooltip-status');
if (header) {
header.textContent = agent.name;
header.style.color = agent.color;
}
if (role) role.textContent = agent.role;
if (dept) dept.textContent = `Dept: ${agent.dept}`;
if (exec) exec.textContent = `Reports to: ${agent.exec.toUpperCase()}`;
if (status) {
status.textContent = agent.speaking ? 'SPEAKING' : 'IDLE';
status.style.color = agent.speaking ? DS.success : DS.gray500;
}
const maxX = window.innerWidth - 200;
const maxY = window.innerHeight - 140;
tooltip.style.left = `${Math.min(screenX + 15, maxX)}px`;
tooltip.style.top = `${Math.min(screenY - 20, maxY)}px`;
tooltip.style.display = 'block';
this.tooltipTimeout = setTimeout(() => {
tooltip.style.display = 'none';
}, 5000);
}
hideAgentTooltip(): void {
const tooltip = document.getElementById('hud-agent-tooltip');
if (tooltip) tooltip.style.display = 'none';
if (this.tooltipTimeout) clearTimeout(this.tooltipTimeout);
}
updateClock(): void {
const el = document.getElementById('hud-clock');
if (!el) return;
const now = new Date();
const pt = new Date(now.toLocaleString('en-US', { timeZone: 'America/Los_Angeles' }));
el.textContent = pt.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) + ' PT';
}
/* ───────────────────────────────────────────
* Design System Stylesheet
* ─────────────────────────────────────────── */
private applyStyles(): void {
const style = document.createElement('style');
style.textContent = `
/* ═══ Design System Tokens ═══ */
:root {
/* Typography */
--ds-font: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
/* Gray palette (Slate scale) */
--ds-gray-50: #f8fafc;
--ds-gray-100: #f1f5f9;
--ds-gray-200: #e2e8f0;
--ds-gray-300: #cbd5e1;
--ds-gray-400: #94a3b8;
--ds-gray-500: #64748b;
--ds-gray-600: #475569;
--ds-gray-700: #334155;
--ds-gray-800: #1e293b;
--ds-gray-900: #0f172a;
/* Accent colors */
--ds-violet: #8b5cf6;
--ds-indigo: #6366f1;
--ds-cyan: #06b6d4;
--ds-blue: #3b82f6;
--ds-emerald: #10b981;
--ds-amber: #f59e0b;
/* Semantic colors */
--ds-success: #10b981;
--ds-warning: #f59e0b;
--ds-error: #ef4444;
--ds-info: #3b82f6;
/* Glass surfaces */
--ds-glass: rgba(10, 14, 28, 0.72);
--ds-glass-strong: rgba(8, 11, 22, 0.88);
--ds-glass-subtle: rgba(15, 20, 38, 0.5);
/* Blur */
--ds-blur-sm: blur(8px);
--ds-blur: blur(16px);
--ds-blur-lg: blur(24px);
/* Borders */
--ds-border: rgba(148, 163, 184, 0.06);
--ds-border-strong: rgba(148, 163, 184, 0.12);
--ds-border-interactive: rgba(99, 102, 241, 0.25);
/* Radius */
--ds-radius-sm: 6px;
--ds-radius-md: 10px;
--ds-radius-lg: 14px;
--ds-radius-full: 9999px;
/* Shadows */
--ds-shadow-sm: 0 2px 8px rgba(0,0,0,0.3);
--ds-shadow-md: 0 4px 16px rgba(0,0,0,0.4);
--ds-shadow-lg: 0 8px 32px rgba(0,0,0,0.5);
--ds-shadow-glow-violet: 0 0 20px rgba(139,92,246,0.15);
--ds-shadow-glow-cyan: 0 0 20px rgba(6,182,212,0.12);
/* Transitions */
--ds-ease: cubic-bezier(0.4, 0, 0.2, 1);
--ds-duration-fast: 150ms;
--ds-duration-normal: 250ms;
}
/* ═══ HUD Container ═══ */
#war-room-hud {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
pointer-events: none;
font-family: var(--ds-font);
color: var(--ds-gray-100);
z-index: 10;
}
/* ═══ Header Bar ═══ */
.hud-header {
position: absolute; top: 0; left: 0; right: 0;
display: flex; justify-content: space-between; align-items: center;
padding: 14px 24px;
background: linear-gradient(180deg, rgba(5,5,16,0.95) 0%, rgba(5,5,16,0.6) 70%, transparent 100%);
}
.hud-header-left {
display: flex; align-items: center; gap: 10px;
}
.hud-logo-pulse {
width: 8px; height: 8px; border-radius: var(--ds-radius-full);
background: var(--ds-violet);
box-shadow: 0 0 8px rgba(139,92,246,0.6), 0 0 16px rgba(139,92,246,0.3);
animation: logoPulse 2.5s ease-in-out infinite;
}
@keyframes logoPulse {
0%, 100% { opacity: 1; box-shadow: 0 0 8px rgba(139,92,246,0.6), 0 0 16px rgba(139,92,246,0.3); }
50% { opacity: 0.7; box-shadow: 0 0 4px rgba(139,92,246,0.3), 0 0 8px rgba(139,92,246,0.15); }
}
.hud-title {
font-size: 13px; font-weight: 800; letter-spacing: 2.5px;
background: linear-gradient(135deg, var(--ds-violet), var(--ds-cyan));
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
}
.hud-status-group {
display: flex; gap: 10px; align-items: center;
}
.hud-status-dot {
width: 6px; height: 6px; border-radius: var(--ds-radius-full);
background: var(--ds-warning);
animation: dotPulse 1.5s ease-in-out infinite;
}
.hud-status-dot.dot-success { background: var(--ds-success); animation: none; }
.hud-status-dot.dot-warning { background: var(--ds-warning); }
.hud-status-dot.dot-error { background: var(--ds-error); }
@keyframes dotPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.hud-status {
font-size: 10px; font-weight: 700; letter-spacing: 0.5px;
color: var(--ds-warning);
}
.hud-governance-mode {
font-size: 9px; font-weight: 600;
padding: 2px 8px; border-radius: var(--ds-radius-full);
background: rgba(139,92,246,0.1);
border: 1px solid rgba(139,92,246,0.2);
}
/* ═══ Glass Panels ═══ */
.hud-panel {
position: absolute;
padding: 16px 20px;
background: var(--ds-glass-strong);
backdrop-filter: var(--ds-blur);
-webkit-backdrop-filter: var(--ds-blur);
border: 1px solid var(--ds-border-strong);
border-radius: var(--ds-radius-lg);
min-width: 240px;
font-size: 13px;
box-shadow: var(--ds-shadow-lg), inset 0 1px 0 rgba(255,255,255,0.03);
transition: border-color var(--ds-duration-normal) var(--ds-ease),
box-shadow var(--ds-duration-normal) var(--ds-ease);
overflow: hidden;
}
/* Colored accent bar (left edge) via pseudo-element */
.hud-panel::before {
content: '';
position: absolute; left: 0; top: 0; bottom: 0;
width: 3px; border-radius: 3px 0 0 3px;
background: var(--ds-violet);
opacity: 0.8;
}
.hud-panel.accent-violet::before { background: var(--ds-violet); }
.hud-panel.accent-cyan::before { background: var(--ds-cyan); }
.hud-panel.accent-amber::before { background: var(--ds-amber); }
.hud-panel.accent-red::before { background: var(--ds-error); }
.hud-panel.accent-emerald::before { background: var(--ds-emerald); }
.hud-panel.accent-blue::before { background: var(--ds-blue); }
.hud-panel-title {
font-size: 11px; font-weight: 700;
color: var(--ds-gray-400);
letter-spacing: 1.5px;
margin-bottom: 12px;
display: flex; align-items: center; gap: 6px;
}
.hud-panel-body {
color: var(--ds-gray-300);
}
/* ═══ Panel Positions ═══ */
.hud-top-left { top: 56px; left: 16px; }
.hud-top-right { top: 56px; right: 16px; }
.hud-bottom-left { bottom: 64px; left: 16px; max-width: 320px; }
.hud-bottom-right { bottom: 64px; right: 16px; }
.hud-mid-right { top: 210px; right: 16px; }
.hud-mid-left { top: 210px; left: 16px; }
/* ═══ Feed ═══ */
.hud-feed-scroll { max-height: 220px; overflow-y: auto; pointer-events: all; }
.hud-feed-scroll::-webkit-scrollbar { width: 3px; }
.hud-feed-scroll::-webkit-scrollbar-track { background: transparent; }
.hud-feed-scroll::-webkit-scrollbar-thumb { background: rgba(148,163,184,0.15); border-radius: 2px; }
.hud-feed-badge {
font-size: 10px; font-weight: 600;
padding: 2px 8px; border-radius: var(--ds-radius-full);
background: rgba(139,92,246,0.15);
color: var(--ds-violet);
margin-left: 4px;
}
.feed-item {
padding: 5px 0;
border-bottom: 1px solid var(--ds-border);
font-size: 12px; line-height: 1.5;
}
.feed-item:last-child { border-bottom: none; }
.feed-time {
color: var(--ds-gray-500);
font-size: 10px; margin-right: 6px;
font-variant-numeric: tabular-nums;
}
/* ═══ Metrics ═══ */
.hud-metric {
display: flex; justify-content: space-between; align-items: center;
padding: 4px 0;
border-bottom: 1px solid var(--ds-border);
}
.hud-metric:last-child { border-bottom: none; }
.hud-metric-label {
font-size: 11px; color: var(--ds-gray-400);
letter-spacing: 0.5px; font-weight: 500;
}
.hud-metric-value {
font-size: 18px; font-weight: 700;
color: var(--ds-gray-100);
font-variant-numeric: tabular-nums;
}
/* ═══ Decisions Grid ═══ */
.decision-grid {
display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
}
.decision-stat {
display: flex; flex-direction: column; align-items: center;
padding: 6px 4px;
background: rgba(255,255,255,0.02);
border-radius: var(--ds-radius-sm);
border: 1px solid var(--ds-border);
transition: background var(--ds-duration-fast) var(--ds-ease);
}
.decision-count {
font-size: 26px; font-weight: 800;
font-variant-numeric: tabular-nums;
line-height: 1.2;
}
.decision-label {
font-size: 11px; color: var(--ds-gray-400);
letter-spacing: 0.5px; margin-top: 2px;
}
.decision-total {
margin-top: 8px; font-size: 12px;
color: var(--ds-gray-400);
border-top: 1px solid var(--ds-border-strong);
padding-top: 6px;
}
/* ═══ Escalations ═══ */
.escalation-header {
font-size: 12px; color: var(--ds-error);
font-weight: 700; margin-bottom: 8px;
display: flex; align-items: center; gap: 5px;
}
.escalation-header::before {
content: '';
width: 6px; height: 6px; border-radius: var(--ds-radius-full);
background: var(--ds-error);
animation: dotPulse 1s ease-in-out infinite;
}
.escalation-row {
padding: 5px 0;
border-bottom: 1px solid var(--ds-border);
font-size: 12px;
}
.escalation-row:last-child { border-bottom: none; }
.escalation-hours {
color: var(--ds-error); font-weight: 600;
}
.escalation-action {
color: var(--ds-success);
font-size: 9px; margin-left: 4px;
}
/* ═══ Agent Tooltip ═══ */
.hud-agent-tooltip {
position: absolute;
padding: 14px 18px;
background: var(--ds-glass-strong);
backdrop-filter: var(--ds-blur-lg);
-webkit-backdrop-filter: var(--ds-blur-lg);
border: 1px solid var(--ds-border-interactive);
border-radius: var(--ds-radius-lg);
min-width: 190px;
pointer-events: none; z-index: 20;
box-shadow: var(--ds-shadow-lg), var(--ds-shadow-glow-violet);
}
.tooltip-header { font-size: 14px; font-weight: 700; margin-bottom: 6px; }
.tooltip-role { font-size: 11px; color: var(--ds-gray-400); margin-bottom: 3px; }
.tooltip-dept { font-size: 10px; color: var(--ds-gray-500); }
.tooltip-exec { font-size: 10px; color: var(--ds-gray-500); }
.tooltip-status { font-size: 10px; font-weight: 600; margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--ds-border); }
/* ═══ Bottom Bar ═══ */
.hud-bottom-bar {
position: absolute; bottom: 0; left: 0; right: 0;
display: flex; justify-content: space-between; align-items: center;
padding: 10px 24px;
background: linear-gradient(0deg, rgba(5,5,16,0.95) 0%, rgba(5,5,16,0.6) 70%, transparent 100%);
}
.hud-bottom-nav {
display: flex; gap: 6px;
}
.hud-view-btn {
pointer-events: all;
display: flex; align-items: center; gap: 6px;
padding: 6px 14px; border-radius: var(--ds-radius-md);
border: 1px solid var(--ds-border-strong);
background: var(--ds-glass);
backdrop-filter: var(--ds-blur-sm);
-webkit-backdrop-filter: var(--ds-blur-sm);
color: var(--ds-gray-300); font-size: 12px; font-weight: 600;
cursor: pointer; font-family: inherit;
transition: all var(--ds-duration-fast) var(--ds-ease);
letter-spacing: 0.3px;
}
.hud-view-btn svg {
width: 13px; height: 13px; opacity: 0.7;
transition: opacity var(--ds-duration-fast) var(--ds-ease);
}
.hud-view-btn:hover {
border-color: var(--ds-border-interactive);
color: var(--ds-gray-100);
background: rgba(99,102,241,0.08);
box-shadow: var(--ds-shadow-sm), 0 0 12px rgba(99,102,241,0.1);
}
.hud-view-btn:hover svg { opacity: 1; }
.hud-view-btn:active {
transform: scale(0.97);
}
.hud-bottom-info {
display: flex; align-items: center; gap: 12px;
}
.hud-agent-count {
font-size: 12px; color: var(--ds-gray-400);
}
.hud-bottom-divider {
width: 1px; height: 12px;
background: var(--ds-border-strong);
}
.hud-clock {
font-size: 13px; color: var(--ds-gray-400);
font-variant-numeric: tabular-nums;
font-weight: 500;
letter-spacing: 0.3px;
}
`;
document.head.appendChild(style);
}
}