Perk-a-cola redesign + surface RCON map/gametype/roster/health

Themes the fleet manager around the Electric Cherry perk-a-cola from Mob of
the Dead — cherry-red art-deco label with electric cyan discharge. New
design tokens (Alfa Slab One + Inter + JetBrains Mono, self-hosted), full
redesign of layout/dashboard/cards/detail page, tokens pass on
deployments/betas/pipeline/beta/diff, cherry+lightning favicon.

Lights up server data that RCON was already fetching but the UI dropped.
parsePlayerRoster() extracts num/score/ping/guid/name from both status
dialects. Worker persists last_map/last_gametype/last_roster/last_seen_at/
last_error (new columns with idempotent boot-time ALTERs) and emits them
over SSE. Fleet cards now show health dot, map · gametype, top-3 roster
preview, and last-seen. Detail page adds a sortable full roster with
distinct empty-lobby vs offline states. New health classifier
(online/stale/offline) drives the visual state.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-09-12 08:20:54 -04:00
co-authored by Claude Opus 4.7
parent 854f5532a9
commit 9bff47a9a4
29 changed files with 1636 additions and 356 deletions
+78
View File
@@ -0,0 +1,78 @@
/* Electric Cherry — design tokens.
Named after the perk-a-cola from Mob of the Dead (CoD Zombies):
deep cherry-red art-deco label + electric cyan discharge. */
@import '@fontsource/alfa-slab-one/400.css';
@import '@fontsource/inter/400.css';
@import '@fontsource/inter/500.css';
@import '@fontsource/inter/600.css';
@import '@fontsource/inter/700.css';
@import '@fontsource/jetbrains-mono/400.css';
@import '@fontsource/jetbrains-mono/500.css';
:root {
/* Cherry — surfaces + primary */
--cherry-975: #0b0407;
--cherry-925: #170a0f;
--cherry-875: #22101a;
--cherry-825: #2c1420;
--cherry-700: #4a0f1e;
--cherry-600: #7a0f22;
--cherry-500: #c8102e;
--cherry-400: #e93656;
--cherry-300: #ff7a92;
/* Spark — electric cyan */
--spark-600: #14b8d0;
--spark-500: #22e0ff;
--spark-400: #7ef1ff;
--spark-glow: 0 0 8px rgba(34, 224, 255, 0.55);
--spark-glow-lg: 0 0 14px rgba(34, 224, 255, 0.45);
/* Neutrals + label */
--cream: #f2e6c8;
--cream-dim: #c9b98f;
--ink-100: #f5eaec;
--ink-300: #c8a4ac;
--ink-500: #7a5560;
--ink-700: #4a2f36;
/* State colors */
--amber-500: #ffb020;
--amber-glow: 0 0 8px rgba(255, 176, 32, 0.4);
--danger-500: #ff3050;
--success-500: var(--spark-500);
/* Typography */
--font-display: 'Alfa Slab One', 'Georgia', serif;
--font-body: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, monospace;
/* Radii */
--r-sm: 3px;
--r-md: 6px;
--r-lg: 10px;
/* Shadows */
--shadow-card: 0 2px 12px rgba(0, 0, 0, 0.5);
--shadow-panel: 0 4px 24px rgba(0, 0, 0, 0.4);
}
@keyframes electric-pulse {
0%, 100% {
box-shadow: 0 0 0 0 rgba(34, 224, 255, 0.55), var(--spark-glow);
}
50% {
box-shadow: 0 0 0 6px rgba(34, 224, 255, 0), var(--spark-glow-lg);
}
}
@keyframes cherry-fizz {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-1px); }
}
@keyframes spark-slide {
from { background-position: -100% 0; }
to { background-position: 200% 0; }
}