diff --git a/server/web/src/pages/CruciblePage.css b/server/web/src/pages/CruciblePage.css
index da1b25d..dd1e875 100644
--- a/server/web/src/pages/CruciblePage.css
+++ b/server/web/src/pages/CruciblePage.css
@@ -87,7 +87,7 @@
.crucible-node-card.selected {
border-color: var(--sel-color, var(--neon-cyan));
- background: rgba(0, 245, 255, 0.06);
+ background: rgba(0, 232, 245, 0.06);
box-shadow: 0 0 12px -4px var(--sel-color, var(--neon-cyan));
}
@@ -113,7 +113,7 @@
.cn-checkbox.checked {
border-color: var(--neon-cyan);
- background: rgba(0, 245, 255, 0.2);
+ background: rgba(0, 232, 245, 0.2);
}
.crucible-node-body {
@@ -503,8 +503,8 @@
/* ── Group colour themes ─────────────────────────────────────────────── */
-.cop-recon { border-color: rgba(0, 245, 255, 0.13); }
-.cop-recon .cop-label { color: rgba(0, 245, 255, 0.65); border-bottom-color: rgba(0, 245, 255, 0.1); }
+.cop-recon { border-color: rgba(0, 232, 245, 0.13); }
+.cop-recon .cop-label { color: rgba(0, 232, 245, 0.65); border-bottom-color: rgba(0, 232, 245, 0.1); }
.cop-agent { border-color: rgba(178, 75, 243, 0.18); }
.cop-agent .cop-label { color: rgba(178, 75, 243, 0.75); border-bottom-color: rgba(178, 75, 243, 0.13); }
@@ -628,7 +628,7 @@
gap: 0.35rem;
}
.crucible-op-active {
- background: rgba(0, 245, 255, 0.12) !important;
+ background: rgba(0, 232, 245, 0.12) !important;
border-color: var(--neon-cyan) !important;
color: var(--neon-cyan) !important;
}
@@ -721,13 +721,13 @@
/* Recon buttons — cyan tint */
.cop-recon .crucible-op-btn {
- border-color: rgba(0, 245, 255, 0.22);
- color: rgba(0, 245, 255, 0.85);
+ border-color: rgba(0, 232, 245, 0.22);
+ color: rgba(0, 232, 245, 0.85);
}
.cop-recon .crucible-op-btn:hover:not(:disabled) {
- background: rgba(0, 245, 255, 0.08);
+ background: rgba(0, 232, 245, 0.08);
border-color: var(--neon-cyan);
- box-shadow: 0 0 9px -2px rgba(0, 245, 255, 0.4);
+ box-shadow: 0 0 9px -2px rgba(0, 232, 245, 0.4);
}
/* Agent buttons — purple tint */
@@ -823,7 +823,7 @@
.crucible-shell-tab:last-child { border-right: none; }
.crucible-shell-tab.active {
- background: rgba(0, 245, 255, 0.12);
+ background: rgba(0, 232, 245, 0.12);
color: var(--neon-cyan);
}
@@ -1087,7 +1087,7 @@
font-family: 'Courier New', Consolas, monospace;
font-size: 0.8rem;
color: var(--neon-cyan);
- background: rgba(0, 245, 255, 0.07);
+ background: rgba(0, 232, 245, 0.07);
padding: 0 4px;
border-radius: 3px;
}
@@ -1113,15 +1113,15 @@
padding: 0.35rem 0.8rem;
font-family: var(--font-tech);
font-size: 0.78rem;
- background: rgba(0, 245, 255, 0.08);
- border: 1px solid rgba(0, 245, 255, 0.3);
+ background: rgba(0, 232, 245, 0.08);
+ border: 1px solid rgba(0, 232, 245, 0.3);
color: var(--neon-cyan);
border-radius: 4px;
cursor: pointer;
transition: all 0.15s;
letter-spacing: 0.05em;
}
-.crucible-btn:hover:not(:disabled) { background: rgba(0, 245, 255, 0.16); }
+.crucible-btn:hover:not(:disabled) { background: rgba(0, 232, 245, 0.16); }
.crucible-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.crucible-btn-muted {
@@ -1152,7 +1152,7 @@
.rich-block {
margin: 0.2rem 0 0.4rem 0;
padding: 0.5rem 0.75rem;
- border-left: 2px solid rgba(0, 245, 255, 0.3);
+ border-left: 2px solid rgba(0, 232, 245, 0.3);
background: rgba(0, 0, 0, 0.35);
border-radius: 0 4px 4px 0;
font-family: var(--font-tech);
@@ -1160,7 +1160,7 @@
max-width: 720px;
}
-.rich-ports { border-left-color: rgba(0, 245, 255, 0.5); }
+.rich-ports { border-left-color: rgba(0, 232, 245, 0.5); }
.rich-patch { border-left-color: rgba(255, 200, 0, 0.5); }
.rich-posture { border-left-color: rgba(150, 120, 255, 0.5); }
diff --git a/server/web/src/pages/LotlTimelinePage.tsx b/server/web/src/pages/LotlTimelinePage.tsx
index 680812b..110e139 100644
--- a/server/web/src/pages/LotlTimelinePage.tsx
+++ b/server/web/src/pages/LotlTimelinePage.tsx
@@ -12,7 +12,7 @@ import { parseAccessDepthServerPolicy } from '../help/accessDepth';
import type { AIDecisionRecord } from '../types';
import LotlFleetOverview from '../components/Lotl/LotlFleetOverview';
import LotlTierTimeline from '../components/Lotl/LotlTierTimeline';
-import { HelpTip } from '../components/HelpTip';
+import SacredPageHeader from '../components/Visual/sacredGeometry/SacredPageHeader';
import '../components/Lotl/LotlTimeline.css';
import './Pages.css';
@@ -131,24 +131,23 @@ export default function LotlTimelinePage() {
}, [selectedAgent, tierOrder]);
return (
-
-
+
+
+ Open in Crucible
+
+ ) : undefined
+ }
+ />
{/* Header */}
-
+
{error && (
@@ -361,7 +359,7 @@ export default function PathTracerPage() {
{hop && }
{hop?.external_ip && (
-
+
{hop.external_ip}:{hop.port}
)}
@@ -429,7 +427,7 @@ export default function PathTracerPage() {
{spreadRoutes.length > 0 && (
Spread Routes
-
+
{spreadRoutes.map((route) => (
-
{route.target_subnet} → {route.seed_agent_name ?? route.seed_agent_id.slice(0, 8)}
diff --git a/server/web/src/styles/operatorDeck.css b/server/web/src/styles/operatorDeck.css
index 9ed5123..f11081f 100644
--- a/server/web/src/styles/operatorDeck.css
+++ b/server/web/src/styles/operatorDeck.css
@@ -131,15 +131,15 @@
}
[data-operator-deck='pathtracer'] {
- --deck-accent: #6b8cff;
- --deck-accent-secondary: var(--neon-cyan, #00e8f5);
- --deck-accent-dim: rgba(107, 140, 255, 0.38);
- --deck-accent-glow: rgba(107, 140, 255, 0.45);
- --deck-accent-bg: rgba(70, 110, 220, 0.08);
- --deck-card-accent-bar: #6b8cff;
- --deck-accent-rail: #6b8cff;
- --deck-accent-rail-glow: rgba(107, 140, 255, 0.48);
- --deck-ambient-ui-opacity: 0.4;
+ --deck-accent: var(--neon-cyan, #00e8f5);
+ --deck-accent-secondary: var(--brass-light, #c4ad5a);
+ --deck-accent-dim: rgba(0, 232, 245, 0.38);
+ --deck-accent-glow: rgba(0, 232, 245, 0.45);
+ --deck-accent-bg: rgba(0, 232, 245, 0.07);
+ --deck-card-accent-bar: var(--neon-cyan);
+ --deck-accent-rail: var(--neon-cyan, #00e8f5);
+ --deck-accent-rail-glow: rgba(0, 232, 245, 0.48);
+ --deck-ambient-ui-opacity: 0.55;
}
/* Forge seasonal skins override deck accent when present on page root */
diff --git a/server/web/src/styles/sacred-geometry.css b/server/web/src/styles/sacred-geometry.css
index e506e0e..7b858fb 100644
--- a/server/web/src/styles/sacred-geometry.css
+++ b/server/web/src/styles/sacred-geometry.css
@@ -73,7 +73,7 @@
font-family: var(--font-display);
font-size: clamp(1.1rem, 2vw, 1.6rem);
color: rgba(201, 162, 39, 0.22);
- text-shadow: 0 0 12px rgba(0, 245, 255, 0.15);
+ text-shadow: 0 0 12px rgba(0, 232, 245, 0.15);
animation: sacred-key-glimmer 8s ease-in-out infinite;
}
@@ -101,7 +101,7 @@
animation: sacred-key-glimmer 12s ease-in-out infinite;
}
-.sacred-layer__wisdom-rail span:nth-child(2) { animation-delay: -3s; color: rgba(0, 245, 255, 0.14); }
+.sacred-layer__wisdom-rail span:nth-child(2) { animation-delay: -3s; color: rgba(0, 232, 245, 0.14); }
.sacred-layer__wisdom-rail span:nth-child(3) { animation-delay: -6s; }
.sacred-layer__wisdom-rail span:nth-child(4) { animation-delay: -9s; color: rgba(255, 176, 32, 0.14); }
@@ -168,7 +168,7 @@
position: absolute;
inset: -20%;
background:
- radial-gradient(ellipse 40% 35% at 20% 30%, rgba(0, 245, 255, 0.06), transparent 55%),
+ radial-gradient(ellipse 40% 35% at 20% 30%, rgba(0, 232, 245, 0.06), transparent 55%),
radial-gradient(ellipse 35% 40% at 80% 70%, rgba(255, 45, 166, 0.05), transparent 55%);
pointer-events: none;
}
@@ -269,7 +269,7 @@
position: absolute;
inset: 0;
opacity: 0.035;
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='83' viewBox='0 0 48 83'%3E%3Cpath d='M24 1 L47 14 L47 42 L24 55 L1 42 L1 14 Z' fill='none' stroke='%23c9a227' stroke-width='0.4'/%3E%3Cpath d='M24 28 L47 41 L47 69 L24 82 L1 69 L1 41 Z' fill='none' stroke='%2300f5ff' stroke-width='0.25' opacity='0.6'/%3E%3C/svg%3E");
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='83' viewBox='0 0 48 83'%3E%3Cpath d='M24 1 L47 14 L47 42 L24 55 L1 42 L1 14 Z' fill='none' stroke='%23c9a227' stroke-width='0.4'/%3E%3Cpath d='M24 28 L47 41 L47 69 L24 82 L1 69 L1 41 Z' fill='none' stroke='%2300e8f5' stroke-width='0.25' opacity='0.6'/%3E%3C/svg%3E");
background-size: 48px 83px;
}
@@ -324,7 +324,7 @@
pointer-events: none;
z-index: 0;
opacity: 0.04;
- background: radial-gradient(circle at 100% 0%, rgba(0, 245, 255, 0.2), transparent 45%),
+ background: radial-gradient(circle at 100% 0%, rgba(0, 232, 245, 0.2), transparent 45%),
radial-gradient(circle at 0% 100%, rgba(255, 176, 32, 0.15), transparent 40%);
}
diff --git a/server/web/src/styles/steampunk-theme.css b/server/web/src/styles/steampunk-theme.css
index 28902f2..9702357 100644
--- a/server/web/src/styles/steampunk-theme.css
+++ b/server/web/src/styles/steampunk-theme.css
@@ -15,6 +15,7 @@
--copper-glow: rgba(120, 80, 120, 0.35);
--neon-cyan: #00e8f5;
+ --neon-cyan-rgb: 0, 232, 245;
--neon-magenta: #e828a8;
--neon-amber: #e89830;
--neon-green: #2ee810;
@@ -27,7 +28,8 @@
--border-brass: rgba(140, 120, 60, 0.28);
--border-neon: rgba(0, 232, 245, 0.22);
--shadow-panel: 0 10px 36px rgba(0, 0, 0, 0.72), 0 0 1px rgba(80, 60, 140, 0.25);
- --shadow-neon-cyan: 0 0 20px rgba(0, 245, 255, 0.35), 0 0 60px rgba(0, 245, 255, 0.1);
+ --shadow-neon-cyan: 0 0 20px rgba(var(--neon-cyan-rgb), 0.35), 0 0 60px rgba(var(--neon-cyan-rgb), 0.1);
+ color-scheme: dark;
--shadow-neon-magenta: 0 0 20px rgba(255, 45, 166, 0.3);
--font-display: 'Cinzel Decorative', Georgia, serif;
@@ -158,7 +160,7 @@ h1, h2, h3, .font-display {
background: linear-gradient(180deg, #1a4a52 0%, #0d2a30 100%);
border: 1px solid var(--neon-cyan);
color: var(--neon-cyan);
- box-shadow: 0 0 15px rgba(0, 245, 255, 0.2);
+ box-shadow: 0 0 15px rgba(var(--neon-cyan-rgb), 0.2);
}
.btn-primary:hover {
@@ -185,7 +187,7 @@ h1, h2, h3, .font-display {
.btn-outline:hover {
border-color: var(--neon-cyan);
color: var(--neon-cyan);
- box-shadow: 0 0 12px rgba(0, 245, 255, 0.15);
+ box-shadow: 0 0 12px rgba(0, 232, 245, 0.15);
}
/* Form inputs — gauge panel style */
@@ -201,7 +203,7 @@ h1, h2, h3, .font-display {
.input:focus,
.select:focus {
border-color: var(--neon-cyan);
- box-shadow: 0 0 0 2px rgba(0, 245, 255, 0.15), inset 0 0 20px rgba(0, 245, 255, 0.05);
+ box-shadow: 0 0 0 2px rgba(0, 232, 245, 0.15), inset 0 0 20px rgba(0, 232, 245, 0.05);
outline: none;
}
@@ -299,3 +301,26 @@ h1, h2, h3, .font-display {
::-webkit-scrollbar-thumb:hover {
background: var(--brass);
}
+
+/* Optional light shell when OS prefers light — steampunk deck remains dark-first. */
+@media (prefers-color-scheme: light) {
+ :root {
+ color-scheme: light dark;
+ --bg-void: #f0ece4;
+ --bg-deep: #e4dfd4;
+ --bg-panel: rgba(255, 252, 245, 0.94);
+ --bg-panel-solid: #faf7f0;
+ --bg-hover: rgba(220, 210, 190, 0.92);
+ --text-primary: #1a1520;
+ --text-secondary: #4a4458;
+ --text-muted: #7a7288;
+ --border-brass: rgba(120, 100, 40, 0.35);
+ --border-neon: rgba(0, 160, 172, 0.35);
+ --shadow-panel: 0 10px 36px rgba(40, 30, 20, 0.12), 0 0 1px rgba(80, 60, 140, 0.15);
+ }
+
+ body {
+ background: var(--bg-void);
+ color: var(--text-primary);
+ }
+}
diff --git a/server/web/src/styles/visual-polish.css b/server/web/src/styles/visual-polish.css
index 0f49edb..1f44111 100644
--- a/server/web/src/styles/visual-polish.css
+++ b/server/web/src/styles/visual-polish.css
@@ -20,7 +20,7 @@ body {
}
::selection {
- background: rgba(0, 245, 255, 0.25);
+ background: rgba(0, 232, 245, 0.25);
color: var(--text-primary);
}
@@ -31,7 +31,7 @@ a {
a:hover {
color: var(--neon-cyan);
- text-shadow: 0 0 12px rgba(0, 245, 255, 0.35);
+ text-shadow: 0 0 12px rgba(0, 232, 245, 0.35);
text-decoration: none;
}
@@ -173,7 +173,7 @@ a:hover {
}
.neon-card-cyan:hover {
- border-color: rgba(0, 245, 255, 0.45);
+ border-color: rgba(0, 232, 245, 0.45);
}
.stat-card {
@@ -196,12 +196,12 @@ a:hover {
}
.stat-value {
- text-shadow: 0 0 24px rgba(0, 245, 255, 0.12);
+ text-shadow: 0 0 24px rgba(0, 232, 245, 0.12);
}
.stat-value.hashrate,
.neon-glow-cyan {
- text-shadow: 0 0 20px rgba(0, 245, 255, 0.35);
+ text-shadow: 0 0 20px rgba(0, 232, 245, 0.35);
}
.neon-glow-gold,
@@ -281,11 +281,11 @@ input[type='range'] {
/* ── Sidebar nav ───────────────────────────────────────────────────────────── */
.nav-item.active {
- text-shadow: 0 0 12px rgba(0, 245, 255, 0.4);
+ text-shadow: 0 0 12px rgba(0, 232, 245, 0.4);
}
.nav-item.active .nav-icon svg {
- filter: drop-shadow(0 0 4px rgba(0, 245, 255, 0.6));
+ filter: drop-shadow(0 0 4px rgba(0, 232, 245, 0.6));
}
/* ── Status & badges ───────────────────────────────────────────────────────── */
@@ -335,7 +335,7 @@ input[type='range'] {
.agent-list-item:hover,
.fleet-agent-row:hover {
- box-shadow: inset 0 0 24px rgba(0, 245, 255, 0.04);
+ box-shadow: inset 0 0 24px rgba(0, 232, 245, 0.04);
}
/* ── Tables & code ─────────────────────────────────────────────────────────── */
@@ -353,7 +353,7 @@ th {
}
tr:hover td {
- background: rgba(0, 245, 255, 0.03);
+ background: rgba(0, 232, 245, 0.03);
}
pre,
@@ -369,7 +369,7 @@ code {
font-family: var(--font-tech);
font-size: 0.88em;
color: var(--neon-cyan);
- background: rgba(0, 245, 255, 0.06);
+ background: rgba(0, 232, 245, 0.06);
padding: 0.1em 0.35em;
border-radius: 2px;
}
@@ -377,7 +377,7 @@ code {
/* ── Session gate ──────────────────────────────────────────────────────────── */
.session-gate {
background:
- radial-gradient(ellipse 80% 60% at 50% 20%, rgba(0, 245, 255, 0.1), transparent 55%),
+ radial-gradient(ellipse 80% 60% at 50% 20%, rgba(0, 232, 245, 0.1), transparent 55%),
radial-gradient(ellipse 60% 50% at 80% 80%, rgba(201, 162, 39, 0.08), transparent 50%),
var(--bg-void);
}
@@ -386,7 +386,7 @@ code {
border-radius: 8px;
box-shadow:
var(--shadow-panel),
- 0 0 40px rgba(0, 245, 255, 0.08),
+ 0 0 40px rgba(0, 232, 245, 0.08),
0 0 80px rgba(201, 162, 39, 0.06);
animation: af-page-in 0.7s cubic-bezier(0.23, 1, 0.32, 1) both;
}
diff --git a/server/web/src/styles/wealth-deck.css b/server/web/src/styles/wealth-deck.css
index f4dad69..3ddf0e4 100644
--- a/server/web/src/styles/wealth-deck.css
+++ b/server/web/src/styles/wealth-deck.css
@@ -144,24 +144,15 @@
text-shadow: 0 0 16px rgba(57, 255, 20, 0.4);
}
-.contrib-panel.sample-contrib .contrib-fill {
+.contrib-panel .contrib-fill {
background: linear-gradient(90deg, var(--brass-dark), var(--neon-cyan));
box-shadow: 0 0 10px rgba(0, 232, 245, 0.25);
}
-.activity-pulse.sample-activity .pulse-row.ok .pulse-dot {
+.activity-pulse .pulse-row.ok .pulse-dot {
box-shadow: 0 0 8px rgba(57, 255, 20, 0.5);
}
-.preview-deck-hint {
- font-family: var(--font-tech);
- font-size: 0.65rem;
- letter-spacing: 0.12em;
- color: rgba(201, 162, 39, 0.55);
- text-align: center;
- margin: 0.5rem 0 1rem;
-}
-
@media (max-width: 768px) {
.deck-wealth-strip {
grid-template-columns: repeat(2, 1fr);
diff --git a/server/web/src/vite-env.d.ts b/server/web/src/vite-env.d.ts
index 82ad93a..f777735 100644
--- a/server/web/src/vite-env.d.ts
+++ b/server/web/src/vite-env.d.ts
@@ -1,5 +1,7 @@
///
+declare const __APP_VERSION__: string;
+
declare module '*.png' {
const src: string;
export default src;
diff --git a/server/web/vite.config.ts b/server/web/vite.config.ts
index 7a474cd..79a713f 100644
--- a/server/web/vite.config.ts
+++ b/server/web/vite.config.ts
@@ -1,8 +1,14 @@
+import { readFileSync } from 'node:fs'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
+const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')) as { version: string }
+
export default defineConfig({
plugins: [react()],
+ define: {
+ __APP_VERSION__: JSON.stringify(pkg.version),
+ },
resolve: {
dedupe: ['react', 'react-dom'],
},
diff --git a/server/web/vitest.config.ts b/server/web/vitest.config.ts
index 0402484..ac5e69d 100644
--- a/server/web/vitest.config.ts
+++ b/server/web/vitest.config.ts
@@ -1,6 +1,12 @@
-import { defineConfig } from 'vitest/config';
+import { readFileSync } from 'node:fs'
+import { defineConfig } from 'vitest/config'
+
+const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')) as { version: string }
export default defineConfig({
+ define: {
+ __APP_VERSION__: JSON.stringify(pkg.version),
+ },
test: {
environment: 'node',
include: ['src/**/*.test.{ts,tsx}'],