style(dashboard): replace remaining hardcoded colors with design tokens

Continued CSS tokenization pass across ambient, fleet, live, simple,
integrations pages and their component stylesheets. Replaces hardcoded
`white`, `#1a1a2e`, and raw rgba values with semantic tokens from
tokens.css.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-04-24 14:39:10 -04:00
parent 55675943ce
commit dd2fdd789c
13 changed files with 334 additions and 306 deletions

View file

@ -9,7 +9,7 @@
<link rel="stylesheet" href="css/ambient.css">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="theme-color" content="#1a1a2e">
<meta name="theme-color" content="#18191b">
</head>
<body class="ambient-mode">
<div class="app-shell app-shell--full">

View file

@ -30,7 +30,7 @@
padding: 16px 24px;
max-width: 800px;
margin: 0 auto;
color: white;
color: var(--text-on-accent);
}
.ap-detection-icon {
@ -161,7 +161,7 @@
}
.ap-btn-primary {
background: white;
background: var(--slate-12);
color: var(--blue-7);
}
@ -173,7 +173,7 @@
.ap-btn-secondary {
background: var(--border-strong);
color: white;
color: var(--text-on-accent);
border: 1px solid var(--slate-7);
}

View file

@ -225,7 +225,7 @@
.contributing-yes .contributing-badge {
background: var(--ok);
color: white;
color: var(--text-on-accent);
}
.contributing-no .contributing-badge {

View file

@ -849,7 +849,7 @@
left: 0;
right: 0;
background: linear-gradient(135deg, var(--alert), var(--alert-muted));
color: white;
color: var(--text-on-accent);
padding: 16px 20px;
z-index: 5000;
display: flex;
@ -915,7 +915,7 @@
.alert-banner-btn-acknowledge {
background: var(--border-strong);
color: white;
color: var(--text-on-accent);
}
.alert-banner-btn-acknowledge:hover {
@ -923,7 +923,7 @@
}
.alert-banner-btn-view {
background: white;
background: var(--slate-12);
color: var(--alert);
}

View file

@ -231,7 +231,7 @@
left: 50%;
transform: translateX(-50%);
background: var(--blue-10);
color: white;
color: var(--text-on-accent);
padding: 10px 20px;
border-radius: var(--radius-modal);
font-size: var(--text-sm);

View file

@ -286,7 +286,7 @@
left: 0;
right: 0;
background: linear-gradient(135deg, var(--alert), var(--alert));
color: white;
color: var(--text-on-accent);
padding: 16px 20px;
z-index: 5000;
display: flex;
@ -369,7 +369,7 @@
.alert-banner-btn.acknowledge {
background: var(--border-strong);
color: white;
color: var(--text-on-accent);
}
.alert-banner-btn.acknowledge:hover {
@ -377,12 +377,12 @@
}
.alert-banner-btn.view {
background: white;
background: var(--slate-12);
color: var(--alert);
}
.alert-banner-btn.view:hover {
background: rgba(255, 255, 255, 0.9);
background: var(--slate-12);
}
/* ── Security Mode Indicator (full page overlay when armed) ──────────────────────── */

View file

@ -105,7 +105,7 @@ body.simple-mode {
/* ===== Alert Banner ===== */
.simple-alert-banner {
background: linear-gradient(135deg, var(--simple-accent-red), var(--alert));
color: white;
color: var(--text-on-accent);
padding: 16px;
border-radius: var(--radius-card);
margin-bottom: 16px;
@ -154,7 +154,7 @@ body.simple-mode {
.simple-alert-banner .alert-dismiss {
background: var(--border-strong);
border: none;
color: white;
color: var(--text-on-accent);
width: 28px;
height: 28px;
border-radius: 50%;
@ -173,7 +173,7 @@ body.simple-mode {
/* ===== Morning Briefing Card ===== */
.simple-morning-briefing {
background: linear-gradient(135deg, var(--blue-7), var(--blue-9));
color: white;
color: var(--text-on-accent);
padding: 20px;
border-radius: var(--radius-modal);
margin-bottom: 16px;
@ -252,7 +252,7 @@ body.simple-mode {
margin-top: 8px;
background: var(--border-strong);
border: none;
color: white;
color: var(--text-on-accent);
padding: 8px 16px;
border-radius: var(--radius-card);
font-size: var(--text-sm);
@ -386,7 +386,7 @@ body.simple-mode {
justify-content: center;
font-size: var(--text-sm);
font-weight: 600;
color: white;
color: var(--text-on-accent);
}
.simple-room-card .room-activity {
@ -450,7 +450,7 @@ body.simple-mode {
.simple-activity-feed .filter-btn.active {
background: var(--simple-accent-blue);
color: white;
color: var(--text-on-accent);
}
.simple-activity-feed .feed-empty {
@ -629,7 +629,7 @@ body.simple-mode {
top: 4px;
right: 4px;
background: var(--simple-accent-red);
color: white;
color: var(--text-on-accent);
font-size: 10px;
font-weight: 600;
padding: 2px 6px;
@ -641,7 +641,7 @@ body.simple-mode {
/* ===== Sleep Summary Card ===== */
.simple-sleep-summary {
background: linear-gradient(135deg, var(--blue-10), var(--blue-8));
color: white;
color: var(--text-on-accent);
padding: 20px;
border-radius: var(--radius-modal);
margin-bottom: 16px;
@ -723,7 +723,7 @@ body.simple-mode {
margin-top: 12px;
background: var(--border-strong);
border: none;
color: white;
color: var(--text-on-accent);
padding: 10px 16px;
border-radius: var(--radius-card);
font-size: var(--text-sm);
@ -922,12 +922,12 @@ body.simple-mode {
.simple-security-toggle .security-toggle-btn.disarmed {
background: var(--simple-accent-green);
color: white;
color: var(--text-on-accent);
}
.simple-security-toggle .security-toggle-btn.armed {
background: var(--simple-accent-red);
color: white;
color: var(--text-on-accent);
}
.simple-security-toggle .security-status {

View file

@ -606,7 +606,7 @@
/* Event Count Badge */
.timeline-count {
background: var(--accent-color, var(--blue-9));
color: white;
color: var(--text-on-accent);
font-size: 11px;
font-weight: 600;
padding: 2px 6px;

View file

@ -35,78 +35,77 @@
</div>
</nav>
<!-- Page Header -->
<header class="fleet-header">
<div class="header-content">
<h1>Fleet Status</h1>
<div class="fleet-summary">
<span class="summary-item">
<span class="summary-label">Total:</span>
<span class="summary-value" id="fleet-total">0</span>
</span>
<span class="summary-item">
<span class="summary-label">Online:</span>
<span class="summary-value online" id="fleet-online">0</span>
</span>
</div>
</div>
<div class="header-actions">
<button id="fleet-refresh-btn" class="btn btn-secondary" title="Refresh fleet data">
<span class="icon">&#x21BB;</span>
<span class="label">Refresh</span>
</button>
<button id="fleet-update-all-btn" class="btn btn-action" title="Update all nodes to latest firmware">
<span class="icon">&#x2191;</span>
<span class="label">Update All</span>
</button>
<button id="fleet-download-btn" class="btn btn-secondary" title="Download CSV report">
<span class="icon">&#x2193;</span>
<span class="label">Download Report</span>
</button>
</div>
</header>
<!-- Filter and Sort Bar -->
<div class="fleet-toolbar" id="fleet-toolbar" style="display: none;">
<div class="toolbar-section">
<div class="filter-group">
<input type="text" id="fleet-search" class="search-input" placeholder="Search by label or MAC...">
<select id="filter-status" class="filter-select">
<option value="">All Status</option>
<option value="online">Online</option>
<option value="offline">Offline</option>
<option value="updating">Updating</option>
</select>
<select id="filter-firmware" class="filter-select">
<option value="">All Firmware</option>
<option value="outdated">Outdated Only</option>
</select>
<select id="filter-role" class="filter-select" multiple>
<option value="tx">TX</option>
<option value="rx">RX</option>
<option value="tx_rx">TX-RX</option>
<option value="passive">Passive</option>
</select>
</div>
<div class="active-filters" id="active-filters"></div>
</div>
</div>
<!-- Bulk Actions Bar (shown when nodes are selected) -->
<div class="bulk-actions-bar" id="bulk-actions-bar" style="display: none;">
<div class="bulk-content">
<span class="bulk-count"><span id="bulk-selected-count">0</span> nodes selected</span>
<div class="bulk-buttons">
<button id="bulk-ota-btn" class="btn btn-action">Update Selected</button>
<button id="bulk-role-btn" class="btn btn-secondary">Re-assign Roles</button>
<button id="bulk-remove-btn" class="btn btn-danger">Remove Selected</button>
<button id="bulk-clear-btn" class="btn btn-link">Clear Selection</button>
</div>
</div>
</div>
<!-- Fleet Table -->
<main class="app-main fleet-main">
<header class="fleet-header">
<div class="header-content">
<h1>Fleet Status</h1>
<div class="fleet-summary">
<span class="summary-item">
<span class="summary-label">Total:</span>
<span class="summary-value" id="fleet-total">0</span>
</span>
<span class="summary-item">
<span class="summary-label">Online:</span>
<span class="summary-value online" id="fleet-online">0</span>
</span>
</div>
</div>
<div class="header-actions">
<button id="fleet-refresh-btn" class="btn btn-secondary" title="Refresh fleet data">
<span class="icon">&#x21BB;</span>
<span class="label">Refresh</span>
</button>
<button id="fleet-update-all-btn" class="btn btn-action" title="Update all nodes to latest firmware">
<span class="icon">&#x2191;</span>
<span class="label">Update All</span>
</button>
<button id="fleet-download-btn" class="btn btn-secondary" title="Download CSV report">
<span class="icon">&#x2193;</span>
<span class="label">Download Report</span>
</button>
</div>
</header>
<!-- Filter and Sort Bar -->
<div class="fleet-toolbar" id="fleet-toolbar" style="display: none;">
<div class="toolbar-section">
<div class="filter-group">
<input type="text" id="fleet-search" class="search-input" placeholder="Search by label or MAC...">
<select id="filter-status" class="filter-select">
<option value="">All Status</option>
<option value="online">Online</option>
<option value="offline">Offline</option>
<option value="updating">Updating</option>
</select>
<select id="filter-firmware" class="filter-select">
<option value="">All Firmware</option>
<option value="outdated">Outdated Only</option>
</select>
<select id="filter-role" class="filter-select" multiple>
<option value="tx">TX</option>
<option value="rx">RX</option>
<option value="tx_rx">TX-RX</option>
<option value="passive">Passive</option>
</select>
</div>
<div class="active-filters" id="active-filters"></div>
</div>
</div>
<!-- Bulk Actions Bar (shown when nodes are selected) -->
<div class="bulk-actions-bar" id="bulk-actions-bar" style="display: none;">
<div class="bulk-content">
<span class="bulk-count"><span id="bulk-selected-count">0</span> nodes selected</span>
<div class="bulk-buttons">
<button id="bulk-ota-btn" class="btn btn-action">Update Selected</button>
<button id="bulk-role-btn" class="btn btn-secondary">Re-assign Roles</button>
<button id="bulk-remove-btn" class="btn btn-danger">Remove Selected</button>
<button id="bulk-clear-btn" class="btn btn-link">Clear Selection</button>
</div>
</div>
</div>
<div class="table-container">
<table class="fleet-table" id="fleet-table">
<thead>
@ -164,7 +163,9 @@
</div>
</main>
<!-- OTA Confirmation Modal -->
</div><!-- /.app-shell -->
<!-- Modals and toasts (outside grid shell) -->
<div class="modal" id="ota-modal" style="display: none;">
<div class="modal-content">
<div class="modal-header">
@ -195,7 +196,6 @@
</div>
</div>
<!-- Role Assignment Modal -->
<div class="modal" id="role-modal" style="display: none;">
<div class="modal-content">
<div class="modal-header">
@ -234,7 +234,6 @@
</div>
</div>
<!-- Remove Confirmation Modal -->
<div class="modal" id="remove-modal" style="display: none;">
<div class="modal-content">
<div class="modal-header">
@ -253,11 +252,8 @@
</div>
</div>
<!-- Toast Notifications -->
<div class="toast-container" id="toast-container"></div>
</div><!-- /.app-shell -->
<!-- Mobile bottom nav -->
<nav class="app-mobile-nav" aria-label="Main navigation">
<ul class="app-mobile-nav__list">

View file

@ -384,18 +384,18 @@
</div>
</main>
<!-- ── Mobile bottom nav ── -->
<nav class="app-mobile-nav">
<ul class="app-mobile-nav__list">
<li class="app-mobile-nav__item"><a href="/" class="app-mobile-nav__link">Home</a></li>
<li class="app-mobile-nav__item"><a href="/live" class="app-mobile-nav__link">Live</a></li>
<li class="app-mobile-nav__item"><a href="/fleet" class="app-mobile-nav__link">Fleet</a></li>
<li class="app-mobile-nav__item"><a href="/integrations" class="app-mobile-nav__link app-mobile-nav__link--active">Integrations</a></li>
</ul>
</nav>
</div><!-- /.app-shell -->
<!-- ── Mobile bottom nav (outside grid) ── -->
<nav class="app-mobile-nav">
<ul class="app-mobile-nav__list">
<li class="app-mobile-nav__item"><a href="/" class="app-mobile-nav__link">Home</a></li>
<li class="app-mobile-nav__item"><a href="/live" class="app-mobile-nav__link">Live</a></li>
<li class="app-mobile-nav__item"><a href="/fleet" class="app-mobile-nav__link">Fleet</a></li>
<li class="app-mobile-nav__item"><a href="/integrations" class="app-mobile-nav__link app-mobile-nav__link--active">Integrations</a></li>
</ul>
</nav>
<script src="js/integrations.js"></script>
<script>
// Override the panel rendering for standalone page

View file

@ -1352,70 +1352,102 @@
// Seek Handler (Time-Travel)
// ============================================
function handleSeek(timestamp, entryElement) {
// Convert timestamp to ISO8601
const targetDate = new Date(timestamp);
const iso8601 = targetDate.toISOString();
if (state.dashboardMode !== 'expert') {
return;
}
// Create a replay window around the event timestamp
const windowMs = CONFIG.replaySeekWindowSec * 1000;
const fromDate = new Date(timestamp - windowMs);
const toDate = new Date(timestamp + windowMs);
// Highlight selected event
clearSelectedEvent();
if (entryElement) {
entryElement.classList.add('timeline-event-selected');
state.replay.selectedEventId = entryElement.dataset.id;
}
// Create replay session
const startPayload = {
from_iso8601: fromDate.toISOString(),
to_iso8601: toDate.toISOString(),
speed: 1
// Use jump-to-time API for single-call replay session creation
var payload = {
timestamp_ms: timestamp,
window_ms: CONFIG.replaySeekWindowSec * 1000
};
fetch('/api/replay/start', {
fetch('/api/replay/jump-to-time', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(startPayload)
body: JSON.stringify(payload)
})
.then(function(res) {
if (!res.ok) {
throw new Error('Failed to start replay session');
throw new Error('Failed to jump to time');
}
return res.json();
})
.then(function(data) {
const sessionId = data.session_id;
state.replay.activeSessionId = data.session_id;
state.replay.isReplaying = true;
state.replay.replayTimestamp = timestamp;
// Seek to the specific timestamp
return fetch('/api/replay/seek', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
session_id: sessionId,
timestamp_iso8601: iso8601
})
});
})
.then(function(res) {
if (!res.ok) {
throw new Error('Failed to seek in replay');
}
return res.json();
})
.then(function(data) {
// Navigate to replay mode
// Show "Now replaying" chip
showNowReplayingChip(timestamp);
// Navigate to replay mode if router available
if (window.SpaxelRouter) {
SpaxelRouter.navigate('replay');
}
// Notify replay module about the jump
if (window.SpaxelReplay && SpaxelReplay.onJumpToTime) {
SpaxelReplay.onJumpToTime(data.session_id, timestamp);
}
if (window.SpaxelApp && SpaxelApp.showToast) {
SpaxelApp.showToast('Replay mode: viewing ' + formatTimestamp(timestamp), 'info');
SpaxelApp.showToast('Viewing ' + formatTimestamp(timestamp), 'info');
}
})
.catch(function(err) {
console.error('[Timeline] Replay seek failed:', err);
console.error('[Timeline] Jump to time failed:', err);
if (window.SpaxelApp && SpaxelApp.showToast) {
SpaxelApp.showToast('Failed to jump to replay: ' + err.message, 'warning');
}
});
}
// ============================================
// Selected Event Highlighting
// ============================================
function clearSelectedEvent() {
if (elements.eventsList) {
var prev = elements.eventsList.querySelector('.timeline-event-selected');
if (prev) {
prev.classList.remove('timeline-event-selected');
}
}
state.replay.selectedEventId = null;
}
// ============================================
// Now Replaying Chip
// ============================================
function showNowReplayingChip(timestampMs) {
var chip = elements.nowReplayingChip;
if (!chip) return;
var timeStr = formatTimestamp(timestampMs);
chip.innerHTML = '<span class="now-replaying-dot"></span> Now replaying: ' + timeStr;
chip.style.display = 'inline-flex';
chip.classList.add('visible');
}
function hideNowReplayingChip() {
var chip = elements.nowReplayingChip;
if (!chip) return;
chip.classList.remove('visible');
chip.style.display = 'none';
state.replay.isReplaying = false;
state.replay.activeSessionId = null;
state.replay.replayTimestamp = null;
clearSelectedEvent();
}
function updateFilterOptions(events) {
// Extract unique values from events
const types = new Set();

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,7 @@
.wizard-close:hover{color:var(--text-primary)}
#wizard-steps{display:flex;align-items:center;justify-content:center;margin-bottom:var(--space-6);gap:0}
.wizard-step-dot{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:var(--fw-heading);background:var(--bg-active);color:var(--slate-7);transition:all .3s;flex-shrink:0}
.wizard-step-dot.active{background:var(--color-primary);color:var(--text-on-accent);box-shadow:0 0 12px rgba(62,150,232,.5)}
.wizard-step-dot.active{background:var(--color-primary);color:var(--text-on-accent);box-shadow:0 0 12px var(--blue-border)}
.wizard-step-dot.completed{background:var(--ok);color:var(--text-on-accent)}
.wizard-step-line{width:20px;height:2px;background:var(--bg-active);flex-shrink:0}
.wizard-step-line.completed{background:var(--ok)}
@ -48,12 +48,12 @@
</style>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="theme-color" content="#f5f5f7">
<meta name="theme-color" content="#18191b">
</head>
<body class="simple-mode has-mobile-nav">
<div class="app-shell app-shell--full">
<!-- Header (grid header area) -->
<header id="simple-mode-header" class="simple-mode-header">
<header id="simple-mode-header" class="simple-mode-header app-header">
<h1>&#x1F3E0; Spaxel</h1>
<div class="mode-toggle">
<button class="mode-toggle-btn active" data-mode="simple">Simple</button>