style(dashboard): continue design token migration across remaining CSS
Replace additional hard-coded colors with design tokens in layout, notifications, panels, timeline, and other CSS files. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
07abc03ef4
commit
1e8876d6b4
21 changed files with 664 additions and 220 deletions
|
|
@ -103,7 +103,7 @@ body.ambient-mode {
|
|||
/* ===== Ambient Status Line ===== */
|
||||
.ambient-status {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
bottomvar(--space-5);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
|
|
@ -118,7 +118,7 @@ body.ambient-mode {
|
|||
.ambient-status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
}
|
||||
|
||||
.ambient-status-dot {
|
||||
|
|
@ -363,7 +363,7 @@ body.ambient-mode {
|
|||
}
|
||||
|
||||
.ambient-alert-btn {
|
||||
padding: 14px 28px;
|
||||
paddingvar(--space-350) var(--space-7);
|
||||
border-radius: var(--radius-card);
|
||||
font-size: var(--text-base);
|
||||
font-weight: 600;
|
||||
|
|
@ -491,7 +491,7 @@ body.ambient-mode {
|
|||
/* ===== Responsive Design ===== */
|
||||
@media (max-width: 768px) {
|
||||
.ambient-container {
|
||||
padding: 10px;
|
||||
paddingvar(--space-250);
|
||||
}
|
||||
|
||||
.ambient-person {
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@
|
|||
#feedback-notes-input {
|
||||
width: 100%;
|
||||
min-height: 80px;
|
||||
padding: 10px;
|
||||
paddingvar(--space-250);
|
||||
background: var(--bg-input);
|
||||
border: 1px solid var(--slate-6);
|
||||
border-radius: var(--radius-control);
|
||||
|
|
@ -325,7 +325,7 @@
|
|||
|
||||
#anomaly-learning-banner {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
topvar(--space-16);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 500;
|
||||
|
|
@ -385,7 +385,7 @@
|
|||
#security-mode-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
padding: var(--space-1) 10px;
|
||||
border-radius: var(--radius-modal);
|
||||
font-size: var(--text-xs);
|
||||
|
|
@ -442,7 +442,7 @@
|
|||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 2px;
|
||||
paddingvar(--space-half);
|
||||
margin-left: var(--space-1);
|
||||
border-radius: var(--radius-control);
|
||||
color: inherit;
|
||||
|
|
@ -775,7 +775,7 @@
|
|||
|
||||
.anomaly-history-score {
|
||||
font-size: var(--text-xs);
|
||||
padding: 2px 6px;
|
||||
paddingvar(--space-half) var(--space-150);
|
||||
border-radius: var(--radius-control);
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
|
|
@ -798,7 +798,7 @@
|
|||
|
||||
.anomaly-history-feedback {
|
||||
font-size: var(--text-xs);
|
||||
padding: 2px 6px;
|
||||
paddingvar(--space-half) var(--space-150);
|
||||
border-radius: var(--radius-control);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@
|
|||
.ble-device {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
padding: 10px var(--space-3);
|
||||
background: var(--border-subtle);
|
||||
border-radius: var(--radius-control);
|
||||
|
|
@ -214,7 +214,7 @@
|
|||
|
||||
.ble-form-group input[type="color"] {
|
||||
height: 36px;
|
||||
padding: 2px;
|
||||
paddingvar(--space-half);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#briefing-card {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
topvar(--space-16);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 90%;
|
||||
|
|
@ -162,7 +162,7 @@
|
|||
/* Simple mode briefing card */
|
||||
.simple-mode #briefing-card {
|
||||
top: auto;
|
||||
bottom: 20px;
|
||||
bottomvar(--space-5);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
|
|
@ -185,7 +185,7 @@
|
|||
#briefing-indicator {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
right: 20px;
|
||||
rightvar(--space-5);
|
||||
transform: translateY(-50%);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
|
@ -279,8 +279,8 @@
|
|||
#briefing-settings .setting-toggle::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
topvar(--space-half);
|
||||
leftvar(--space-half);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: var(--text-on-accent);
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
.cp-search-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
padding: 14px var(--space-4);
|
||||
border-bottom: 1px solid var(--border-default);
|
||||
}
|
||||
|
|
@ -93,7 +93,7 @@
|
|||
color: var(--slate-7);
|
||||
background: var(--highlight-subtle);
|
||||
border-radius: var(--radius-control);
|
||||
padding: 2px 7px;
|
||||
paddingvar(--space-half) var(--space-175);
|
||||
flex-shrink: 0;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
.cp-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
padding: 9px var(--space-4);
|
||||
cursor: pointer;
|
||||
transition: background 0.1s;
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@
|
|||
.toggle-switch .slider:before {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
topvar(--space-1);
|
||||
leftvar(--space-1);
|
||||
}
|
||||
|
||||
.toggle-switch input:checked + .slider:before {
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@
|
|||
.zone-badge {
|
||||
display: inline-block;
|
||||
min-width: 20px;
|
||||
padding: 2px 6px;
|
||||
paddingvar(--space-half) var(--space-150);
|
||||
border-radius: var(--radius-control);
|
||||
font-size: var(--text-2xs);
|
||||
font-weight: 600;
|
||||
|
|
@ -248,7 +248,7 @@
|
|||
.ble-match-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
|
@ -274,7 +274,7 @@
|
|||
.ble-match-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
}
|
||||
|
||||
.ble-match-detail {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ body.fleet-page {
|
|||
.nav-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
padding: var(--space-2) var(--space-4);
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-default);
|
||||
|
|
@ -116,7 +116,7 @@ body.fleet-page {
|
|||
|
||||
.summary-item {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
|
|
@ -143,7 +143,7 @@ body.fleet-page {
|
|||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
padding: var(--space-2) var(--space-4);
|
||||
border-radius: var(--radius-control);
|
||||
font-size: var(--text-sm);
|
||||
|
|
@ -267,7 +267,7 @@ body.fleet-page {
|
|||
.filter-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
padding: var(--space-1) 10px;
|
||||
background: var(--blue-muted);
|
||||
border: 1px solid var(--blue-border);
|
||||
|
|
@ -509,7 +509,7 @@ body.fleet-page {
|
|||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
padding: var(--space-1) 10px;
|
||||
border-radius: var(--radius-card);
|
||||
font-size: var(--text-xs);
|
||||
|
|
@ -559,7 +559,7 @@ body.fleet-page {
|
|||
.firmware-version {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
}
|
||||
|
||||
.firmware-current {
|
||||
|
|
@ -574,7 +574,7 @@ body.fleet-page {
|
|||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: 2px 6px;
|
||||
paddingvar(--space-half) var(--space-150);
|
||||
background: var(--warn-bg);
|
||||
border-radius: var(--radius-control);
|
||||
font-size: var(--text-2xs);
|
||||
|
|
@ -694,7 +694,7 @@ body.fleet-page {
|
|||
border: none;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
paddingvar(--space-150);
|
||||
font-size: var(--text-base);
|
||||
border-radius: var(--radius-control);
|
||||
transition: all 0.2s;
|
||||
|
|
@ -981,8 +981,8 @@ body.fleet-page {
|
|||
============================================ */
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
bottomvar(--space-6);
|
||||
rightvar(--space-6);
|
||||
z-index: 1100;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -1163,9 +1163,9 @@ body.fleet-page {
|
|||
}
|
||||
|
||||
.toast-container {
|
||||
right: 12px;
|
||||
left: 12px;
|
||||
bottom: 12px;
|
||||
rightvar(--space-3);
|
||||
leftvar(--space-3);
|
||||
bottomvar(--space-3);
|
||||
}
|
||||
|
||||
.toast {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
.floorplan-panel {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
topvar(--space-16);
|
||||
right: 340px;
|
||||
width: 380px;
|
||||
max-height: calc(100vh - 80px);
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
}
|
||||
|
||||
.floorplan-btn {
|
||||
padding: 6px 14px;
|
||||
paddingvar(--space-150) var(--space-350);
|
||||
border-radius: var(--radius-control);
|
||||
font-size: var(--text-xs);
|
||||
cursor: pointer;
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
border: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
}
|
||||
|
||||
.floorplan-btn-primary {
|
||||
|
|
@ -213,7 +213,7 @@
|
|||
|
||||
.floorplan-distance-input input {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
paddingvar(--space-150) var(--space-250);
|
||||
background: var(--border-default);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: var(--radius-control);
|
||||
|
|
@ -255,7 +255,7 @@
|
|||
/* Responsive adjustments */
|
||||
@media (max-width: 1200px) {
|
||||
.floorplan-panel {
|
||||
right: 20px;
|
||||
rightvar(--space-5);
|
||||
width: 340px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
/* ===== Guided Help Container ===== */
|
||||
.guided-help-container {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
bottomvar(--space-5);
|
||||
leftvar(--space-5);
|
||||
z-index: 200;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
.help-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
}
|
||||
|
||||
.help-icon {
|
||||
|
|
@ -269,9 +269,9 @@
|
|||
/* ===== Responsive Design ===== */
|
||||
@media (max-width: 480px) {
|
||||
.guided-help-container {
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
bottomvar(--space-250);
|
||||
leftvar(--space-250);
|
||||
rightvar(--space-250);
|
||||
}
|
||||
|
||||
.guided-help-panel {
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
gapvar(--space-half);
|
||||
padding: var(--space-2) 0;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
|
|
@ -269,8 +269,8 @@
|
|||
|
||||
.live-panel--left {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
left: 20px;
|
||||
topvar(--space-16);
|
||||
leftvar(--space-5);
|
||||
width: 280px;
|
||||
max-height: calc(100vh - 80px);
|
||||
padding: var(--space-3);
|
||||
|
|
@ -278,16 +278,16 @@
|
|||
|
||||
.live-panel--right {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
bottomvar(--space-5);
|
||||
rightvar(--space-5);
|
||||
width: 400px;
|
||||
padding: var(--space-3);
|
||||
}
|
||||
|
||||
.live-panel--presence {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
right: 20px;
|
||||
topvar(--space-16);
|
||||
rightvar(--space-5);
|
||||
width: 240px;
|
||||
max-height: calc(100vh - 80px);
|
||||
padding: var(--space-3);
|
||||
|
|
@ -296,8 +296,8 @@
|
|||
/* Legacy ID-based selectors (match live.html panel IDs) */
|
||||
#node-panel {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
left: 20px;
|
||||
topvar(--space-16);
|
||||
leftvar(--space-5);
|
||||
width: 280px;
|
||||
max-height: calc(100vh - 80px);
|
||||
background: var(--live-node-bg);
|
||||
|
|
@ -309,8 +309,8 @@
|
|||
|
||||
#chart-panel {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
bottomvar(--space-5);
|
||||
rightvar(--space-5);
|
||||
width: 400px;
|
||||
height: 260px;
|
||||
background: var(--live-chart-bg);
|
||||
|
|
@ -321,8 +321,8 @@
|
|||
|
||||
#presence-panel {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
right: 20px;
|
||||
topvar(--space-16);
|
||||
rightvar(--space-5);
|
||||
width: 300px;
|
||||
background: var(--live-panel-bg);
|
||||
border-radius: var(--radius-card);
|
||||
|
|
@ -342,8 +342,8 @@
|
|||
|
||||
#chart-panel {
|
||||
width: calc(100% - 16px);
|
||||
right: 8px;
|
||||
left: 8px;
|
||||
rightvar(--space-2);
|
||||
leftvar(--space-2);
|
||||
bottom: calc(8px + env(safe-area-inset-bottom));
|
||||
}
|
||||
}
|
||||
|
|
@ -352,9 +352,9 @@
|
|||
@media (max-width: 639px) {
|
||||
#chart-panel {
|
||||
width: calc(100% - 16px);
|
||||
right: 8px;
|
||||
left: 8px;
|
||||
bottom: 8px;
|
||||
rightvar(--space-2);
|
||||
leftvar(--space-2);
|
||||
bottomvar(--space-2);
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
|
|
@ -392,13 +392,13 @@ dialog.app-dialog::backdrop {
|
|||
/* ── Setup toolbar (floats over 3D canvas in setup page) ── */
|
||||
.setup-toolbar {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
bottomvar(--space-5);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
gapvar(--space-2);
|
||||
background: var(--overlay-strong);
|
||||
padding: 8px 16px;
|
||||
paddingvar(--space-2) var(--space-4);
|
||||
border-radius: var(--radius-card);
|
||||
z-index: 100;
|
||||
}
|
||||
|
|
@ -407,7 +407,7 @@ dialog.app-dialog::backdrop {
|
|||
background: var(--slate-5);
|
||||
border: 1px solid var(--border-default);
|
||||
color: var(--text-primary);
|
||||
padding: 8px 16px;
|
||||
paddingvar(--space-2) var(--space-4);
|
||||
border-radius: var(--radius-control);
|
||||
cursor: pointer;
|
||||
font-size: var(--text-sm);
|
||||
|
|
@ -428,8 +428,8 @@ dialog.app-dialog::backdrop {
|
|||
@media (max-width: 639px) {
|
||||
.setup-toolbar {
|
||||
bottom: calc(8px + env(safe-area-inset-bottom));
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
leftvar(--space-2);
|
||||
rightvar(--space-2);
|
||||
transform: none;
|
||||
width: auto;
|
||||
overflow-x: auto;
|
||||
|
|
@ -438,7 +438,7 @@ dialog.app-dialog::backdrop {
|
|||
|
||||
.setup-toolbar button {
|
||||
flex-shrink: 0;
|
||||
padding: 8px 12px;
|
||||
paddingvar(--space-2) var(--space-3);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
}
|
||||
|
|
@ -473,8 +473,8 @@ dialog.app-dialog::backdrop {
|
|||
#diurnal-banner {
|
||||
top: auto;
|
||||
bottom: calc(60px + env(safe-area-inset-bottom));
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
leftvar(--space-2);
|
||||
rightvar(--space-2);
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -492,8 +492,8 @@ dialog.app-dialog::backdrop {
|
|||
#anomaly-learning-banner {
|
||||
top: auto;
|
||||
bottom: calc(60px + env(safe-area-inset-bottom));
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
leftvar(--space-2);
|
||||
rightvar(--space-2);
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -502,7 +502,7 @@ dialog.app-dialog::backdrop {
|
|||
#security-mode-indicator {
|
||||
position: fixed;
|
||||
top: 45px;
|
||||
right: 20px;
|
||||
rightvar(--space-5);
|
||||
z-index: 150;
|
||||
}
|
||||
|
||||
|
|
@ -510,21 +510,21 @@ dialog.app-dialog::backdrop {
|
|||
#security-mode-indicator {
|
||||
top: auto;
|
||||
bottom: calc(60px + env(safe-area-inset-bottom));
|
||||
right: 8px;
|
||||
rightvar(--space-2);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Link health panel (floats over canvas) ── */
|
||||
.link-health-panel {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
right: 20px;
|
||||
topvar(--space-16);
|
||||
rightvar(--space-5);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.link-health-panel {
|
||||
right: 8px;
|
||||
rightvar(--space-2);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -532,8 +532,8 @@ dialog.app-dialog::backdrop {
|
|||
.link-health-panel {
|
||||
top: auto;
|
||||
bottom: calc(60px + env(safe-area-inset-bottom));
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
leftvar(--space-2);
|
||||
rightvar(--space-2);
|
||||
max-height: 40vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
|
@ -542,7 +542,7 @@ dialog.app-dialog::backdrop {
|
|||
/* ── Replay control bar (shown during replay) ── */
|
||||
.replay-control-bar {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
bottomvar(--space-5);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 150;
|
||||
|
|
@ -550,8 +550,8 @@ dialog.app-dialog::backdrop {
|
|||
|
||||
@media (max-width: 639px) {
|
||||
.replay-control-bar {
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
leftvar(--space-2);
|
||||
rightvar(--space-2);
|
||||
transform: none;
|
||||
bottom: calc(60px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
|
@ -560,7 +560,7 @@ dialog.app-dialog::backdrop {
|
|||
/* ── Replay tuning panel (floats over canvas) ── */
|
||||
.replay-tuning-panel {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
topvar(--space-16);
|
||||
right: 340px;
|
||||
width: 280px;
|
||||
z-index: 100;
|
||||
|
|
@ -569,8 +569,8 @@ dialog.app-dialog::backdrop {
|
|||
|
||||
@media (max-width: 1023px) {
|
||||
.replay-tuning-panel {
|
||||
right: 8px;
|
||||
left: 8px;
|
||||
rightvar(--space-2);
|
||||
leftvar(--space-2);
|
||||
width: auto;
|
||||
max-width: 340px;
|
||||
}
|
||||
|
|
@ -578,8 +578,8 @@ dialog.app-dialog::backdrop {
|
|||
|
||||
@media (max-width: 639px) {
|
||||
.replay-tuning-panel {
|
||||
right: 8px;
|
||||
left: 8px;
|
||||
rightvar(--space-2);
|
||||
leftvar(--space-2);
|
||||
width: auto;
|
||||
top: auto;
|
||||
bottom: calc(60px + env(safe-area-inset-bottom));
|
||||
|
|
@ -599,7 +599,7 @@ dialog.app-dialog::backdrop {
|
|||
/* ── Portal editor panel (floats over canvas) ── */
|
||||
#portal-editor-panel {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
topvar(--space-16);
|
||||
left: 320px;
|
||||
width: 240px;
|
||||
z-index: 100;
|
||||
|
|
@ -608,8 +608,8 @@ dialog.app-dialog::backdrop {
|
|||
|
||||
@media (max-width: 1023px) {
|
||||
#portal-editor-panel {
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
leftvar(--space-2);
|
||||
rightvar(--space-2);
|
||||
width: auto;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
|
@ -617,8 +617,8 @@ dialog.app-dialog::backdrop {
|
|||
|
||||
@media (max-width: 639px) {
|
||||
#portal-editor-panel {
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
leftvar(--space-2);
|
||||
rightvar(--space-2);
|
||||
width: auto;
|
||||
top: auto;
|
||||
bottom: calc(60px + env(safe-area-inset-bottom));
|
||||
|
|
@ -630,7 +630,7 @@ dialog.app-dialog::backdrop {
|
|||
/* ── Room editor panel (floats over canvas) ── */
|
||||
#room-editor-panel {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
topvar(--space-16);
|
||||
left: 320px;
|
||||
width: 240px;
|
||||
z-index: 100;
|
||||
|
|
@ -639,8 +639,8 @@ dialog.app-dialog::backdrop {
|
|||
|
||||
@media (max-width: 1023px) {
|
||||
#room-editor-panel {
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
leftvar(--space-2);
|
||||
rightvar(--space-2);
|
||||
width: auto;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
|
@ -648,8 +648,8 @@ dialog.app-dialog::backdrop {
|
|||
|
||||
@media (max-width: 639px) {
|
||||
#room-editor-panel {
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
leftvar(--space-2);
|
||||
rightvar(--space-2);
|
||||
width: auto;
|
||||
top: auto;
|
||||
bottom: calc(60px + env(safe-area-inset-bottom));
|
||||
|
|
@ -661,7 +661,7 @@ dialog.app-dialog::backdrop {
|
|||
/* ── GDOP legend (floats over canvas) ── */
|
||||
#gdop-legend {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
bottomvar(--space-5);
|
||||
left: 320px;
|
||||
z-index: 100;
|
||||
display: none;
|
||||
|
|
@ -669,14 +669,14 @@ dialog.app-dialog::backdrop {
|
|||
|
||||
@media (max-width: 1023px) {
|
||||
#gdop-legend {
|
||||
left: 8px;
|
||||
leftvar(--space-2);
|
||||
bottom: calc(280px + env(safe-area-inset-bottom));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 639px) {
|
||||
#gdop-legend {
|
||||
left: 8px;
|
||||
leftvar(--space-2);
|
||||
bottom: calc(220px + env(safe-area-inset-bottom));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
/* Channel Cards */
|
||||
.channels-list {
|
||||
display: grid;
|
||||
gap: 15px;
|
||||
gapvar(--space-375);
|
||||
margin-bottom: var(--space-5);
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
background: var(--bg-hover);
|
||||
border: 1px solid var(--slate-5);
|
||||
border-radius: var(--radius-card);
|
||||
padding: 15px;
|
||||
paddingvar(--space-375);
|
||||
}
|
||||
|
||||
.channel-header {
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
|
||||
.channel-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
}
|
||||
|
||||
.channel-details {
|
||||
|
|
@ -109,7 +109,7 @@
|
|||
border: none;
|
||||
font-size: var(--text-xl);
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
paddingvar(--space-125);
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
|
@ -128,7 +128,7 @@
|
|||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
color: var(--text-on-accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
@ -147,7 +147,7 @@
|
|||
.time-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
gapvar(--space-125);
|
||||
}
|
||||
|
||||
.time-field label {
|
||||
|
|
@ -167,18 +167,18 @@
|
|||
.days-selector {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
}
|
||||
|
||||
.day-checkbox {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
gapvar(--space-375);
|
||||
}
|
||||
|
||||
.day-checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
gapvar(--space-125);
|
||||
color: var(--text-on-accent);
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
|
|
@ -194,14 +194,14 @@
|
|||
.batching-config {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
gapvar(--space-375);
|
||||
}
|
||||
|
||||
.batch-window,
|
||||
.max-batch-size {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
gapvar(--space-125);
|
||||
}
|
||||
|
||||
.batch-window label,
|
||||
|
|
@ -231,8 +231,8 @@
|
|||
.event-type-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 10px;
|
||||
gapvar(--space-375);
|
||||
paddingvar(--space-250);
|
||||
background: var(--bg-hover);
|
||||
border-radius: var(--radius-control);
|
||||
}
|
||||
|
|
@ -283,8 +283,8 @@
|
|||
content: "";
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
leftvar(--space-075);
|
||||
bottomvar(--space-075);
|
||||
background-color: var(--text-on-accent);
|
||||
transition: 0.3s;
|
||||
border-radius: 50%;
|
||||
|
|
@ -344,7 +344,7 @@
|
|||
color: var(--text-muted);
|
||||
font-size: var(--text-3xl);
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
paddingvar(--space-125);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
|
|
@ -382,7 +382,7 @@
|
|||
|
||||
.auth-fields {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
}
|
||||
|
||||
.auth-fields input {
|
||||
|
|
@ -403,12 +403,12 @@
|
|||
#modal-event-types {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
justify-content: flex-end;
|
||||
margin-top: var(--space-5);
|
||||
}
|
||||
|
|
@ -416,8 +416,8 @@
|
|||
/* Toast Notifications */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
bottomvar(--space-5);
|
||||
rightvar(--space-5);
|
||||
padding: 15px var(--space-5);
|
||||
border-radius: var(--radius-card);
|
||||
color: var(--text-on-accent);
|
||||
|
|
@ -466,16 +466,16 @@
|
|||
/* Responsive Design */
|
||||
@media (max-width: 600px) {
|
||||
.notification-settings {
|
||||
padding: 15px;
|
||||
paddingvar(--space-375);
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
padding: 15px;
|
||||
paddingvar(--space-375);
|
||||
}
|
||||
|
||||
.time-range {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
}
|
||||
|
||||
.channels-list {
|
||||
|
|
@ -551,7 +551,7 @@
|
|||
|
||||
.channel-config-group {
|
||||
margin-bottom: var(--space-5);
|
||||
padding: 15px;
|
||||
paddingvar(--space-375);
|
||||
background: var(--bg-hover);
|
||||
border-radius: var(--radius-control);
|
||||
}
|
||||
|
|
@ -575,14 +575,14 @@
|
|||
.panel-days-selector {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.panel-day-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
gapvar(--space-125);
|
||||
color: var(--text-on-accent);
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
|
|
@ -597,8 +597,8 @@
|
|||
.panel-event-type-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 10px;
|
||||
gapvar(--space-375);
|
||||
paddingvar(--space-250);
|
||||
background: var(--bg-hover);
|
||||
border-radius: var(--radius-control);
|
||||
}
|
||||
|
|
@ -648,8 +648,8 @@
|
|||
content: "";
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
leftvar(--space-075);
|
||||
bottomvar(--space-075);
|
||||
background-color: var(--text-on-accent);
|
||||
transition: 0.3s;
|
||||
border-radius: 50%;
|
||||
|
|
|
|||
|
|
@ -263,12 +263,12 @@
|
|||
/* ----- Toast Notifications ----- */
|
||||
#toast-container {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
bottomvar(--space-5);
|
||||
rightvar(--space-5);
|
||||
z-index: 3000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
|
@ -385,7 +385,7 @@
|
|||
/* ----- Mode Toggle Bar ----- */
|
||||
.mode-toggle-bar {
|
||||
position: fixed;
|
||||
top: 40px;
|
||||
topvar(--space-10);
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 44px;
|
||||
|
|
@ -426,8 +426,8 @@
|
|||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
leftvar(--space-4);
|
||||
rightvar(--space-4);
|
||||
height: 2px;
|
||||
background: var(--blue-10);
|
||||
border-radius: 1px 1px 0 0;
|
||||
|
|
@ -498,7 +498,7 @@
|
|||
background: var(--bg-hover);
|
||||
border-radius: var(--radius-control);
|
||||
outline: none;
|
||||
margin: 10px;
|
||||
marginvar(--space-250);
|
||||
}
|
||||
|
||||
.panel-form-group input[type="range"]::-webkit-slider-thumb {
|
||||
|
|
@ -538,7 +538,7 @@
|
|||
.panel-form-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
cursor: pointer;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
|
@ -627,7 +627,7 @@
|
|||
|
||||
.panel-btn-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
|
||||
|
|
@ -828,7 +828,7 @@
|
|||
color: var(--warn);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
}
|
||||
|
||||
#security-dialog .learning-warning::before {
|
||||
|
|
@ -900,7 +900,7 @@
|
|||
|
||||
.alert-banner-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
}
|
||||
|
||||
.alert-banner-btn {
|
||||
|
|
@ -1027,7 +1027,7 @@
|
|||
.learning-ready-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
padding: var(--space-1) 10px;
|
||||
background: var(--ok-muted);
|
||||
border-radius: var(--radius-card);
|
||||
|
|
@ -1091,7 +1091,7 @@
|
|||
/* Arm/Disarm Button */
|
||||
.security-arm-btn {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
paddingvar(--space-350);
|
||||
border-radius: var(--radius-card);
|
||||
font-size: var(--text-md);
|
||||
font-weight: 600;
|
||||
|
|
@ -1101,7 +1101,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
}
|
||||
|
||||
.security-arm-btn.disarmed {
|
||||
|
|
@ -1257,9 +1257,9 @@
|
|||
}
|
||||
|
||||
#toast-container {
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
leftvar(--space-250);
|
||||
rightvar(--space-250);
|
||||
bottomvar(--space-250);
|
||||
}
|
||||
|
||||
.toast {
|
||||
|
|
@ -1671,7 +1671,7 @@
|
|||
|
||||
.contributing-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
paddingvar(--space-half) var(--space-150);
|
||||
border-radius: var(--radius-control);
|
||||
font-size: var(--text-2xs);
|
||||
font-weight: 600;
|
||||
|
|
@ -1702,7 +1702,7 @@
|
|||
.ble-match-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
|
|
@ -1732,7 +1732,7 @@
|
|||
.ble-match-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
}
|
||||
|
||||
.ble-match-detail {
|
||||
|
|
@ -1787,7 +1787,7 @@
|
|||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--slate-11);
|
||||
transition: background 0.2s, color 0.2s;
|
||||
|
|
@ -1845,7 +1845,7 @@
|
|||
|
||||
.links-table th,
|
||||
.links-table td {
|
||||
padding: 6px 6px;
|
||||
paddingvar(--space-150) var(--space-150);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1868,7 +1868,7 @@
|
|||
|
||||
.volume-tools {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
|
@ -1927,7 +1927,7 @@
|
|||
|
||||
.trigger-card-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
|
@ -1935,8 +1935,8 @@
|
|||
.error-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
padding: 2px 7px;
|
||||
gapvar(--space-075);
|
||||
paddingvar(--space-half) var(--space-175);
|
||||
font-size: var(--text-3xs);
|
||||
font-weight: 600;
|
||||
border-radius: var(--radius-control);
|
||||
|
|
@ -1949,8 +1949,8 @@
|
|||
.disabled-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
padding: 2px 7px;
|
||||
gapvar(--space-075);
|
||||
paddingvar(--space-half) var(--space-175);
|
||||
font-size: var(--text-3xs);
|
||||
font-weight: 600;
|
||||
border-radius: var(--radius-control);
|
||||
|
|
@ -1963,8 +1963,8 @@
|
|||
.warning-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
padding: 2px 7px;
|
||||
gapvar(--space-075);
|
||||
paddingvar(--space-half) var(--space-175);
|
||||
font-size: var(--text-3xs);
|
||||
font-weight: 600;
|
||||
border-radius: var(--radius-control);
|
||||
|
|
@ -2070,8 +2070,8 @@
|
|||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
left: 2px;
|
||||
bottom: 2px;
|
||||
leftvar(--space-half);
|
||||
bottomvar(--space-half);
|
||||
background: var(--slate-11);
|
||||
border-radius: 50%;
|
||||
transition: transform 0.2s, background 0.2s;
|
||||
|
|
@ -2125,7 +2125,7 @@
|
|||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
paddingvar(--space-150) var(--space-250);
|
||||
font-size: var(--text-sm);
|
||||
background: var(--border-subtle);
|
||||
border: 1px solid var(--bg-active);
|
||||
|
|
@ -2382,7 +2382,7 @@
|
|||
/* Simulator Panel */
|
||||
.simulator-panel {
|
||||
position: fixed;
|
||||
top: 40px;
|
||||
topvar(--space-10);
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 360px;
|
||||
|
|
@ -2459,7 +2459,7 @@
|
|||
.sim-space-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
|
|
@ -2473,7 +2473,7 @@
|
|||
|
||||
.sim-space-controls input[type="number"] {
|
||||
width: 80px;
|
||||
padding: 6px 10px;
|
||||
paddingvar(--space-150) var(--space-250);
|
||||
background: var(--border-subtle);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: var(--radius-control);
|
||||
|
|
@ -2491,7 +2491,7 @@
|
|||
|
||||
.sim-tool-btn {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
paddingvar(--space-250);
|
||||
background: var(--border-subtle);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: var(--radius-control);
|
||||
|
|
@ -2645,7 +2645,7 @@
|
|||
.sim-gdop-controls label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--slate-11);
|
||||
cursor: pointer;
|
||||
|
|
@ -2732,7 +2732,7 @@
|
|||
.sim-recommendation {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
padding: 10px var(--space-3);
|
||||
background: var(--border-subtle);
|
||||
border-radius: var(--radius-control);
|
||||
|
|
@ -3044,7 +3044,7 @@
|
|||
align-items: center;
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-muted);
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
}
|
||||
|
||||
.sim-gdop-controls input[type="checkbox"] {
|
||||
|
|
@ -3178,7 +3178,7 @@
|
|||
}
|
||||
|
||||
.sim-rec-priority {
|
||||
padding: 2px 6px;
|
||||
paddingvar(--space-half) var(--space-150);
|
||||
border-radius: var(--radius-control);
|
||||
font-size: var(--text-3xs);
|
||||
font-weight: 600;
|
||||
|
|
@ -3272,7 +3272,7 @@
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
}
|
||||
|
||||
.gdop-stat-item {
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
.replay-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
gapvar(--space-half);
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
border: 1px solid var(--bg-hover);
|
||||
border-radius: var(--radius-control);
|
||||
color: var(--slate-11);
|
||||
padding: 6px 10px;
|
||||
paddingvar(--space-150) var(--space-250);
|
||||
font-size: var(--text-sm);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
|
@ -185,7 +185,7 @@
|
|||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
}
|
||||
|
||||
.replay-tune-btn:hover {
|
||||
|
|
@ -416,7 +416,7 @@
|
|||
/* ----- Replay Mode Indicator ----- */
|
||||
.replay-mode-indicator {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
bottomvar(--space-5);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--blue-10);
|
||||
|
|
|
|||
|
|
@ -348,7 +348,7 @@
|
|||
|
||||
.alert-banner-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
|
@ -390,7 +390,7 @@
|
|||
#security-mode-indicator {
|
||||
position: fixed;
|
||||
top: 45px;
|
||||
right: 20px;
|
||||
rightvar(--space-5);
|
||||
background: var(--alert);
|
||||
color: var(--text-on-accent);
|
||||
padding: 6px var(--space-3);
|
||||
|
|
@ -468,7 +468,7 @@
|
|||
font-size: var(--text-sm);
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
z-index: 150;
|
||||
box-shadow: 0 4px 12px var(--blue-border);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -348,7 +348,7 @@ body.simple-mode {
|
|||
.simple-room-card .room-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 500;
|
||||
padding: var(--space-1) 10px;
|
||||
|
|
@ -373,7 +373,7 @@ body.simple-mode {
|
|||
.simple-room-card .room-occupants {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
|
|
@ -626,13 +626,13 @@ body.simple-mode {
|
|||
|
||||
.simple-quick-actions .action-badge {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
topvar(--space-1);
|
||||
rightvar(--space-1);
|
||||
background: var(--simple-accent-red);
|
||||
color: var(--text-on-accent);
|
||||
font-size: var(--text-3xs);
|
||||
font-weight: 600;
|
||||
padding: 2px 6px;
|
||||
paddingvar(--space-half) var(--space-150);
|
||||
border-radius: var(--radius-card);
|
||||
min-width: 16px;
|
||||
text-align: center;
|
||||
|
|
@ -911,7 +911,7 @@ body.simple-mode {
|
|||
|
||||
.simple-security-toggle .security-toggle-btn {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
paddingvar(--space-350);
|
||||
border-radius: var(--radius-card);
|
||||
font-size: var(--text-base);
|
||||
font-weight: 600;
|
||||
|
|
|
|||
|
|
@ -429,7 +429,7 @@
|
|||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-2);
|
||||
padding: 10px;
|
||||
paddingvar(--space-250);
|
||||
background: var(--bg-hover);
|
||||
border-radius: var(--radius-control);
|
||||
border-left: 3px solid var(--slate-7);
|
||||
|
|
@ -459,7 +459,7 @@
|
|||
.sim-shopping-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
}
|
||||
|
||||
.sim-shopping-item {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
.sleep-summary-card {
|
||||
position: fixed;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
topvar(--space-4);
|
||||
rightvar(--space-4);
|
||||
z-index: 1000;
|
||||
background: var(--bg-card, var(--bg-card));
|
||||
border: 1px solid var(--border-color, var(--slate-5));
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
color: var(--text-muted, var(--text-muted));
|
||||
font-size: var(--text-xl);
|
||||
cursor: pointer;
|
||||
padding: 2px 6px;
|
||||
paddingvar(--space-half) var(--space-150);
|
||||
line-height: 1;
|
||||
border-radius: var(--radius-control);
|
||||
}
|
||||
|
|
@ -166,7 +166,7 @@
|
|||
color: var(--text-muted, var(--text-muted));
|
||||
font-size: var(--text-lg);
|
||||
cursor: pointer;
|
||||
padding: 2px 6px;
|
||||
paddingvar(--space-half) var(--space-150);
|
||||
border-radius: var(--radius-control);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
background: transparent;
|
||||
border: 1px solid var(--border-color, var(--bg-hover));
|
||||
border-radius: var(--radius-control);
|
||||
padding: 6px 10px;
|
||||
paddingvar(--space-150) var(--space-250);
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary, var(--text-secondary));
|
||||
display: flex;
|
||||
|
|
@ -115,8 +115,8 @@
|
|||
.timeline-category-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
gapvar(--space-150);
|
||||
paddingvar(--space-150) var(--space-250);
|
||||
background: var(--bg-primary, var(--bg-page));
|
||||
border: 1px solid var(--border-color, var(--bg-hover));
|
||||
border-radius: var(--radius-control);
|
||||
|
|
@ -176,7 +176,7 @@
|
|||
background: var(--bg-primary, var(--bg-page));
|
||||
border: 1px solid var(--border-color, var(--bg-hover));
|
||||
color: var(--text-primary, var(--slate-11));
|
||||
padding: 6px 10px;
|
||||
paddingvar(--space-150) var(--space-250);
|
||||
border-radius: var(--radius-control);
|
||||
font-size: var(--text-sm);
|
||||
min-width: 100px;
|
||||
|
|
@ -297,7 +297,7 @@
|
|||
.timeline-event-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
|
|
@ -327,7 +327,7 @@
|
|||
font-size: var(--text-sm);
|
||||
font-weight: 500;
|
||||
color: var(--text-primary, var(--slate-11));
|
||||
margin: 2px;
|
||||
marginvar(--space-half);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
|
|
@ -629,7 +629,7 @@
|
|||
color: var(--text-on-accent);
|
||||
font-size: var(--text-2xs);
|
||||
font-weight: 600;
|
||||
padding: 2px 6px;
|
||||
paddingvar(--space-half) var(--space-150);
|
||||
border-radius: var(--radius-card);
|
||||
margin-left: var(--space-2);
|
||||
}
|
||||
|
|
@ -654,7 +654,7 @@
|
|||
.timeline-live-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--color-success, var(--ok));
|
||||
padding: var(--space-1) var(--space-2);
|
||||
|
|
@ -706,8 +706,8 @@
|
|||
#timeline-now-replaying {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 2px 10px;
|
||||
gapvar(--space-150);
|
||||
paddingvar(--space-half) var(--space-250);
|
||||
margin-left: var(--space-2);
|
||||
background: color-mix(in srgb, var(--blue-9) 20%, transparent);
|
||||
color: var(--blue-11);
|
||||
|
|
@ -783,7 +783,7 @@
|
|||
}
|
||||
|
||||
.timeline-event {
|
||||
padding: 10px;
|
||||
paddingvar(--space-250);
|
||||
}
|
||||
|
||||
.timeline-event-actions {
|
||||
|
|
@ -900,7 +900,7 @@
|
|||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-secondary, var(--text-secondary));
|
||||
padding: 6px;
|
||||
paddingvar(--space-150);
|
||||
border-radius: var(--radius-control);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
|
|
@ -943,7 +943,7 @@
|
|||
padding: var(--space-2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
}
|
||||
|
||||
/* Compact Event Card for Sidebar */
|
||||
|
|
@ -1040,7 +1040,7 @@
|
|||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
font-size: var(--text-2xs);
|
||||
color: var(--text-muted, var(--text-muted));
|
||||
}
|
||||
|
|
@ -1060,7 +1060,7 @@
|
|||
/* Event Actions */
|
||||
.sidebar-timeline-event-actions {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
gapvar(--space-half);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
|
@ -1239,8 +1239,8 @@
|
|||
.now-replaying-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 2px 10px;
|
||||
gapvar(--space-150);
|
||||
paddingvar(--space-half) var(--space-250);
|
||||
margin-left: var(--space-2);
|
||||
background: color-mix(in srgb, var(--blue-9) 20%, transparent);
|
||||
color: var(--blue-11);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
background: var(--warn-bg);
|
||||
border: 1px solid var(--warn-border);
|
||||
border-radius: var(--radius-control);
|
||||
padding: 10px;
|
||||
paddingvar(--space-250);
|
||||
margin-bottom: var(--space-2);
|
||||
font-size: var(--text-xs);
|
||||
animation: troubleshoot-fade-in 0.3s ease-out;
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
.troubleshoot-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
margin-bottom: var(--space-2);
|
||||
color: var(--warn);
|
||||
font-weight: 600;
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
.troubleshoot-timeline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gapvar(--space-150);
|
||||
}
|
||||
|
||||
.troubleshoot-step {
|
||||
|
|
@ -285,7 +285,7 @@
|
|||
/* Dismiss all button */
|
||||
.spaxel-dismiss-all {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
bottomvar(--space-5);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--bg-hover);
|
||||
|
|
@ -347,7 +347,7 @@
|
|||
|
||||
.post-cal-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
gapvar(--space-250);
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
|
|
|||
444
dashboard/js/replay.test.js
Normal file
444
dashboard/js/replay.test.js
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
/**
|
||||
* Tests for SpaxelReplay time-travel module
|
||||
*
|
||||
* Covers:
|
||||
* - jumpToTime creates replay session and fetches blobs
|
||||
* - exitReplayMode cleans up state and notifies Viz3D
|
||||
* - pauseLiveMode enters replay with correct time window
|
||||
* - Timeline loop polls for session state during playback
|
||||
* - Scrubber updates position based on session state
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Mock DOM elements
|
||||
function setupDOM() {
|
||||
document.body.innerHTML = `
|
||||
<div id="status-bar"></div>
|
||||
<div id="scene-container"></div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Mock Viz3D on both global and window
|
||||
var viz3dMock = {
|
||||
enterReplayMode: jest.fn(),
|
||||
exitReplayMode: jest.fn(),
|
||||
updateReplayBlobs: jest.fn()
|
||||
};
|
||||
global.Viz3D = viz3dMock;
|
||||
|
||||
// Mock SpaxelApp on both global and window
|
||||
var appMock = {
|
||||
showToast: jest.fn()
|
||||
};
|
||||
global.SpaxelApp = appMock;
|
||||
|
||||
// Mock SpaxelSidebarTimeline on both global and window
|
||||
var sidebarMock = {
|
||||
clearSelection: jest.fn(),
|
||||
hideNowReplayingChip: jest.fn()
|
||||
};
|
||||
global.SpaxelSidebarTimeline = sidebarMock;
|
||||
|
||||
// Mock SpaxelRouter on both global and window
|
||||
var routerMock = {
|
||||
navigate: jest.fn()
|
||||
};
|
||||
global.SpaxelRouter = routerMock;
|
||||
|
||||
describe('SpaxelReplay', function() {
|
||||
let Replay;
|
||||
let fetchMock;
|
||||
|
||||
beforeEach(function() {
|
||||
jest.clearAllMocks();
|
||||
setupDOM();
|
||||
|
||||
// Default fetch mock returns store info
|
||||
fetchMock = jest.fn(function(url, options) {
|
||||
if (url === '/api/replay/sessions') {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: function() {
|
||||
return Promise.resolve({
|
||||
has_data: true,
|
||||
oldest_timestamp_ms: Date.now() - 3600000,
|
||||
newest_timestamp_ms: Date.now(),
|
||||
sessions: []
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (url === '/api/replay/jump-to-time') {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: function() {
|
||||
return Promise.resolve({
|
||||
session_id: 'jump-session-1',
|
||||
timestamp_ms: 1710519800000,
|
||||
from_ms: 1710519795000,
|
||||
to_ms: 1710519805000,
|
||||
state: 'paused'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Session state endpoint (with blobs)
|
||||
var sessionMatch = url.match(/\/api\/replay\/session\/(.+)$/);
|
||||
if (sessionMatch) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: function() {
|
||||
return Promise.resolve({
|
||||
session_id: sessionMatch[1],
|
||||
current_ms: 1710519800000,
|
||||
from_ms: 1710519795000,
|
||||
to_ms: 1710519805000,
|
||||
state: 'paused',
|
||||
speed: 1,
|
||||
blobs: [
|
||||
{
|
||||
id: 1,
|
||||
x: 2.5,
|
||||
y: 1.3,
|
||||
z: 0.8,
|
||||
vx: 0.1,
|
||||
vy: 0.0,
|
||||
vz: 0.0,
|
||||
weight: 0.85,
|
||||
posture: 'standing'
|
||||
}
|
||||
],
|
||||
timestamp_ms: 1710519800000
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Stop session
|
||||
if (url === '/api/replay/stop') {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: function() {
|
||||
return Promise.resolve({ status: 'stopped' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Start session
|
||||
if (url === '/api/replay/start') {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: function() {
|
||||
return Promise.resolve({
|
||||
session_id: 'start-session-1',
|
||||
from_ms: 1710519740000,
|
||||
to_ms: 1710519800000,
|
||||
speed: 1,
|
||||
state: 'paused'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
ok: false,
|
||||
statusText: 'Not Found'
|
||||
});
|
||||
});
|
||||
|
||||
global.fetch = fetchMock;
|
||||
|
||||
// Load the module
|
||||
jest.isolateModules(function() {
|
||||
require('./replay.js');
|
||||
Replay = window.SpaxelReplay;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Initialization Tests
|
||||
// ============================================
|
||||
describe('Initialization', function() {
|
||||
test('initializes and fetches store info', function() {
|
||||
expect(Replay).toBeTruthy();
|
||||
expect(Replay.isReplayMode()).toBe(false);
|
||||
expect(Replay.isPaused()).toBe(false);
|
||||
// Should have called fetch for store info
|
||||
expect(fetchMock).toHaveBeenCalledWith('/api/replay/sessions');
|
||||
});
|
||||
|
||||
test('getSession returns initial empty state', function() {
|
||||
var session = Replay.getSession();
|
||||
expect(session.id).toBeNull();
|
||||
expect(session.fromMs).toBeNull();
|
||||
expect(session.state).toBe('stopped');
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Jump-to-Time Tests
|
||||
// ============================================
|
||||
describe('jumpToTime', function() {
|
||||
test('creates replay session centered on timestamp', function() {
|
||||
return Replay.jumpToTime(1710519800000).then(function(data) {
|
||||
expect(fetchMock).toHaveBeenCalledWith('/api/replay/jump-to-time', expect.objectContaining({
|
||||
method: 'POST',
|
||||
headers: expect.objectContaining({ 'Content-Type': 'application/json' })
|
||||
}));
|
||||
|
||||
// Check the request body
|
||||
var call = fetchMock.mock.calls.find(function(c) { return c[0] === '/api/replay/jump-to-time'; });
|
||||
var body = JSON.parse(call[1].body);
|
||||
expect(body.timestamp_ms).toBe(1710519800000);
|
||||
expect(body.window_ms).toBe(5000);
|
||||
});
|
||||
});
|
||||
|
||||
test('uses custom window_ms when provided', function() {
|
||||
fetchMock.mockImplementation(function(url) {
|
||||
if (url === '/api/replay/jump-to-time') {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: function() {
|
||||
return Promise.resolve({
|
||||
session_id: 'custom-window',
|
||||
timestamp_ms: 1710519800000,
|
||||
from_ms: 1710519790000,
|
||||
to_ms: 1710519810000,
|
||||
state: 'paused'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (url.match(/\/api\/replay\/session\//)) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: function() {
|
||||
return Promise.resolve({
|
||||
session_id: 'custom-window',
|
||||
current_ms: 1710519800000,
|
||||
from_ms: 1710519790000,
|
||||
to_ms: 1710519810000,
|
||||
state: 'paused',
|
||||
blobs: [],
|
||||
timestamp_ms: 1710519800000
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (url === '/api/replay/sessions') {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: function() { return Promise.resolve({ has_data: true, sessions: [] }); }
|
||||
});
|
||||
}
|
||||
return Promise.resolve({ ok: false, statusText: 'Not Found' });
|
||||
});
|
||||
|
||||
// Need to re-init with new fetch mock
|
||||
jest.isolateModules(function() {
|
||||
require('./replay.js');
|
||||
var R = window.SpaxelReplay;
|
||||
R.jumpToTime(1710519800000, 10000).then(function() {
|
||||
var call = fetchMock.mock.calls.find(function(c) { return c[0] === '/api/replay/jump-to-time'; });
|
||||
var body = JSON.parse(call[1].body);
|
||||
expect(body.window_ms).toBe(10000);
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
test('enters replay mode after successful jump', function() {
|
||||
return Replay.jumpToTime(1710519800000).then(function() {
|
||||
expect(Replay.isReplayMode()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('shows replay control bar after jump', function() {
|
||||
return Replay.jumpToTime(1710519800000).then(function() {
|
||||
var bar = document.getElementById('replay-control-bar');
|
||||
expect(bar).toBeTruthy();
|
||||
expect(bar.style.display).toBe('block');
|
||||
});
|
||||
});
|
||||
|
||||
test('calls Viz3D.enterReplayMode', function() {
|
||||
return Replay.jumpToTime(1710519800000).then(function() {
|
||||
expect(global.Viz3D.enterReplayMode).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
test('fetches session blobs and feeds them to Viz3D', function() {
|
||||
return Replay.jumpToTime(1710519800000).then(function() {
|
||||
// Should have fetched session state for blobs
|
||||
var sessionCall = fetchMock.mock.calls.find(function(c) {
|
||||
return c[0] && c[0].match(/\/api\/replay\/session\//);
|
||||
});
|
||||
expect(sessionCall).toBeTruthy();
|
||||
|
||||
// Should have fed blobs to Viz3D
|
||||
expect(global.Viz3D.updateReplayBlobs).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: 1,
|
||||
x: 2.5,
|
||||
y: 1.3,
|
||||
z: 0.8
|
||||
})
|
||||
]),
|
||||
1710519800000
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('updates session state from jump response', function() {
|
||||
return Replay.jumpToTime(1710519800000).then(function() {
|
||||
var session = Replay.getSession();
|
||||
expect(session.id).toBe('jump-session-1');
|
||||
expect(session.currentMs).toBe(1710519800000);
|
||||
expect(session.state).toBe('paused');
|
||||
});
|
||||
});
|
||||
|
||||
test('handles API error gracefully', function() {
|
||||
fetchMock.mockImplementation(function(url) {
|
||||
if (url === '/api/replay/sessions') {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: function() { return Promise.resolve({ has_data: false, sessions: [] }); }
|
||||
});
|
||||
}
|
||||
if (url === '/api/replay/jump-to-time') {
|
||||
return Promise.resolve({
|
||||
ok: false,
|
||||
statusText: 'Internal Server Error'
|
||||
});
|
||||
}
|
||||
return Promise.resolve({ ok: false, statusText: 'Not Found' });
|
||||
});
|
||||
|
||||
jest.isolateModules(function() {
|
||||
require('./replay.js');
|
||||
return window.SpaxelReplay.jumpToTime(1710519800000).catch(function(err) {
|
||||
expect(err.message).toContain('Failed to jump to time');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Exit Replay Mode Tests
|
||||
// ============================================
|
||||
describe('exitReplayMode', function() {
|
||||
test('exits replay mode and clears state', function() {
|
||||
return Replay.jumpToTime(1710519800000).then(function() {
|
||||
expect(Replay.isReplayMode()).toBe(true);
|
||||
|
||||
return Replay.exitReplay();
|
||||
}).then(function() {
|
||||
expect(Replay.isReplayMode()).toBe(false);
|
||||
expect(Replay.isPaused()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test('hides replay control bar', function() {
|
||||
return Replay.jumpToTime(1710519800000).then(function() {
|
||||
return Replay.exitReplay();
|
||||
}).then(function() {
|
||||
var bar = document.getElementById('replay-control-bar');
|
||||
expect(bar.style.display).toBe('none');
|
||||
});
|
||||
});
|
||||
|
||||
test('calls Viz3D.exitReplayMode', function() {
|
||||
return Replay.jumpToTime(1710519800000).then(function() {
|
||||
return Replay.exitReplay();
|
||||
}).then(function() {
|
||||
expect(global.Viz3D.exitReplayMode).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
test('clears sidebar timeline selection and chip', function() {
|
||||
return Replay.jumpToTime(1710519800000).then(function() {
|
||||
return Replay.exitReplay();
|
||||
}).then(function() {
|
||||
expect(global.SpaxelSidebarTimeline.clearSelection).toHaveBeenCalled();
|
||||
expect(global.SpaxelSidebarTimeline.hideNowReplayingChip).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
test('navigates back to live mode', function() {
|
||||
return Replay.jumpToTime(1710519800000).then(function() {
|
||||
return Replay.exitReplay();
|
||||
}).then(function() {
|
||||
expect(global.SpaxelRouter.navigate).toHaveBeenCalledWith('live');
|
||||
});
|
||||
});
|
||||
|
||||
test('stops the replay session via API', function() {
|
||||
return Replay.jumpToTime(1710519800000).then(function() {
|
||||
return Replay.exitReplay();
|
||||
}).then(function() {
|
||||
var stopCall = fetchMock.mock.calls.find(function(c) { return c[0] === '/api/replay/stop'; });
|
||||
expect(stopCall).toBeTruthy();
|
||||
var body = JSON.parse(stopCall[1].body);
|
||||
expect(body.session_id).toBe('jump-session-1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Pause Live Mode Tests
|
||||
// ============================================
|
||||
describe('pauseLiveMode', function() {
|
||||
test('creates replay window 60 seconds before now', function() {
|
||||
var beforeMs = Date.now();
|
||||
|
||||
Replay.pauseLive();
|
||||
|
||||
var startCall = fetchMock.mock.calls.find(function(c) { return c[0] === '/api/replay/start'; });
|
||||
expect(startCall).toBeTruthy();
|
||||
var body = JSON.parse(startCall[1].body);
|
||||
var fromMs = new Date(body.from_iso8601).getTime();
|
||||
var toMs = new Date(body.to_iso8601).getTime();
|
||||
|
||||
// Should be approximately 60s window
|
||||
var windowMs = toMs - fromMs;
|
||||
expect(windowMs).toBeGreaterThanOrEqual(59000);
|
||||
expect(windowMs).toBeLessThanOrEqual(61000);
|
||||
expect(fromMs).toBeGreaterThanOrEqual(beforeMs - 61000);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Replay Control Bar UI Tests
|
||||
// ============================================
|
||||
describe('Replay Control Bar', function() {
|
||||
test('control bar is created in the DOM', function() {
|
||||
var bar = document.getElementById('replay-control-bar');
|
||||
expect(bar).toBeTruthy();
|
||||
});
|
||||
|
||||
test('control bar has all required buttons', function() {
|
||||
expect(document.getElementById('replay-back-btn')).toBeTruthy();
|
||||
expect(document.getElementById('replay-play-btn')).toBeTruthy();
|
||||
expect(document.getElementById('replay-speed')).toBeTruthy();
|
||||
expect(document.getElementById('replay-scrubber')).toBeTruthy();
|
||||
expect(document.getElementById('replay-close-btn')).toBeTruthy();
|
||||
expect(document.getElementById('replay-tune-btn')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('control bar is hidden by default', function() {
|
||||
var bar = document.getElementById('replay-control-bar');
|
||||
expect(bar.style.display).toBe('none');
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue