XcodeAddy commited on
Commit
2d63ba6
·
2 Parent(s): cf2cf65c8e966f

Merge branch 'main' of https://github.com/ADITYAGABA1322/sentinel-env

Browse files
UI_MIGRATION.md ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # UI Migration Guide: index.html → Next.js
2
+
3
+ ## ✅ COMPLETED
4
+
5
+ ### 1. **CSS Design Tokens**
6
+ - ✅ Created design token mapping in `globals.css`
7
+ - All color variables, fonts, and spacing preserved
8
+ - Ready for selective integration
9
+
10
+ ### 2. **JSX Conversion**
11
+ - ✅ Created `/ui/app/page-landing.tsx`
12
+ - Converted HTML → React JSX components
13
+ - `class` → `className`
14
+ - Canvas refs with `useEffect` lifecycle
15
+ - All sections: hero, overview, simulation, architecture, metrics
16
+
17
+ ### 3. **Canvas Initialization**
18
+ - ✅ Added `useRef` hooks for canvas elements
19
+ - ✅ Wrapped canvas logic in `useEffect`
20
+ - ✅ TODO placeholders for actual drawing logic
21
+
22
+ ---
23
+
24
+ ## 📋 INTEGRATION STEPS
25
+
26
+ ### Step 1: Apply index.html CSS to globals.css
27
+
28
+ Your `globals.css` has old styles. Update it with index.html theme:
29
+
30
+ ```bash
31
+ # Option A: Keep existing structure, add index.html CSS as override
32
+ # Option B: Replace entire globals.css with index.html styles (RECOMMENDED)
33
+ ```
34
+
35
+ The `page-landing.tsx` expects these CSS classes:
36
+ - Layout: `nav`, `section`, `footer`
37
+ - Components: `card`, `btn-primary`, `btn-secondary`, `metric-block`
38
+ - Utilities: `anim-1`, `divider`, `hero-stats`
39
+
40
+ ### Step 2: Choose Integration Strategy
41
+
42
+ **Option A: Use Landing Page Separately** ✅ (SIMPLEST)
43
+ ```
44
+ /app/page.tsx → Your existing interactive app
45
+ /app/landing.tsx → New static landing (page-landing.tsx)
46
+ /app/layout.tsx → Wrap both
47
+ ```
48
+
49
+ **Option B: Replace page.tsx Completely** (if landing is the main page)
50
+ ```
51
+ /app/page.tsx → Replace with page-landing.tsx
52
+ /app/mission/page.tsx → Move existing components to subfolder
53
+ ```
54
+
55
+ **Option C: Hybrid** (recommended)
56
+ ```
57
+ /app/page.tsx → Dynamic switcher (landing vs mission)
58
+ /app/landing.tsx → page-landing.tsx
59
+ /app/components/* → Existing components
60
+ ```
61
+
62
+ ---
63
+
64
+ ## 🎨 CSS INTEGRATION
65
+
66
+ ### Required Classes from index.html:
67
+
68
+ ```css
69
+ /* Navigation */
70
+ nav, .nav-logo, .nav-links, .nav-badge
71
+
72
+ /* Hero Section */
73
+ #hero, .hero-content, .hero-tag, .hero-ctas
74
+ .btn-primary, .btn-secondary
75
+
76
+ /* Cards */
77
+ .cards-grid, .card, .card-id, .card-title, .card-footer
78
+
79
+ /* Simulation */
80
+ .sim-wrapper, .sim-topbar, .sim-body, .sim-panel
81
+ .agent-row, .trust-bar-bg, .trust-bar-fill
82
+ .metric-block, .metric-value
83
+
84
+ /* Architecture */
85
+ .arch-node, .arch-flow
86
+
87
+ /* Metrics */
88
+ .metric-block, .metric-grid
89
+
90
+ /* Footer */
91
+ footer, .footer-left, .footer-right
92
+
93
+ /* Animations */
94
+ @keyframes pulse-dot, fadeInUp
95
+ .anim-1, .anim-2, .anim-3, .anim-4, .anim-5
96
+ ```
97
+
98
+ ---
99
+
100
+ ## 🖼️ CANVAS MIGRATION
101
+
102
+ The new `page-landing.tsx` has placeholders:
103
+
104
+ ```typescript
105
+ function initHeroCanvas(canvas: HTMLCanvasElement) {
106
+ // TODO: Copy canvas.js logic from index.html
107
+ // The neural network grid visualization
108
+ }
109
+
110
+ function initSimCanvas(canvas: HTMLCanvasElement) {
111
+ // TODO: Copy canvas.js logic from index.html
112
+ // The orchestrator network visualization
113
+ }
114
+ ```
115
+
116
+ **To migrate canvas logic:**
117
+
118
+ 1. Find the inline `<script>` block in `index.html` (lines ~1710+)
119
+ 2. Extract the two IIFE functions:
120
+ - Hero canvas: Neural network grid
121
+ - Sim canvas: Orchestrator network
122
+ 3. Paste into `initHeroCanvas()` and `initSimCanvas()`
123
+ 4. Update canvas size handling for React
124
+
125
+ ---
126
+
127
+ ## ✨ FEATURES NOT YET IMPLEMENTED
128
+
129
+ These are hooks for future work - marked with `TODO`:
130
+
131
+ - [ ] Canvas drawing logic (detailed in "Canvas Migration" above)
132
+ - [ ] Button click handlers (Launch Mission, Read Docs)
133
+ - [ ] Live metrics updates (currently static)
134
+ - [ ] Specialist status animation
135
+ - [ ] Episode log scrolling (right panel)
136
+
137
+ ---
138
+
139
+ ## 📁 FILE STRUCTURE AFTER MIGRATION
140
+
141
+ ```
142
+ /ui
143
+ ├── app/
144
+ │ ├── layout.tsx (unchanged)
145
+ │ ├── page.tsx (existing - interactive mode)
146
+ │ ├── page-landing.tsx (NEW - landing page)
147
+ │ ├── globals.css (UPDATED with index.html styles)
148
+ │ ├── components/
149
+ │ │ ├── Landing.tsx
150
+ │ │ ├── MissionControl.tsx
151
+ │ │ └── ...
152
+ │ └── hooks/
153
+ │ └── useSentinel.ts
154
+ ├── index.html (ARCHIVE - no longer needed after migration)
155
+ ├── .env (unchanged)
156
+ └── package.json (unchanged)
157
+ ```
158
+
159
+ ---
160
+
161
+ ## 🚀 QUICK START
162
+
163
+ 1. **Update globals.css** with index.html styles (or use the CSS I provided)
164
+ 2. **Keep page-landing.tsx** as new file (or rename to page.tsx if replacing)
165
+ 3. **Migrate canvas logic** from index.html `<script>` section
166
+ 4. **Test**: Visit `http://localhost:3000/` or `/landing` depending on routing
167
+
168
+ ---
169
+
170
+ ## ✅ DONE vs 🚧 TODO
171
+
172
+ | Part | Status | Notes |
173
+ |------|--------|-------|
174
+ | HTML → JSX | ✅ | page-landing.tsx ready |
175
+ | CSS Variables | ✅ | Design tokens extracted |
176
+ | Layout/Structure | ✅ | All sections preserved |
177
+ | Canvas Setup | ✅ | useRef + useEffect ready |
178
+ | Canvas Drawing | 🚧 | TODO - copy from index.html |
179
+ | Button Handlers | 🚧 | TODO - implement event handlers |
180
+ | Live Updates | 🚧 | TODO - connect to API/state |
181
+ | Animations | ✅ | @keyframes preserved in CSS |
182
+
183
+ ---
184
+
185
+ ## NEXT: What You Need to Do
186
+
187
+ 1. **Decision**: Keep separate landing page or replace main page?
188
+ 2. **CSS**: Merge index.html styles into `globals.css`
189
+ 3. **Canvas**: Copy drawing logic from `index.html` `<script>` into the TODO functions
190
+ 4. **Test**: Run `npm run dev` and verify visual parity
191
+
192
+ Need help with any step? 🚀
app.py CHANGED
@@ -12,6 +12,7 @@ from threading import RLock
12
  from typing import Any
13
 
14
  from fastapi import FastAPI, HTTPException, Query
 
15
  from fastapi.staticfiles import StaticFiles
16
  from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, StreamingResponse
17
  from pydantic import BaseModel
@@ -36,6 +37,15 @@ app = FastAPI(
36
  version="1.0.0",
37
  )
38
 
 
 
 
 
 
 
 
 
 
39
  @dataclass
40
  class SessionEntry:
41
  env: SentinelEnv | ClusterTrustEnv
@@ -212,6 +222,8 @@ class StepRequest(BaseModel):
212
  # Endpoints
213
  # ---------------------------------------------------------------------------
214
 
 
 
215
  @app.get("/health")
216
  def health():
217
  return {
 
12
  from typing import Any
13
 
14
  from fastapi import FastAPI, HTTPException, Query
15
+ from fastapi.middleware.cors import CORSMiddleware
16
  from fastapi.staticfiles import StaticFiles
17
  from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, StreamingResponse
18
  from pydantic import BaseModel
 
37
  version="1.0.0",
38
  )
39
 
40
+ # Add CORS middleware to allow browser requests from frontend
41
+ app.add_middleware(
42
+ CORSMiddleware,
43
+ allow_origins=["*"], # Allow all origins (use specific domains in production)
44
+ allow_credentials=True,
45
+ allow_methods=["*"], # Allow all HTTP methods (GET, POST, OPTIONS, etc.)
46
+ allow_headers=["*"], # Allow all headers
47
+ )
48
+
49
  @dataclass
50
  class SessionEntry:
51
  env: SentinelEnv | ClusterTrustEnv
 
222
  # Endpoints
223
  # ---------------------------------------------------------------------------
224
 
225
+
226
+
227
  @app.get("/health")
228
  def health():
229
  return {
ui/app/components/AgentTrustMonitor.tsx ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import type { Observation, EventItem } from "../lib/types";
4
+
5
+ const AGENT_ROLES: Record<string, string> = {
6
+ S0: "COORDINATOR", S1: "OBSERVER", S2: "EXECUTOR", S3: "FLAGGED", S4: "VALIDATOR",
7
+ };
8
+
9
+ type Props = {
10
+ observation: Observation | null;
11
+ trustDeltas: Record<string, number>;
12
+ activeSpec: string | null;
13
+ events: EventItem[];
14
+ running: boolean;
15
+ totalReward: number;
16
+ };
17
+
18
+ function trustClass(t: number) {
19
+ if (t >= 0.65) return "high";
20
+ if (t >= 0.35) return "mid";
21
+ return "low";
22
+ }
23
+
24
+ function trustColor(t: number) {
25
+ if (t >= 0.65) return "var(--green)";
26
+ if (t >= 0.35) return "var(--amber)";
27
+ return "var(--red)";
28
+ }
29
+
30
+ export default function AgentTrustMonitor({
31
+ observation, trustDeltas, activeSpec, events, running, totalReward,
32
+ }: Props) {
33
+ const agents = observation?.available_specialists ?? ["S0", "S1", "S2", "S3", "S4"];
34
+ const trust = observation?.trust_snapshot ?? {};
35
+ const lastReward = observation?.last_reward ?? 0;
36
+
37
+ // detect adversarial from events
38
+ const poisonedAgents = new Set(
39
+ events.filter(e => e.outcome === "poisoned" || e.outcome === "blocked")
40
+ .map(e => e.specialist).filter(Boolean)
41
+ );
42
+
43
+ // mean trust
44
+ const vals = agents.map(id => trust[id] ?? 0.5);
45
+ const meanTrust = vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : 0.5;
46
+ const advRatio = agents.length ? Math.round((poisonedAgents.size / agents.length) * 100) : 0;
47
+
48
+ return (
49
+ <div className="sim-panel">
50
+ <div className="sim-panel-label">AGENT TRUST REGISTRY</div>
51
+
52
+ {agents.map(id => {
53
+ const t = trust[id] ?? 0.5;
54
+ const delta = trustDeltas[id] ?? 0;
55
+ const isActive = activeSpec === id;
56
+ const isAdv = poisonedAgents.has(id);
57
+ const role = AGENT_ROLES[id] ?? "AGENT";
58
+
59
+ return (
60
+ <div className="agent-row" key={id}>
61
+ <div className="agent-header">
62
+ <span className={`agent-id ${isActive ? "active" : isAdv ? "adversarial" : "neutral"}`}>
63
+ {id} // {isAdv ? "⚠ FLAGGED" : role}
64
+ </span>
65
+ <span className="agent-trust-val" style={{ color: trustColor(t) }}>
66
+ {t.toFixed(2)}
67
+ </span>
68
+ </div>
69
+ <div className="trust-bar-bg">
70
+ <div
71
+ className={`trust-bar-fill ${trustClass(t)}`}
72
+ style={{ width: `${Math.max(2, t * 100)}%` }}
73
+ />
74
+ </div>
75
+ <div className="agent-state" style={isAdv ? { color: "var(--red)" } : {}}>
76
+ {isAdv
77
+ ? "STATE: ADVERSARIAL // ISOLATED"
78
+ : isActive
79
+ ? "STATE: ACTIVE // DELEGATING"
80
+ : `STATE: READY // Δ ${delta >= 0 ? "+" : ""}${delta.toFixed(3)}`
81
+ }
82
+ </div>
83
+ </div>
84
+ );
85
+ })}
86
+
87
+ <div style={{ marginTop: 20, paddingTop: 16, borderTop: "1px solid rgba(0,200,255,0.08)" }}>
88
+ <div className="sim-metric-row">
89
+ <span className="sim-metric-label">MEAN TRUST</span>
90
+ <span className="sim-metric-val c">{meanTrust.toFixed(3)}</span>
91
+ </div>
92
+ <div className="sim-metric-row">
93
+ <span className="sim-metric-label">ADV RATIO</span>
94
+ <span className="sim-metric-val r">{advRatio}%</span>
95
+ </div>
96
+ <div className="sim-metric-row">
97
+ <span className="sim-metric-label">STEP REWARD</span>
98
+ <span className={`sim-metric-val ${lastReward >= 0 ? "g" : "r"}`}>
99
+ {lastReward >= 0 ? "+" : ""}{lastReward.toFixed(3)}
100
+ </span>
101
+ </div>
102
+ <div className="sim-metric-row">
103
+ <span className="sim-metric-label">TOTAL REWARD</span>
104
+ <span className={`sim-metric-val ${totalReward >= 0 ? "g" : "r"}`}>
105
+ {totalReward >= 0 ? "+" : ""}{totalReward.toFixed(2)}
106
+ </span>
107
+ </div>
108
+ </div>
109
+ </div>
110
+ );
111
+ }
ui/app/components/ArchitecturePipeline.tsx ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ export default function ArchitecturePipeline() {
4
+ const nodes = [
5
+ { id: "LAYER-01", name: "AGENTS", desc: "S0–S4 emit\nobservations + actions\nper timestep", color: "rgba(0, 200, 255, 0.4)", labelColor: "#00f5ff" },
6
+ { id: "LAYER-02", name: "ADV DETECTOR", desc: "KL-divergence\nanomaly scan\nByzantine flag", color: "rgba(255, 45, 85, 0.3)", labelColor: "#ff2d55" },
7
+ { id: "LAYER-03", name: "ORCHESTRATOR", desc: "Trust-weighted\naggregation &\ndecision output", color: "rgba(0, 255, 136, 0.3)", labelColor: "#00ff88" },
8
+ { id: "LAYER-04", name: "REWARD SIG.", desc: "Shaped scalar\nwith adversarial\npenalty term", color: "rgba(255, 184, 0, 0.3)", labelColor: "#ffb800" },
9
+ { id: "LAYER-05", name: "POLICY UPDATE", desc: "PPO gradient\nstep + trust\nposterior update", color: "rgba(0, 85, 255, 0.4)", labelColor: "#0088ff" },
10
+ ];
11
+
12
+ const arrows = ["ACTIONS", "FLAGS", "DECISION", "REWARD"];
13
+
14
+ return (
15
+ <>
16
+ <div className="arch-flow">
17
+ {nodes.map((node, i) => (
18
+ <span key={node.id} style={{ display: "contents" }}>
19
+ <div className="arch-node" style={{ "--node-color": node.color } as React.CSSProperties}>
20
+ <div className="arch-node-id" style={{ color: node.labelColor }}>{node.id}</div>
21
+ <div className="arch-node-name">{node.name}</div>
22
+ <div className="arch-node-desc">{node.desc.split("\n").map((l, j) => <span key={j}>{l}<br /></span>)}</div>
23
+ </div>
24
+ {i < nodes.length - 1 && (
25
+ <div className="arch-arrow">
26
+ <div className="arch-arrow-line" />
27
+ <div className="arch-arrow-label">{arrows[i]}</div>
28
+ </div>
29
+ )}
30
+ </span>
31
+ ))}
32
+ </div>
33
+
34
+ <div className="arch-code-loop">
35
+ <span style={{ color: "var(--muted)" }}>LOOP:</span>&nbsp;&nbsp;
36
+ <span style={{ color: "var(--cyan)" }}>observe()</span> →{" "}
37
+ <span style={{ color: "var(--red)" }}>detect_adversary()</span> →{" "}
38
+ <span style={{ color: "var(--green)" }}>aggregate_trust()</span> →{" "}
39
+ <span style={{ color: "var(--white)" }}>act()</span> →{" "}
40
+ <span style={{ color: "var(--amber)" }}>compute_reward()</span> →{" "}
41
+ <span style={{ color: "#0088ff" }}>update_policy()</span> →{" "}
42
+ <span style={{ color: "var(--cyan)" }}>repeat</span>
43
+ &nbsp;&nbsp;
44
+ <span style={{ color: "var(--muted)" }}>// T: O(N·K) // SPACE: O(N²)</span>
45
+ </div>
46
+ </>
47
+ );
48
+ }
ui/app/components/ExecutionLog.tsx ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useRef, useEffect } from "react";
4
+ import type { EventItem, Observation } from "../lib/types";
5
+
6
+ type Props = {
7
+ events: EventItem[];
8
+ observation: Observation | null;
9
+ info: { total_reward: number; score: number; adversarial_detections?: number; adversarial_poisonings?: number } | undefined;
10
+ };
11
+
12
+ function logClass(e: EventItem): string {
13
+ if (e.outcome === "poisoned") return "alert";
14
+ if (e.outcome === "blocked") return "warn";
15
+ if (e.outcome === "success") return "ok";
16
+ return "";
17
+ }
18
+
19
+ function formatTime(step: number): string {
20
+ const m = 14, s = 3 + step;
21
+ const ss = s % 60;
22
+ const mm = m + Math.floor(s / 60);
23
+ return `${mm}:${String(ss).padStart(2, "0")}:${String(Math.floor(Math.random() * 60)).padStart(2, "0")}`;
24
+ }
25
+
26
+ export default function ExecutionLog({ events, observation, info }: Props) {
27
+ const scrollRef = useRef<HTMLDivElement>(null);
28
+
29
+ useEffect(() => {
30
+ if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
31
+ }, [events.length]);
32
+
33
+ const detectRate = info?.adversarial_detections !== undefined && info?.adversarial_poisonings !== undefined
34
+ ? (info.adversarial_detections + info.adversarial_poisonings) > 0
35
+ ? Math.round((info.adversarial_detections / (info.adversarial_detections + info.adversarial_poisonings)) * 100)
36
+ : 0
37
+ : null;
38
+
39
+ return (
40
+ <div className="sim-panel" style={{ borderRight: "none", borderLeft: "1px solid rgba(0,200,255,0.08)" }}>
41
+ <div className="sim-panel-label">EVENT LOG</div>
42
+
43
+ <div ref={scrollRef} style={{ maxHeight: 220, overflowY: "auto" }}>
44
+ {events.slice(-12).map((e, i) => (
45
+ <div key={i} className={`log-entry ${logClass(e)}`}>
46
+ <span className="log-time">{formatTime(e.step)}</span>
47
+ {e.action === "reset"
48
+ ? "EPISODE RESET ⟳"
49
+ : `${e.specialist ?? "SYS"} ${e.summary.substring(0, 40)}`
50
+ }
51
+ </div>
52
+ ))}
53
+ {events.length === 0 && (
54
+ <div className="log-entry" style={{ opacity: 0.3 }}>
55
+ Waiting for simulation data...
56
+ </div>
57
+ )}
58
+ </div>
59
+
60
+ <div style={{ marginTop: 16, paddingTop: 16, borderTop: "1px solid rgba(0,200,255,0.08)" }}>
61
+ <div className="sim-panel-label">EPISODE METRICS</div>
62
+ <div className="sim-metric-row">
63
+ <span className="sim-metric-label">CUMULATIVE REWARD</span>
64
+ <span className={`sim-metric-val ${(info?.total_reward ?? 0) >= 0 ? "g" : "r"}`}>
65
+ {(info?.total_reward ?? 0) >= 0 ? "+" : ""}{(info?.total_reward ?? 0).toFixed(2)}
66
+ </span>
67
+ </div>
68
+ <div className="sim-metric-row">
69
+ <span className="sim-metric-label">SCORE</span>
70
+ <span className="sim-metric-val c">{(info?.score ?? 0).toFixed(3)}</span>
71
+ </div>
72
+ {detectRate !== null && (
73
+ <div className="sim-metric-row">
74
+ <span className="sim-metric-label">DETECT RATE</span>
75
+ <span className="sim-metric-val c">{detectRate}%</span>
76
+ </div>
77
+ )}
78
+ <div className="sim-metric-row">
79
+ <span className="sim-metric-label">STEP</span>
80
+ <span className="sim-metric-val a">
81
+ {observation?.step_count ?? 0}/{observation?.max_steps ?? 0}
82
+ </span>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ );
87
+ }
ui/app/components/HeroCanvas.tsx ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useRef, useEffect } from "react";
4
+
5
+ type Node = {
6
+ x: number; y: number;
7
+ vx: number; vy: number;
8
+ r: number; pulse: number;
9
+ type: "blue" | "green" | "red";
10
+ };
11
+
12
+ export default function HeroCanvas() {
13
+ const ref = useRef<HTMLCanvasElement>(null);
14
+
15
+ useEffect(() => {
16
+ const canvas = ref.current;
17
+ if (!canvas) return;
18
+ const ctx = canvas.getContext("2d");
19
+ if (!ctx) return;
20
+
21
+ let W = 0, H = 0;
22
+ let nodes: Node[] = [];
23
+ let animId = 0;
24
+
25
+ function resize() {
26
+ W = canvas!.width = canvas!.offsetWidth;
27
+ H = canvas!.height = canvas!.offsetHeight;
28
+ buildNodes();
29
+ }
30
+
31
+ function buildNodes() {
32
+ nodes = [];
33
+ const count = Math.floor((W * H) / 18000);
34
+ for (let i = 0; i < count; i++) {
35
+ nodes.push({
36
+ x: Math.random() * W, y: Math.random() * H,
37
+ vx: (Math.random() - 0.5) * 0.3, vy: (Math.random() - 0.5) * 0.3,
38
+ r: Math.random() * 2 + 1,
39
+ pulse: Math.random() * Math.PI * 2,
40
+ type: Math.random() < 0.1 ? "red" : Math.random() < 0.15 ? "green" : "blue",
41
+ });
42
+ }
43
+ }
44
+
45
+ function draw() {
46
+ ctx!.clearRect(0, 0, W, H);
47
+
48
+ // Grid
49
+ ctx!.strokeStyle = "rgba(0,100,200,0.06)";
50
+ ctx!.lineWidth = 0.5;
51
+ for (let x = 0; x < W; x += 60) {
52
+ ctx!.beginPath(); ctx!.moveTo(x, 0); ctx!.lineTo(x, H); ctx!.stroke();
53
+ }
54
+ for (let y = 0; y < H; y += 60) {
55
+ ctx!.beginPath(); ctx!.moveTo(0, y); ctx!.lineTo(W, y); ctx!.stroke();
56
+ }
57
+
58
+ // Update
59
+ nodes.forEach(n => {
60
+ n.x += n.vx; n.y += n.vy;
61
+ if (n.x < 0 || n.x > W) n.vx *= -1;
62
+ if (n.y < 0 || n.y > H) n.vy *= -1;
63
+ n.pulse += 0.02;
64
+ });
65
+
66
+ // Edges
67
+ for (let i = 0; i < nodes.length; i++) {
68
+ for (let j = i + 1; j < nodes.length; j++) {
69
+ const a = nodes[i], b = nodes[j];
70
+ const dx = a.x - b.x, dy = a.y - b.y;
71
+ const dist = Math.sqrt(dx * dx + dy * dy);
72
+ if (dist < 140) {
73
+ const alpha = (1 - dist / 140) * 0.25;
74
+ let color: string;
75
+ if (a.type === "red" || b.type === "red") color = `rgba(255,45,85,${alpha * 0.8})`;
76
+ else if (a.type === "green" || b.type === "green") color = `rgba(0,255,136,${alpha * 0.6})`;
77
+ else color = `rgba(0,200,255,${alpha})`;
78
+ ctx!.strokeStyle = color;
79
+ ctx!.lineWidth = 0.5;
80
+ ctx!.beginPath(); ctx!.moveTo(a.x, a.y); ctx!.lineTo(b.x, b.y); ctx!.stroke();
81
+ }
82
+ }
83
+ }
84
+
85
+ // Nodes
86
+ nodes.forEach(n => {
87
+ const pulse = 0.5 + 0.5 * Math.sin(n.pulse);
88
+ let color: string, gcolor: string;
89
+ if (n.type === "red") { color = `rgba(255,45,85,${0.4 + 0.5 * pulse})`; gcolor = "#FF2D55"; }
90
+ else if (n.type === "green") { color = `rgba(0,255,136,${0.4 + 0.5 * pulse})`; gcolor = "#00FF88"; }
91
+ else { color = `rgba(0,200,255,${0.35 + 0.4 * pulse})`; gcolor = "#00F5FF"; }
92
+
93
+ const g = ctx!.createRadialGradient(n.x, n.y, 0, n.x, n.y, n.r * 3);
94
+ g.addColorStop(0, gcolor); g.addColorStop(1, "transparent");
95
+ ctx!.fillStyle = g;
96
+ ctx!.beginPath(); ctx!.arc(n.x, n.y, n.r * 3, 0, Math.PI * 2); ctx!.fill();
97
+ ctx!.fillStyle = color;
98
+ ctx!.beginPath(); ctx!.arc(n.x, n.y, n.r, 0, Math.PI * 2); ctx!.fill();
99
+ });
100
+
101
+ animId = requestAnimationFrame(draw);
102
+ }
103
+
104
+ const onResize = () => { cancelAnimationFrame(animId); resize(); draw(); };
105
+ window.addEventListener("resize", onResize);
106
+ resize();
107
+ draw();
108
+
109
+ return () => { cancelAnimationFrame(animId); window.removeEventListener("resize", onResize); };
110
+ }, []);
111
+
112
+ return (
113
+ <div className="hero-canvas-wrap">
114
+ <canvas ref={ref} />
115
+ </div>
116
+ );
117
+ }
ui/app/components/MetricsGrid.tsx ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import type { EvalSummary } from "../lib/types";
4
+
5
+ type Props = {
6
+ proof: {
7
+ random: EvalSummary;
8
+ heuristic: EvalSummary;
9
+ oracle: EvalSummary;
10
+ trained?: EvalSummary;
11
+ } | null;
12
+ };
13
+
14
+ export default function MetricsGrid({ proof }: Props) {
15
+ const trained = proof?.trained;
16
+ const heuristic = proof?.heuristic;
17
+ const random = proof?.random;
18
+
19
+ const trustAcc = trained ? Math.round(trained.avg_trust_calibration * 100) : 92;
20
+ const detectRate = trained ? Math.round(trained.avg_detection_rate * 100) : 87;
21
+ const improvement = trained && heuristic
22
+ ? Math.round((trained.avg_score - heuristic.avg_score) * 100)
23
+ : 34;
24
+ const avgScore = trained ? trained.avg_score.toFixed(2) : "0.91";
25
+
26
+ const baselineTrust = random ? Math.round(random.avg_trust_calibration * 100) : 61;
27
+ const baselineDetect = random ? Math.round(random.avg_detection_rate * 100) : 43;
28
+
29
+ return (
30
+ <div className="metrics-grid">
31
+ <div className="metric-block" style={{ "--m-color": "#00f5ff" } as React.CSSProperties}>
32
+ <div className="metric-ref">TABLE 1 // ROW A // TRUST ACCURACY</div>
33
+ <div className="metric-value">{trustAcc}<span className="metric-unit">%</span></div>
34
+ <div className="metric-label">Trust Accuracy</div>
35
+ <div className="metric-sub">
36
+ Correct trust assignment rate against ground-truth agent labels across all evaluation episodes.
37
+ </div>
38
+ <div className="metric-bar-wrap">
39
+ <div className="metric-bar-bg">
40
+ <div className="metric-bar-fill" style={{ width: `${trustAcc}%` }} />
41
+ </div>
42
+ <div className="metric-bar-label">
43
+ <span>BASELINE: {baselineTrust}%</span>
44
+ <span>SENTINEL: {trustAcc}%</span>
45
+ </div>
46
+ </div>
47
+ </div>
48
+
49
+ <div className="metric-block" style={{ "--m-color": "#ff2d55" } as React.CSSProperties}>
50
+ <div className="metric-ref">TABLE 1 // ROW B // ADV DETECTION</div>
51
+ <div className="metric-value">{detectRate}<span className="metric-unit">%</span></div>
52
+ <div className="metric-label">Adversarial Detection Rate</div>
53
+ <div className="metric-sub">
54
+ Precision-recall F1 on Byzantine agent identification. False positive rate held below 5% threshold.
55
+ </div>
56
+ <div className="metric-bar-wrap">
57
+ <div className="metric-bar-bg">
58
+ <div className="metric-bar-fill" style={{ width: `${detectRate}%` }} />
59
+ </div>
60
+ <div className="metric-bar-label">
61
+ <span>BASELINE: {baselineDetect}%</span>
62
+ <span>SENTINEL: {detectRate}%</span>
63
+ </div>
64
+ </div>
65
+ </div>
66
+
67
+ <div className="metric-block" style={{ "--m-color": "#00ff88" } as React.CSSProperties}>
68
+ <div className="metric-ref">TABLE 2 // ROW C // POLICY GAIN</div>
69
+ <div className="metric-value">+{improvement}<span className="metric-unit">%</span></div>
70
+ <div className="metric-label">Policy Improvement</div>
71
+ <div className="metric-sub">
72
+ Cumulative episode return gain over heuristic baseline after convergence.
73
+ </div>
74
+ <div className="metric-bar-wrap">
75
+ <div className="metric-bar-bg">
76
+ <div className="metric-bar-fill" style={{ width: `${Math.min(100, 50 + improvement)}%` }} />
77
+ </div>
78
+ <div className="metric-bar-label">
79
+ <span>HEURISTIC</span>
80
+ <span>TRAINED RL</span>
81
+ </div>
82
+ </div>
83
+ </div>
84
+
85
+ <div className="metric-block" style={{ "--m-color": "#ffb800" } as React.CSSProperties}>
86
+ <div className="metric-ref">TABLE 2 // ROW D // FINAL SCORE</div>
87
+ <div className="metric-value">{avgScore}</div>
88
+ <div className="metric-label">Average Score</div>
89
+ <div className="metric-sub">
90
+ Mean normalized score across all tasks. Higher is better (range 0–1, boundary exclusive).
91
+ </div>
92
+ <div className="metric-bar-wrap">
93
+ <div className="metric-bar-bg">
94
+ <div className="metric-bar-fill" style={{ width: `${Number(avgScore) * 100}%` }} />
95
+ </div>
96
+ <div className="metric-bar-label">
97
+ <span>RANDOM: {random ? random.avg_score.toFixed(2) : "0.28"}</span>
98
+ <span>SENTINEL: {avgScore}</span>
99
+ </div>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ );
104
+ }
ui/app/components/SimCanvas.tsx ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useRef, useEffect } from "react";
4
+
5
+ type AgentData = { id: string; trust: number; type: string };
6
+
7
+ type Props = {
8
+ trustSnapshot: Record<string, number>;
9
+ adversarialAgents: Set<string>;
10
+ activeSpec: string | null;
11
+ };
12
+
13
+ const AGENT_ANGLES = [0, 72, 144, 216, 288];
14
+ const COLORS: Record<string, string> = {
15
+ orch: "#00F5FF", normal: "#00FF88", degraded: "#FFB800", adversarial: "#FF2D55",
16
+ };
17
+
18
+ function hexToRgb(hex: string) {
19
+ if (hex === "#00F5FF") return "0,245,255";
20
+ if (hex === "#00FF88") return "0,255,136";
21
+ if (hex === "#FFB800") return "255,184,0";
22
+ if (hex === "#FF2D55") return "255,45,85";
23
+ return "0,200,255";
24
+ }
25
+
26
+ function agentType(id: string, trust: number, isAdv: boolean): string {
27
+ if (isAdv) return "adversarial";
28
+ if (trust < 0.4) return "degraded";
29
+ return "normal";
30
+ }
31
+
32
+ export default function SimCanvas({ trustSnapshot, adversarialAgents, activeSpec }: Props) {
33
+ const ref = useRef<HTMLCanvasElement>(null);
34
+ const dataRef = useRef({ trustSnapshot, adversarialAgents, activeSpec });
35
+
36
+ useEffect(() => {
37
+ dataRef.current = { trustSnapshot, adversarialAgents, activeSpec };
38
+ }, [trustSnapshot, adversarialAgents, activeSpec]);
39
+
40
+ useEffect(() => {
41
+ const canvas = ref.current;
42
+ if (!canvas) return;
43
+ const ctx = canvas.getContext("2d");
44
+ if (!ctx) return;
45
+
46
+ let W = 0, H = 0, tick = 0, animId = 0;
47
+ type Packet = { agentIdx: number; progress: number; dir: number };
48
+ let dataPackets: Packet[] = [];
49
+
50
+ function resize() {
51
+ W = canvas!.width = canvas!.offsetWidth;
52
+ H = canvas!.height = canvas!.offsetHeight;
53
+ }
54
+
55
+ function getPos(angle: number, cx: number, cy: number, r: number) {
56
+ const rad = ((angle - 90) * Math.PI) / 180;
57
+ return { x: cx + Math.cos(rad) * r, y: cy + Math.sin(rad) * r };
58
+ }
59
+
60
+ function draw() {
61
+ const { trustSnapshot: ts, adversarialAgents: advSet, activeSpec: active } = dataRef.current;
62
+ ctx!.clearRect(0, 0, W, H);
63
+ tick++;
64
+
65
+ const cx = W / 2, cy = H / 2;
66
+ const outerR = Math.min(W, H) * 0.35;
67
+ const orchR = 30, agentR = 18;
68
+
69
+ const agents: AgentData[] = ["S0", "S1", "S2", "S3", "S4"].map((id, i) => ({
70
+ id, trust: ts[id] ?? 0.5,
71
+ type: i === 0 ? "orch" : agentType(id, ts[id] ?? 0.5, advSet.has(id)),
72
+ }));
73
+
74
+ // Grid
75
+ ctx!.strokeStyle = "rgba(0,100,200,0.04)";
76
+ ctx!.lineWidth = 0.5;
77
+ for (let x = 0; x < W; x += 40) { ctx!.beginPath(); ctx!.moveTo(x, 0); ctx!.lineTo(x, H); ctx!.stroke(); }
78
+ for (let y = 0; y < H; y += 40) { ctx!.beginPath(); ctx!.moveTo(0, y); ctx!.lineTo(W, y); ctx!.stroke(); }
79
+
80
+ // Orbit ring
81
+ ctx!.beginPath(); ctx!.arc(cx, cy, outerR, 0, Math.PI * 2);
82
+ ctx!.strokeStyle = "rgba(0,200,255,0.06)"; ctx!.lineWidth = 1;
83
+ ctx!.setLineDash([4, 8]); ctx!.stroke(); ctx!.setLineDash([]);
84
+
85
+ // Scanning ring
86
+ const scanAngle = (tick * 0.02) % (Math.PI * 2);
87
+ ctx!.save(); ctx!.translate(cx, cy); ctx!.rotate(scanAngle);
88
+ const scanArc = ctx!.createLinearGradient(-outerR, 0, outerR, 0);
89
+ scanArc.addColorStop(0, "rgba(0,200,255,0)");
90
+ scanArc.addColorStop(1, "rgba(0,200,255,0.08)");
91
+ ctx!.beginPath(); ctx!.moveTo(0, 0); ctx!.arc(0, 0, outerR, -0.4, 0); ctx!.closePath();
92
+ ctx!.fillStyle = scanArc; ctx!.fill(); ctx!.restore();
93
+
94
+ // Connections from orch to agents
95
+ const orchPos = { x: cx, y: cy };
96
+ agents.slice(1).forEach((a, i) => {
97
+ const pos = getPos(AGENT_ANGLES[i + 1], cx, cy, outerR);
98
+ const col = COLORS[a.type] || COLORS.normal;
99
+ const alpha = a.type === "adversarial" ? 0.12 : a.trust * 0.35;
100
+ const isDash = a.type === "adversarial";
101
+ if (isDash) ctx!.setLineDash([4, 6]); else ctx!.setLineDash([]);
102
+ ctx!.beginPath(); ctx!.moveTo(orchPos.x, orchPos.y); ctx!.lineTo(pos.x, pos.y);
103
+ ctx!.strokeStyle = `rgba(${hexToRgb(col)},${alpha})`;
104
+ ctx!.lineWidth = a.trust * 2; ctx!.stroke(); ctx!.setLineDash([]);
105
+ });
106
+
107
+ // Data packets
108
+ if (tick % 20 === 0) {
109
+ const ai = Math.floor(Math.random() * 4) + 1;
110
+ dataPackets.push({ agentIdx: ai, progress: 0, dir: Math.random() > 0.5 ? 1 : -1 });
111
+ }
112
+ dataPackets = dataPackets.filter(p => p.progress <= 1);
113
+ dataPackets.forEach(p => {
114
+ p.progress += 0.025;
115
+ const a = agents[p.agentIdx];
116
+ const pos = getPos(AGENT_ANGLES[p.agentIdx], cx, cy, outerR);
117
+ const t = p.progress;
118
+ const px = orchPos.x + (pos.x - orchPos.x) * (p.dir > 0 ? t : 1 - t);
119
+ const py = orchPos.y + (pos.y - orchPos.y) * (p.dir > 0 ? t : 1 - t);
120
+ const col = COLORS[a.type] || COLORS.normal;
121
+ ctx!.beginPath(); ctx!.arc(px, py, 3, 0, Math.PI * 2); ctx!.fillStyle = col; ctx!.fill();
122
+ const g = ctx!.createRadialGradient(px, py, 0, px, py, 8);
123
+ g.addColorStop(0, col); g.addColorStop(1, "transparent");
124
+ ctx!.beginPath(); ctx!.arc(px, py, 8, 0, Math.PI * 2); ctx!.fillStyle = g; ctx!.fill();
125
+ });
126
+
127
+ // Outer agents
128
+ agents.slice(1).forEach((a, i) => {
129
+ const pos = getPos(AGENT_ANGLES[i + 1], cx, cy, outerR);
130
+ const col = COLORS[a.type] || COLORS.normal;
131
+ const pulse = 0.7 + 0.3 * Math.sin(tick * 0.05 + AGENT_ANGLES[i + 1]);
132
+ const isActive = active === a.id;
133
+
134
+ // Glow
135
+ const g = ctx!.createRadialGradient(pos.x, pos.y, 0, pos.x, pos.y, agentR * 2.5);
136
+ g.addColorStop(0, `rgba(${hexToRgb(col)},${(isActive ? 0.35 : 0.2) * pulse})`);
137
+ g.addColorStop(1, "transparent");
138
+ ctx!.beginPath(); ctx!.arc(pos.x, pos.y, agentR * 2.5, 0, Math.PI * 2);
139
+ ctx!.fillStyle = g; ctx!.fill();
140
+
141
+ // Node circle
142
+ ctx!.beginPath(); ctx!.arc(pos.x, pos.y, agentR, 0, Math.PI * 2);
143
+ ctx!.fillStyle = `rgba(${hexToRgb(col)},0.1)`; ctx!.fill();
144
+ ctx!.strokeStyle = `rgba(${hexToRgb(col)},${0.6 * pulse})`;
145
+ ctx!.lineWidth = isActive ? 2.5 : 1.5; ctx!.stroke();
146
+
147
+ // Adversarial warning ring
148
+ if (a.type === "adversarial") {
149
+ ctx!.beginPath();
150
+ ctx!.arc(pos.x, pos.y, agentR + 6 + Math.sin(tick * 0.1) * 3, 0, Math.PI * 2);
151
+ ctx!.strokeStyle = `rgba(255,45,85,${0.3 * pulse})`;
152
+ ctx!.lineWidth = 1; ctx!.setLineDash([3, 4]); ctx!.stroke(); ctx!.setLineDash([]);
153
+ }
154
+
155
+ // Labels
156
+ ctx!.font = '9px "Share Tech Mono"'; ctx!.fillStyle = col; ctx!.textAlign = "center";
157
+ ctx!.fillText(a.id, pos.x, pos.y - agentR - 8);
158
+ ctx!.fillStyle = "rgba(232,244,255,0.3)";
159
+ ctx!.fillText(a.trust.toFixed(2), pos.x, pos.y + 4);
160
+ });
161
+
162
+ // Orchestrator
163
+ const orchPulse = 0.7 + 0.3 * Math.sin(tick * 0.04);
164
+ const orchG = ctx!.createRadialGradient(cx, cy, 0, cx, cy, orchR * 3);
165
+ orchG.addColorStop(0, `rgba(0,245,255,${0.15 * orchPulse})`);
166
+ orchG.addColorStop(1, "transparent");
167
+ ctx!.beginPath(); ctx!.arc(cx, cy, orchR * 3, 0, Math.PI * 2);
168
+ ctx!.fillStyle = orchG; ctx!.fill();
169
+
170
+ ctx!.beginPath(); ctx!.arc(cx, cy, orchR, 0, Math.PI * 2);
171
+ ctx!.fillStyle = "rgba(0,245,255,0.1)"; ctx!.fill();
172
+ ctx!.strokeStyle = `rgba(0,245,255,${0.8 * orchPulse})`;
173
+ ctx!.lineWidth = 2; ctx!.stroke();
174
+
175
+ ctx!.beginPath(); ctx!.arc(cx, cy, orchR * 0.6, 0, Math.PI * 2);
176
+ ctx!.strokeStyle = `rgba(0,245,255,${0.4 * orchPulse})`;
177
+ ctx!.lineWidth = 1; ctx!.stroke();
178
+
179
+ ctx!.font = 'bold 9px "Share Tech Mono"'; ctx!.fillStyle = "#00F5FF";
180
+ ctx!.textAlign = "center"; ctx!.fillText("S0", cx, cy - 4);
181
+ ctx!.font = '8px "Share Tech Mono"'; ctx!.fillStyle = "rgba(0,245,255,0.5)";
182
+ ctx!.fillText("ORCH", cx, cy + 8);
183
+
184
+ animId = requestAnimationFrame(draw);
185
+ }
186
+
187
+ const ro = new ResizeObserver(() => resize());
188
+ ro.observe(canvas.parentElement!);
189
+ resize(); draw();
190
+
191
+ return () => { cancelAnimationFrame(animId); ro.disconnect(); };
192
+ }, []);
193
+
194
+ return <canvas ref={ref} />;
195
+ }
ui/app/components/SystemModules.tsx ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ type Props = { running: boolean; done: boolean; adversarialCount: number };
4
+
5
+ export default function SystemModules({ running, done, adversarialCount }: Props) {
6
+ return (
7
+ <div className="cards-grid">
8
+ <div className="card" style={{ "--card-accent": "#00f5ff" } as React.CSSProperties}>
9
+ <div className="card-id">MOD-001 // ENVIRONMENT</div>
10
+ <div className="card-icon">
11
+ <svg viewBox="0 0 40 40" fill="none"><rect x="2" y="2" width="16" height="16" stroke="#00F5FF" strokeWidth="1" opacity="0.6"/><rect x="22" y="2" width="16" height="16" stroke="#00F5FF" strokeWidth="1" opacity="0.3"/><rect x="2" y="22" width="16" height="16" stroke="#00F5FF" strokeWidth="1" opacity="0.3"/><rect x="22" y="22" width="16" height="16" stroke="#00F5FF" strokeWidth="1" opacity="0.6"/><line x1="18" y1="10" x2="22" y2="10" stroke="#00F5FF" strokeWidth="1"/><line x1="10" y1="18" x2="10" y2="22" stroke="#00F5FF" strokeWidth="1"/><circle cx="10" cy="10" r="2" fill="#00F5FF"/><circle cx="30" cy="30" r="2" fill="#00F5FF" opacity="0.5"/></svg>
12
+ </div>
13
+ <div className="card-title">Multi-Agent Environment</div>
14
+ <div className="card-body">
15
+ Discrete-time partially observable environment hosting N heterogeneous agents.
16
+ Supports configurable adversarial injection ratios and stochastic reward structures per episode.
17
+ </div>
18
+ <div className="card-footer">
19
+ <div className="card-status">
20
+ <div className="status-dot" style={running ? {} : { background: done ? "var(--amber)" : "var(--muted)", animation: "none" }} />
21
+ {running ? "RUNNING" : done ? "COMPLETE" : "IDLE"}
22
+ </div>
23
+ <div className="card-ver">gym v0.26.2</div>
24
+ </div>
25
+ </div>
26
+
27
+ <div className="card" style={{ "--card-accent": "#00ff88" } as React.CSSProperties}>
28
+ <div className="card-id">MOD-002 // TRUST ENGINE</div>
29
+ <div className="card-icon">
30
+ <svg viewBox="0 0 40 40" fill="none"><circle cx="20" cy="20" r="14" stroke="#00FF88" strokeWidth="1" strokeDasharray="4 2" opacity="0.5"/><circle cx="20" cy="20" r="8" stroke="#00FF88" strokeWidth="1" opacity="0.8"/><circle cx="20" cy="20" r="3" fill="#00FF88"/><line x1="20" y1="6" x2="20" y2="12" stroke="#00FF88" strokeWidth="1" opacity="0.5"/><line x1="20" y1="28" x2="20" y2="34" stroke="#00FF88" strokeWidth="1" opacity="0.5"/><line x1="6" y1="20" x2="12" y2="20" stroke="#00FF88" strokeWidth="1" opacity="0.5"/><line x1="28" y1="20" x2="34" y2="20" stroke="#00FF88" strokeWidth="1" opacity="0.5"/></svg>
31
+ </div>
32
+ <div className="card-title">Trust Calibration Engine</div>
33
+ <div className="card-body">
34
+ Bayesian trust scoring module that maintains per-agent belief distributions.
35
+ Updates posteriors using observed action-outcome consistency.
36
+ </div>
37
+ <div className="card-footer">
38
+ <div className="card-status">
39
+ <div className="status-dot" />
40
+ CALIBRATING
41
+ </div>
42
+ <div className="card-ver">TCE v1.1.4</div>
43
+ </div>
44
+ </div>
45
+
46
+ <div className="card" style={{ "--card-accent": "#ff2d55" } as React.CSSProperties}>
47
+ <div className="card-id">MOD-003 // ADV DETECTION</div>
48
+ <div className="card-icon">
49
+ <svg viewBox="0 0 40 40" fill="none"><polygon points="20,4 36,32 4,32" stroke="#FF2D55" strokeWidth="1" fill="none" opacity="0.7"/><line x1="20" y1="14" x2="20" y2="24" stroke="#FF2D55" strokeWidth="1.5"/><circle cx="20" cy="28" r="1.5" fill="#FF2D55"/></svg>
50
+ </div>
51
+ <div className="card-title">Adversarial Detection Layer</div>
52
+ <div className="card-body">
53
+ Anomaly-based detector using temporal divergence scoring across agent action histories.
54
+ Flags Byzantine agents via KL-divergence threshold on expected vs observed policy distributions.
55
+ </div>
56
+ <div className="card-footer">
57
+ <div className="card-status" style={{ color: adversarialCount > 0 ? "var(--red)" : "var(--green)" }}>
58
+ <div className="status-dot" style={adversarialCount > 0 ? { background: "var(--red)" } : {}} />
59
+ {adversarialCount > 0 ? `${adversarialCount} THREAT${adversarialCount > 1 ? "S" : ""}` : "CLEAR"}
60
+ </div>
61
+ <div className="card-ver">ADL v2.0.1</div>
62
+ </div>
63
+ </div>
64
+
65
+ <div className="card" style={{ "--card-accent": "#ffb800" } as React.CSSProperties}>
66
+ <div className="card-id">MOD-004 // RL OPTIMIZER</div>
67
+ <div className="card-icon">
68
+ <svg viewBox="0 0 40 40" fill="none"><polyline points="4,32 10,20 16,26 22,14 28,18 36,6" stroke="#FFB800" strokeWidth="1.5" fill="none"/><circle cx="10" cy="20" r="2" fill="#FFB800" opacity="0.6"/><circle cx="22" cy="14" r="2" fill="#FFB800" opacity="0.6"/><circle cx="36" cy="6" r="2.5" fill="#FFB800"/><line x1="4" y1="35" x2="36" y2="35" stroke="#FFB800" strokeWidth="0.5" opacity="0.3"/><line x1="4" y1="35" x2="4" y2="4" stroke="#FFB800" strokeWidth="0.5" opacity="0.3"/></svg>
69
+ </div>
70
+ <div className="card-title">Reinforcement Learning Optimizer</div>
71
+ <div className="card-body">
72
+ Proximal Policy Optimization (PPO) with trust-weighted reward shaping.
73
+ Policy gradient updates incorporate adversarial penalty terms.
74
+ </div>
75
+ <div className="card-footer">
76
+ <div className="card-status" style={{ color: "var(--amber)" }}>
77
+ <div className="status-dot" style={{ background: "var(--amber)" }} />
78
+ TRAINING
79
+ </div>
80
+ <div className="card-ver">PPO v3.2.0</div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ );
85
+ }
ui/app/globals.css CHANGED
@@ -1,398 +1,414 @@
1
- @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
2
 
3
  :root {
4
- --bg: #06080f;
5
- --bg-s: rgba(12,17,30,.65);
6
- --bg-e: rgba(22,30,52,.5);
7
- --bg-card: linear-gradient(135deg,rgba(15,20,38,.8),rgba(10,14,26,.9));
8
- --bdr: rgba(120,130,180,.08);
9
- --bdr-s: rgba(120,130,180,.15);
10
- --bdr-h: rgba(120,130,180,.25);
11
- --ink: #eef2ff;
12
- --ink2: #8b95b8;
13
- --ink3: #565f7e;
14
- --accent: #7c5cfc;
15
- --accent2: #c084fc;
16
- --accent3: #38bdf8;
17
- --green: #34d399;
18
- --red: #fb4e6a;
19
- --amber: #fbbf24;
20
- --indigo: #818cf8;
21
- --glow-accent: rgba(124,92,252,.35);
22
- --glow-green: rgba(52,211,153,.25);
23
- --glow-red: rgba(251,78,106,.2);
24
- --mono: 'JetBrains Mono',ui-monospace,monospace;
25
- --sans: 'Inter',system-ui,sans-serif;
26
- --display: 'Space Grotesk','Inter',system-ui,sans-serif;
27
- --r: 20px;
28
- --r-sm: 12px;
29
- --r-xs: 8px;
30
  }
31
 
32
- *{box-sizing:border-box;margin:0}
33
- html{background:var(--bg);scroll-behavior:smooth}
34
- body{
35
- color:var(--ink);font-family:var(--sans);min-height:100vh;
36
- background:
37
- radial-gradient(ellipse 120% 80% at 50% -20%,rgba(124,92,252,.08),transparent 60%),
38
- radial-gradient(ellipse 80% 60% at 0% 50%,rgba(56,189,248,.04),transparent),
39
- radial-gradient(ellipse 60% 80% at 100% 80%,rgba(192,132,252,.04),transparent),
40
- var(--bg);
41
- }
42
- button,select,input{font:inherit;color:inherit}
43
-
44
- /* ── animated bg ─────────────────────────────────────── */
45
- .grid-bg{
46
- position:fixed;inset:0;pointer-events:none;z-index:0;
47
- background-image:
48
- radial-gradient(circle at 1px 1px,rgba(120,130,180,.06) 1px,transparent 0);
49
- background-size:32px 32px;
50
- mask-image:radial-gradient(ellipse 60% 50% at 50% 40%,black,transparent);
51
  }
52
 
53
- /* ── glow orbs ───────────────────────────────────────── */
54
- .shell{position:relative;z-index:1;min-height:100vh;display:flex;flex-direction:column;overflow-x:hidden}
55
- .shell::before,.shell::after{
56
- content:'';position:fixed;border-radius:50%;pointer-events:none;
57
- filter:blur(100px);opacity:.4;z-index:0;
58
- }
59
- .shell::before{width:600px;height:600px;top:-200px;left:-100px;background:rgba(124,92,252,.15)}
60
- .shell::after{width:500px;height:500px;bottom:-150px;right:-100px;background:rgba(56,189,248,.1)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
- /* ── header ──────────────────────────────────────────── */
63
- .hdr{
64
- position:sticky;top:0;z-index:50;height:60px;padding:0 28px;
65
- display:flex;align-items:center;gap:24px;
66
- background:rgba(6,8,15,.7);backdrop-filter:blur(24px) saturate(1.6);
67
- border-bottom:1px solid var(--bdr);
68
- }
69
- .hdr-brand{
70
- font-family:var(--display);font-size:22px;font-weight:700;letter-spacing:-.03em;
71
- background:linear-gradient(135deg,#7c5cfc,#38bdf8);
72
- -webkit-background-clip:text;-webkit-text-fill-color:transparent;
73
- flex-shrink:0;
74
- }
75
- .hdr-nav{display:flex;gap:2px;margin-left:20px}
76
- .hdr-nav button{
77
- height:34px;border-radius:8px;border:1px solid transparent;padding:0 14px;
78
- background:transparent;cursor:pointer;font-size:13px;font-weight:500;
79
- color:var(--ink3);transition:all .2s;
80
- }
81
- .hdr-nav button:hover{color:var(--ink);background:rgba(124,92,252,.06)}
82
- .hdr-nav button.on{
83
- color:var(--ink);
84
- background:linear-gradient(135deg,rgba(124,92,252,.12),rgba(56,189,248,.06));
85
- border-color:rgba(124,92,252,.2);
86
- box-shadow:0 0 12px rgba(124,92,252,.1);
87
- }
88
- .hdr-controls{margin-left:auto;display:flex;gap:8px;align-items:center;flex-shrink:0}
89
- .hdr-pill{
90
- display:flex;align-items:center;gap:6px;
91
- background:rgba(12,17,30,.6);border:1px solid var(--bdr);
92
- border-radius:var(--r-xs);padding:6px 10px;
93
- }
94
- .hdr-pill label{font-size:10px;color:var(--ink3);text-transform:uppercase;letter-spacing:.08em;font-weight:600}
95
- .hdr-pill select,.hdr-pill input{background:transparent;border:0;outline:none;width:56px;font-size:13px;font-weight:600}
96
- .hdr-pill select{width:70px}
97
 
98
- /* ── buttons ─────────────────────────────────────────── */
99
- .btn{
100
- height:40px;border-radius:var(--r-sm);padding:0 18px;
101
- border:1px solid var(--bdr-s);cursor:pointer;font-size:13px;font-weight:600;
102
- display:inline-flex;align-items:center;gap:8px;transition:all .2s;
103
- background:rgba(12,17,30,.5);
104
- }
105
- .btn:hover{border-color:var(--bdr-h);transform:translateY(-1px);box-shadow:0 4px 20px rgba(0,0,0,.3)}
106
- .btn:disabled{opacity:.4;cursor:not-allowed;transform:none;box-shadow:none}
107
- .btn-primary{
108
- background:linear-gradient(135deg,rgba(124,92,252,.3),rgba(56,189,248,.12));
109
- border-color:rgba(124,92,252,.3);
110
- box-shadow:0 0 20px rgba(124,92,252,.08);
111
- }
112
- .btn-primary:hover{box-shadow:0 0 30px rgba(124,92,252,.2);border-color:rgba(124,92,252,.5)}
113
- .btn-danger{border-color:rgba(251,78,106,.3);color:var(--red)}
114
- .btn-lg{height:52px;padding:0 28px;font-size:15px;border-radius:14px;font-family:var(--display)}
115
- .btn-block{width:100%}
116
- .btn-ghost{border-color:transparent;background:transparent}
117
- .btn-ghost:hover{background:rgba(124,92,252,.06)}
118
- .btn-glow{
119
- position:relative;overflow:hidden;
120
- }
121
- .btn-glow::before{
122
- content:'';position:absolute;inset:-2px;border-radius:inherit;
123
- background:linear-gradient(135deg,#7c5cfc,#38bdf8,#c084fc);opacity:0;
124
- transition:opacity .3s;z-index:-1;filter:blur(8px);
125
- }
126
- .btn-glow:hover::before{opacity:.4}
127
 
128
- /* ── panel ────────────────��──────────────────────────── */
129
- .panel{
130
- background:var(--bg-card);
131
- border:1px solid var(--bdr);
132
- border-radius:var(--r);padding:24px;
133
- backdrop-filter:blur(16px);
134
- box-shadow:0 8px 40px rgba(0,0,0,.25),inset 0 1px 0 rgba(255,255,255,.03);
135
- position:relative;overflow:hidden;
136
- }
137
- .panel::before{
138
- content:'';position:absolute;top:0;left:0;right:0;height:1px;
139
- background:linear-gradient(90deg,transparent,rgba(255,255,255,.06),transparent);
140
- }
141
- .panel-head{margin-bottom:18px}
142
- .panel-eyebrow{
143
- font-size:10px;text-transform:uppercase;letter-spacing:.14em;
144
- color:var(--accent);font-weight:700;margin-bottom:6px;
145
- font-family:var(--mono);
146
- }
147
- .panel-title{font-size:20px;font-weight:700;font-family:var(--display);letter-spacing:-.02em}
148
 
149
- /* ── main ────────────────────────────────────────────── */
150
- .main{flex:1;width:min(1440px,100%);margin:0 auto;padding:24px 28px 80px;position:relative;z-index:1}
 
 
 
151
 
152
- /* ══════════════════════════════════════════════════════
153
- LANDING
154
- ══════════════════════════════════════════════════════ */
155
- .land{display:grid;gap:40px}
156
- .land-hero{text-align:center;padding:80px 20px 48px;position:relative}
157
- .land-hero h1{
158
- font-family:var(--display);font-size:clamp(40px,7vw,72px);font-weight:700;
159
- line-height:.95;letter-spacing:-.04em;margin-bottom:20px;
160
- }
161
- .land-hero h1 span{
162
- background:linear-gradient(135deg,#7c5cfc 0%,#38bdf8 50%,#34d399 100%);
163
- -webkit-background-clip:text;-webkit-text-fill-color:transparent;
164
- }
165
- .land-hero p{
166
- max-width:580px;margin:0 auto 36px;font-size:16px;line-height:1.7;color:var(--ink2);
167
- }
168
- .land-ctas{display:flex;gap:14px;justify-content:center;flex-wrap:wrap}
169
 
170
- /* score strip */
171
- .score-strip{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;max-width:780px;margin:0 auto}
172
- .score-card{text-align:center;padding:24px 16px;position:relative}
173
- .score-card .lbl{font-size:10px;color:var(--ink3);text-transform:uppercase;letter-spacing:.12em;font-weight:600;font-family:var(--mono)}
174
- .score-card .val{font-size:32px;font-weight:700;margin-top:8px;font-family:var(--mono);letter-spacing:-.02em}
175
- .score-card.g .val{color:var(--green);text-shadow:0 0 20px var(--glow-green)}
176
- .score-card.a .val{color:var(--indigo);text-shadow:0 0 20px var(--glow-accent)}
177
- .score-card.r .val{color:var(--red);text-shadow:0 0 20px var(--glow-red)}
178
- .score-card.w .val{color:var(--amber)}
179
 
180
- /* before / after */
181
- .ba-section{max-width:920px;margin:0 auto}
182
- .ba-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px}
183
- .ba-card{padding:28px;position:relative}
184
- .ba-card.before{border-color:rgba(251,78,106,.15)}
185
- .ba-card.before::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,var(--red),var(--amber))}
186
- .ba-card.after{border-color:rgba(52,211,153,.15)}
187
- .ba-card.after::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,var(--green),var(--accent3))}
188
- .ba-tag{
189
- display:inline-flex;align-items:center;gap:6px;
190
- font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;
191
- padding:5px 12px;border-radius:6px;margin-bottom:14px;font-family:var(--mono);
192
- }
193
- .ba-card.before .ba-tag{background:rgba(251,78,106,.1);color:var(--red)}
194
- .ba-card.after .ba-tag{background:rgba(52,211,153,.1);color:var(--green)}
195
- .ba-card h3{font-size:18px;margin-bottom:14px;font-family:var(--display)}
196
- .ba-steps{display:grid;gap:8px}
197
- .ba-step{
198
- font-size:13px;color:var(--ink2);line-height:1.5;padding:12px 14px;
199
- border-radius:var(--r-xs);background:rgba(0,0,0,.25);
200
- display:flex;align-items:flex-start;gap:10px;
201
- border:1px solid rgba(255,255,255,.02);
202
- }
203
- .ba-step .num{
204
- flex-shrink:0;width:22px;height:22px;border-radius:50%;font-size:11px;font-weight:700;
205
- display:grid;place-items:center;
206
- }
207
- .ba-card.before .ba-step .num{background:rgba(251,78,106,.15);color:var(--red)}
208
- .ba-card.after .ba-step .num{background:rgba(52,211,153,.15);color:var(--green)}
209
- .ba-score{font-family:var(--mono);font-size:40px;font-weight:700;margin-top:20px}
210
- .ba-card.before .ba-score{color:var(--red);text-shadow:0 0 30px var(--glow-red)}
211
- .ba-card.after .ba-score{color:var(--green);text-shadow:0 0 30px var(--glow-green)}
212
 
213
- /* architecture */
214
- .arch-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;max-width:920px;margin:0 auto}
215
- .arch-card{padding:24px;transition:all .3s}
216
- .arch-card:hover{border-color:rgba(124,92,252,.2);transform:translateY(-2px);box-shadow:0 8px 40px rgba(124,92,252,.08)}
217
- .arch-card svg{color:var(--accent);margin-bottom:12px}
218
- .arch-card h4{font-size:15px;margin-bottom:8px;font-family:var(--display)}
219
- .arch-card p{font-size:12px;color:var(--ink2);line-height:1.6}
 
 
 
 
220
 
221
- /* evidence chart gallery */
222
- .chart-section{max-width:1180px;margin:0 auto}
223
- .chart-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
224
- .chart-card{
225
- display:flex;flex-direction:column;gap:14px;text-decoration:none;color:inherit;
226
- padding:16px;transition:all .25s;
227
- }
228
- .chart-card:hover{border-color:rgba(56,189,248,.24);transform:translateY(-2px)}
229
- .chart-meta{display:flex;gap:10px;align-items:flex-start;min-height:64px}
230
- .chart-meta svg{color:var(--accent3);flex-shrink:0;margin-top:2px}
231
- .chart-meta h4{font-size:14px;font-family:var(--display);margin-bottom:5px}
232
- .chart-meta p{font-size:12px;line-height:1.45;color:var(--ink2)}
233
- .chart-card img{
234
- display:block;width:100%;aspect-ratio:5/3;object-fit:cover;
235
- border-radius:10px;border:1px solid rgba(255,255,255,.06);
236
- background:#f8fafc;
237
  }
238
 
239
- /* ══════════════════════════════════════════════════════
240
- MISSION CONTROL
241
- ══════════════════════════════════════════════════════ */
242
- .mc{display:grid;grid-template-columns:1.1fr 0.9fr;gap:18px}
243
- .mc-left,.mc-right{display:grid;gap:18px;align-content:start}
244
-
245
- /* specialist network */
246
- .net{position:relative;aspect-ratio:1.1;max-height:420px;margin:0 auto;width:100%}
247
- .net-svg{position:absolute;inset:0;width:100%;height:100%}
248
- .net-line{stroke:rgba(120,130,180,.1);stroke-width:1;transition:all .4s}
249
- .net-line.active{stroke:var(--accent);stroke-width:2;filter:drop-shadow(0 0 8px var(--glow-accent))}
250
- .net-node{
251
- position:absolute;transform:translate(-50%,-50%);
252
- border-radius:16px;padding:12px 16px;text-align:center;min-width:76px;
253
- border:1px solid var(--bdr);background:var(--bg-card);
254
- backdrop-filter:blur(12px);transition:all .3s;cursor:default;
255
- }
256
- .net-node.orch{
257
- min-width:110px;z-index:2;
258
- border-color:rgba(124,92,252,.25);
259
- background:linear-gradient(135deg,rgba(124,92,252,.15),rgba(56,189,248,.06));
260
- box-shadow:0 0 30px rgba(124,92,252,.1);
261
- }
262
- .net-node.active{
263
- border-color:rgba(124,92,252,.5);
264
- box-shadow:0 0 24px var(--glow-accent),inset 0 0 20px rgba(124,92,252,.05);
265
- }
266
- .net-node.danger{border-color:rgba(251,78,106,.35);box-shadow:0 0 20px var(--glow-red)}
267
- .net-node .id{font-weight:700;font-size:14px;font-family:var(--display)}
268
- .net-node .trust{font-family:var(--mono);font-size:12px;color:var(--ink2);margin-top:3px}
269
- .net-node .delta{font-family:var(--mono);font-size:10px;margin-top:2px}
270
- .delta-up{color:var(--green)}
271
- .delta-down{color:var(--red)}
272
 
273
- /* trust timeline */
274
- .tl{display:grid;gap:12px}
275
- .tl-row{display:grid;grid-template-columns:40px 1fr 54px 56px;align-items:center;gap:10px}
276
- .tl-id{font-weight:700;font-size:14px;font-family:var(--display)}
277
- .tl-track{height:8px;border-radius:99px;background:rgba(255,255,255,.04);overflow:hidden;border:1px solid rgba(255,255,255,.03)}
278
- .tl-fill{height:100%;border-radius:inherit;transition:width .5s cubic-bezier(.34,1.56,.64,1)}
279
- .tl-val{font-family:var(--mono);font-size:13px;font-weight:600;text-align:right}
280
- .tl-delta{font-family:var(--mono);font-size:11px;text-align:right}
281
 
282
- /* mission briefing */
283
- .brief-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px;margin-bottom:16px}
284
- .brief-stat{padding:14px;border-radius:var(--r-sm);background:rgba(0,0,0,.2);border:1px solid rgba(255,255,255,.02)}
285
- .brief-stat .lbl{font-size:10px;color:var(--ink3);text-transform:uppercase;letter-spacing:.1em;font-weight:600;font-family:var(--mono)}
286
- .brief-stat .val{font-size:20px;font-weight:700;margin-top:6px;font-family:var(--mono)}
287
- .brief-subtask{padding:16px;border-radius:var(--r-sm);background:rgba(0,0,0,.15);margin-top:12px;border:1px solid rgba(255,255,255,.02)}
288
- .brief-subtask .top{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
289
- .brief-subtask .top span{font-size:10px;color:var(--ink3);text-transform:uppercase;font-family:var(--mono);font-weight:600}
290
- .brief-subtask p{font-size:13px;color:var(--ink2);line-height:1.6}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
 
292
- /* stakes gauge */
293
- .stakes{display:flex;align-items:center;gap:10px}
294
- .stakes-track{flex:1;height:6px;border-radius:99px;background:rgba(255,255,255,.04);overflow:hidden}
295
- .stakes-fill{height:100%;border-radius:inherit;transition:width .4s}
296
- .stakes-val{font-family:var(--mono);font-size:13px;font-weight:700}
297
- .stakes-warn{font-size:10px;font-weight:800;text-transform:uppercase;color:var(--red);letter-spacing:.06em;animation:pulse-warn 1.2s ease-in-out infinite}
298
- @keyframes pulse-warn{0%,100%{opacity:1}50%{opacity:.3}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
- /* action center */
301
- .ac-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
302
- .ac-btn{
303
- height:56px;border-radius:14px;font-size:14px;font-weight:600;
304
- border:1px solid var(--bdr);background:var(--bg-card);
305
- cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;
306
- transition:all .25s;color:var(--ink);font-family:var(--display);
307
- position:relative;overflow:hidden;
308
- }
309
- .ac-btn:hover{border-color:var(--bdr-h);transform:translateY(-2px);box-shadow:0 8px 30px rgba(0,0,0,.3)}
310
- .ac-btn:disabled{opacity:.35;cursor:not-allowed;transform:none;box-shadow:none}
311
- .ac-btn.rec{
312
- border-color:rgba(124,92,252,.3);
313
- background:linear-gradient(135deg,rgba(124,92,252,.15),rgba(56,189,248,.06));
314
- box-shadow:0 0 24px rgba(124,92,252,.08);
315
- }
316
- .ac-btn.rec::after{content:'★';font-size:10px;color:var(--accent)}
317
- .ac-auto{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px}
318
 
319
- /* flight recorder */
320
- .fr-list{display:grid;gap:6px;max-height:340px;overflow-y:auto}
321
- .fr-list::-webkit-scrollbar{width:4px}
322
- .fr-list::-webkit-scrollbar-thumb{background:var(--bdr-s);border-radius:2px}
323
- .fr-row{
324
- display:grid;grid-template-columns:36px 1fr auto;gap:10px;
325
- padding:10px 14px;border-radius:var(--r-sm);align-items:center;
326
- background:rgba(0,0,0,.15);font-size:13px;border:1px solid rgba(255,255,255,.02);
327
- transition:background .2s;
328
- }
329
- .fr-row:hover{background:rgba(124,92,252,.04)}
330
- .fr-step{font-weight:700;color:var(--ink3);font-family:var(--mono);font-size:12px}
331
- .fr-action{font-weight:600;font-size:13px}
332
- .fr-summary{color:var(--ink2);font-size:11px;margin-top:2px}
333
- .fr-reward{font-family:var(--mono);font-weight:700;font-size:14px}
334
- .fr-reward.pos{color:var(--green);text-shadow:0 0 8px var(--glow-green)}
335
- .fr-reward.neg{color:var(--red)}
336
- .fr-icon{font-size:14px}
337
- .fr-toggle{font-size:11px;color:var(--ink3);cursor:pointer;background:none;border:none;padding:6px 0;margin-top:8px;font-family:var(--mono)}
338
- .fr-toggle:hover{color:var(--accent)}
339
 
340
- /* json viewer */
341
- .json-view{margin-top:10px;border-radius:var(--r-sm);overflow:hidden;border:1px solid var(--bdr)}
342
- .json-view pre{
343
- margin:0;padding:14px;max-height:240px;overflow:auto;
344
- font-family:var(--mono);font-size:11px;line-height:1.6;
345
- color:var(--ink3);background:rgba(0,0,0,.3);
346
- }
 
 
 
 
 
347
 
348
- /* ══════════════════════════════════════════════════════
349
- JUDGE WIZARD
350
- ══════════════════════════════════════════════════════ */
351
- .jw{max-width:840px;margin:0 auto;display:grid;gap:24px}
352
- .jw-progress{display:flex;gap:8px;align-items:center;justify-content:center;padding:8px 0}
353
- .jw-dot{width:12px;height:12px;border-radius:50%;background:rgba(120,130,180,.12);border:2px solid var(--bdr-s);transition:all .3s}
354
- .jw-dot.done{background:var(--green);border-color:var(--green);box-shadow:0 0 10px var(--glow-green)}
355
- .jw-dot.active{background:var(--accent);border-color:var(--accent);box-shadow:0 0 12px var(--glow-accent)}
356
- .jw-bar{width:48px;height:2px;background:var(--bdr);border-radius:1px;transition:background .3s}
357
- .jw-bar.done{background:var(--green)}
358
 
359
- .jw-stage{padding:40px 36px;text-align:center}
360
- .jw-step-num{font-size:11px;text-transform:uppercase;letter-spacing:.14em;color:var(--accent);font-weight:700;margin-bottom:12px;font-family:var(--mono)}
361
- .jw-stage h2{font-size:32px;font-weight:700;margin-bottom:12px;font-family:var(--display);letter-spacing:-.02em}
362
- .jw-stage p{color:var(--ink2);font-size:15px;line-height:1.7;max-width:520px;margin:0 auto 28px}
 
 
 
 
 
 
 
 
363
 
364
- .jw-results{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin:24px auto;max-width:500px}
365
- .jw-stat{padding:20px;border-radius:var(--r-sm);background:rgba(0,0,0,.2);text-align:center;border:1px solid rgba(255,255,255,.03)}
366
- .jw-stat .lbl{font-size:10px;color:var(--ink3);text-transform:uppercase;letter-spacing:.1em;font-weight:600;font-family:var(--mono)}
367
- .jw-stat .val{font-size:26px;font-weight:700;margin-top:6px;font-family:var(--mono)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
 
369
- .jw-nav{display:flex;gap:14px;justify-content:center;margin-top:20px}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
 
371
- .jw-compare{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:24px}
372
- .jw-compare-card{padding:28px;text-align:center}
373
- .jw-compare-card h4{font-size:14px;margin-bottom:10px;font-family:var(--display);color:var(--ink2)}
374
- .jw-compare-card .big{font-family:var(--mono);font-size:44px;font-weight:700}
375
- .jw-compare-card.bad .big{color:var(--red);text-shadow:0 0 30px var(--glow-red)}
376
- .jw-compare-card.good .big{color:var(--green);text-shadow:0 0 30px var(--glow-green)}
 
 
 
377
 
378
- /* judge inline trust + recorder */
379
- .jw-inline{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:20px;max-width:700px;margin-left:auto;margin-right:auto}
380
- .jw-inline .panel{padding:18px}
 
 
 
 
381
 
382
- /* ── responsive ──────────────────────────────────────── */
383
- @media(max-width:1024px){
384
- .mc,.ba-grid,.jw-inline{grid-template-columns:1fr}
385
- .arch-grid{grid-template-columns:repeat(2,1fr)}
386
- .chart-grid{grid-template-columns:repeat(2,1fr)}
387
- .score-strip{grid-template-columns:repeat(2,1fr)}
388
- }
389
- @media(max-width:640px){
390
- .hdr{padding:0 14px;gap:10px;height:52px}
391
- .hdr-nav{display:none}
392
- .main{padding:14px 14px 40px}
393
- .arch-grid,.chart-grid,.ac-grid,.ac-auto,.brief-grid,.jw-results,.jw-compare{grid-template-columns:1fr}
394
- .score-strip{grid-template-columns:1fr}
395
- .land-hero{padding:48px 12px 32px}
396
- .land-hero h1{font-size:36px}
397
- .net{max-height:280px}
398
- }
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Orbitron:wght@400;600;700;900&family=Space+Mono:wght@400;700&display=swap');
2
 
3
  :root {
4
+ --bg: #03060f;
5
+ --bg2: #060d1a;
6
+ --bg3: #0a1628;
7
+ --cyan: #00f5ff;
8
+ --blue: #0055ff;
9
+ --blue-dim: #0033aa;
10
+ --green: #00ff88;
11
+ --red: #ff2d55;
12
+ --amber: #ffb800;
13
+ --white: #e8f4ff;
14
+ --muted: #4a6080;
15
+ --glass: rgba(0, 200, 255, 0.04);
16
+ --glass-border: rgba(0, 200, 255, 0.12);
17
+ --glass-hover: rgba(0, 200, 255, 0.08);
18
+ --font-display: 'Orbitron', monospace;
19
+ --font-mono: 'Share Tech Mono', monospace;
20
+ --font-body: 'Space Mono', monospace;
 
 
 
 
 
 
 
 
 
21
  }
22
 
23
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
24
+ html { scroll-behavior: smooth; }
25
+ body {
26
+ background: var(--bg);
27
+ color: var(--white);
28
+ font-family: var(--font-body);
29
+ font-size: 14px;
30
+ line-height: 1.7;
31
+ overflow-x: hidden;
 
 
 
 
 
 
 
 
 
 
32
  }
33
 
34
+ /* ── SCROLLBAR ── */
35
+ ::-webkit-scrollbar { width: 4px; }
36
+ ::-webkit-scrollbar-track { background: var(--bg); }
37
+ ::-webkit-scrollbar-thumb { background: var(--blue-dim); border-radius: 2px; }
38
+
39
+ /* ── NAV ── */
40
+ .nav-bar {
41
+ position: fixed; top: 0; left: 0; right: 0; z-index: 100;
42
+ display: flex; align-items: center; justify-content: space-between;
43
+ padding: 18px 48px;
44
+ background: rgba(3, 6, 15, 0.85);
45
+ backdrop-filter: blur(12px);
46
+ border-bottom: 1px solid rgba(0, 200, 255, 0.08);
47
+ overflow: hidden;
48
+ }
49
+ .nav-bar::before {
50
+ content: ""; position: absolute; inset: 0;
51
+ background: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0, 245, 255, 0.01) 2px, rgba(0, 245, 255, 0.01) 4px);
52
+ pointer-events: none; opacity: 0.5;
53
+ }
54
+ .nav-logo {
55
+ font-family: var(--font-display);
56
+ font-size: 15px; font-weight: 700;
57
+ letter-spacing: 0.35em; color: var(--cyan);
58
+ display: flex; align-items: center; gap: 10px;
59
+ animation: logo-flicker 10s linear infinite;
60
+ }
61
+ @keyframes logo-flicker {
62
+ 0%, 95%, 100% { opacity: 1; }
63
+ 96% { opacity: 0.7; }
64
+ 97% { opacity: 1; }
65
+ 98% { opacity: 0.8; }
66
+ 99% { opacity: 1; }
67
+ }
68
+ .nav-logo-dot {
69
+ width: 8px; height: 8px;
70
+ background: var(--cyan); border-radius: 50%;
71
+ box-shadow: 0 0 10px var(--cyan);
72
+ animation: pulse-dot 2s ease-in-out infinite;
73
+ }
74
+ @keyframes pulse-dot {
75
+ 0%, 100% { opacity: 1; box-shadow: 0 0 10px var(--cyan); }
76
+ 50% { opacity: 0.4; box-shadow: 0 0 20px var(--cyan); }
77
+ }
78
+ .nav-links {
79
+ display: flex; gap: 36px; list-style: none;
80
+ font-family: var(--font-mono); font-size: 11px;
81
+ letter-spacing: 0.15em; color: var(--muted);
82
+ }
83
+ .nav-links a, .nav-links button {
84
+ color: inherit; text-decoration: none; background: none; border: none;
85
+ cursor: pointer; font: inherit; letter-spacing: inherit;
86
+ transition: color 0.2s;
87
+ }
88
+ .nav-links a:hover, .nav-links button:hover { color: var(--cyan); }
89
+ .nav-badge {
90
+ font-family: var(--font-mono); font-size: 10px;
91
+ letter-spacing: 0.12em; color: var(--green);
92
+ border: 1px solid rgba(0, 255, 136, 0.3);
93
+ padding: 4px 12px; border-radius: 2px;
94
+ background: rgba(0, 255, 136, 0.06);
95
+ display: flex; align-items: center; gap: 6px;
96
+ }
97
+ .nav-badge-dot {
98
+ width: 5px; height: 5px; background: currentColor; border-radius: 50%;
99
+ box-shadow: 0 0 8px currentColor;
100
+ animation: pulse-badge 1.5s ease-in-out infinite;
101
+ }
102
+ .nav-badge.running { color: var(--cyan); border-color: rgba(0, 245, 255, 0.3); background: rgba(0, 245, 255, 0.06); }
103
+ .nav-badge.complete { color: var(--amber); border-color: rgba(255, 184, 0, 0.3); background: rgba(255, 184, 0, 0.06); }
104
 
105
+ /* ── HERO ── */
106
+ .hero { position: relative; min-height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: flex-start; padding: 120px 48px 80px; overflow: hidden; }
107
+ .hero-scanline { position: absolute; inset: 0; z-index: 1; pointer-events: none; background: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,0.04) 2px, rgba(0,0,0,0.04) 4px); }
108
+ .hero-canvas-wrap { position: absolute; inset: 0; z-index: 0; opacity: 0.55; }
109
+ .hero-canvas-wrap canvas { display: block; width: 100%; height: 100%; }
110
+ .hero-content { position: relative; z-index: 2; max-width: 760px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
+ .hero-tag {
113
+ font-family: var(--font-mono); font-size: 10px;
114
+ letter-spacing: 0.3em; color: var(--cyan);
115
+ margin-bottom: 24px; display: flex; align-items: center; gap: 10px;
116
+ }
117
+ .hero-tag::before { content: ''; display: block; width: 32px; height: 1px; background: var(--cyan); opacity: 0.6; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
+ .hero h1 {
120
+ font-family: var(--font-display);
121
+ font-size: clamp(32px, 5vw, 58px);
122
+ font-weight: 900; line-height: 1.1;
123
+ letter-spacing: -0.01em; color: var(--white);
124
+ margin-bottom: 24px;
125
+ }
126
+ .hero h1 .accent { color: var(--cyan); }
127
+ .hero h1 .accent-red { color: var(--red); }
 
 
 
 
 
 
 
 
 
 
 
128
 
129
+ .hero-sub {
130
+ font-family: var(--font-body); font-size: 14px; line-height: 1.9;
131
+ color: rgba(232, 244, 255, 0.55);
132
+ max-width: 560px; margin-bottom: 44px;
133
+ }
134
 
135
+ .hero-ctas { display: flex; gap: 16px; flex-wrap: wrap; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
+ .btn-primary {
138
+ font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.2em;
139
+ padding: 14px 32px; background: var(--cyan); color: #03060f;
140
+ border: none; cursor: pointer; font-weight: 700; text-transform: uppercase;
141
+ clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px));
142
+ transition: all 0.2s; text-decoration: none; display: inline-flex; align-items: center; gap: 8px;
143
+ }
144
+ .btn-primary:hover { background: #fff; box-shadow: 0 0 30px rgba(0, 245, 255, 0.5); }
145
+ .btn-primary:disabled { opacity: 0.4; cursor: not-allowed; box-shadow: none; }
146
 
147
+ .btn-secondary {
148
+ font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.2em;
149
+ padding: 13px 32px; background: transparent; color: var(--cyan);
150
+ border: 1px solid rgba(0, 245, 255, 0.35); cursor: pointer; text-transform: uppercase;
151
+ clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px));
152
+ transition: all 0.2s; text-decoration: none; display: inline-flex; align-items: center; gap: 8px;
153
+ }
154
+ .btn-secondary:hover { background: rgba(0, 245, 255, 0.08); border-color: var(--cyan); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
+ .btn-sm-ctrl {
157
+ font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.15em;
158
+ padding: 6px 14px; background: transparent; color: var(--cyan);
159
+ border: 1px solid rgba(0, 245, 255, 0.2); cursor: pointer; text-transform: uppercase;
160
+ transition: all 0.2s;
161
+ }
162
+ .btn-sm-ctrl:hover { background: rgba(0, 245, 255, 0.08); border-color: var(--cyan); }
163
+ .btn-sm-ctrl:disabled { opacity: 0.3; cursor: not-allowed; }
164
+ .btn-sm-ctrl.active { background: rgba(0, 245, 255, 0.12); border-color: var(--cyan); }
165
+ .btn-sm-ctrl.danger { color: var(--red); border-color: rgba(255, 45, 85, 0.3); }
166
+ .btn-sm-ctrl.danger:hover { background: rgba(255, 45, 85, 0.08); }
167
 
168
+ .hero-stats {
169
+ position: absolute; right: 48px; bottom: 80px; z-index: 2;
170
+ display: flex; flex-direction: column; gap: 20px; text-align: right;
171
+ }
172
+ .hero-stat-item { font-family: var(--font-mono); }
173
+ .hero-stat-num {
174
+ font-family: var(--font-display); font-size: 26px; font-weight: 700;
175
+ color: var(--cyan); line-height: 1;
176
+ text-shadow: 0 0 20px rgba(0, 245, 255, 0.4);
177
+ }
178
+ .hero-stat-label {
179
+ font-size: 9px; letter-spacing: 0.2em;
180
+ color: var(--muted); text-transform: uppercase;
 
 
 
181
  }
182
 
183
+ /* ── SECTION SHARED ── */
184
+ .section-block { padding: 100px 48px; position: relative; }
185
+ .section-block.alt-bg { background: var(--bg2); }
186
+ .section-label {
187
+ font-family: var(--font-mono); font-size: 9px;
188
+ letter-spacing: 0.35em; color: var(--muted); text-transform: uppercase;
189
+ margin-bottom: 12px; display: flex; align-items: center; gap: 12px;
190
+ }
191
+ .section-label::after { content: ''; flex: 1; max-width: 60px; height: 1px; background: var(--muted); opacity: 0.4; }
192
+ .section-title {
193
+ font-family: var(--font-display);
194
+ font-size: clamp(22px, 3vw, 34px);
195
+ font-weight: 700; color: var(--white);
196
+ margin-bottom: 16px; letter-spacing: 0.02em;
197
+ }
198
+ .section-desc {
199
+ font-size: 13px; color: rgba(232, 244, 255, 0.4);
200
+ max-width: 480px; line-height: 1.8; margin-bottom: 60px;
201
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
+ /* ── DIVIDER ── */
204
+ .divider { width: calc(100% - 96px); height: 1px; background: linear-gradient(90deg, transparent, rgba(0, 200, 255, 0.15), transparent); margin: 0 48px; }
 
 
 
 
 
 
205
 
206
+ /* ── FEATURE CARDS ── */
207
+ .cards-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 2px; }
208
+ .card {
209
+ background: var(--glass); border: 1px solid var(--glass-border);
210
+ padding: 32px 28px; position: relative; overflow: hidden;
211
+ transition: border-color 0.3s, background 0.3s;
212
+ }
213
+ .card::before {
214
+ content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px;
215
+ background: linear-gradient(90deg, transparent, var(--card-accent, var(--cyan)), transparent);
216
+ opacity: 0; transition: opacity 0.3s;
217
+ }
218
+ .card:hover { background: var(--glass-hover); border-color: rgba(0, 200, 255, 0.25); }
219
+ .card:hover::before { opacity: 1; }
220
+ .card-id { font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.3em; color: var(--card-accent, var(--cyan)); margin-bottom: 20px; opacity: 0.7; }
221
+ .card-icon { width: 40px; height: 40px; margin-bottom: 20px; }
222
+ .card-icon svg { width: 100%; height: 100%; }
223
+ .card-title { font-family: var(--font-display); font-size: 13px; font-weight: 600; letter-spacing: 0.08em; color: var(--white); margin-bottom: 12px; }
224
+ .card-body { font-size: 12px; line-height: 1.8; color: rgba(232, 244, 255, 0.45); }
225
+ .card-footer { margin-top: 24px; padding-top: 16px; border-top: 1px solid rgba(0, 200, 255, 0.08); display: flex; justify-content: space-between; align-items: center; }
226
+ .card-status { font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.2em; color: var(--green); display: flex; align-items: center; gap: 6px; }
227
+ .status-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--green); animation: pulse-dot 1.5s ease-in-out infinite; }
228
+ .card-ver { font-family: var(--font-mono); font-size: 9px; color: var(--muted); }
229
 
230
+ /* ── SIMULATION ── */
231
+ .sim-wrapper {
232
+ background: #020810;
233
+ border: 1px solid rgba(0, 200, 255, 0.14); border-radius: 2px; overflow: hidden;
234
+ box-shadow: 0 0 80px rgba(0, 80, 200, 0.08), inset 0 0 80px rgba(0, 0, 0, 0.5);
235
+ }
236
+ .sim-topbar {
237
+ background: rgba(0, 20, 50, 0.9);
238
+ border-bottom: 1px solid rgba(0, 200, 255, 0.1);
239
+ padding: 10px 20px; display: flex; align-items: center; gap: 16px;
240
+ }
241
+ .sim-dots { display: flex; gap: 6px; }
242
+ .sim-dot { width: 10px; height: 10px; border-radius: 50%; }
243
+ .sim-dot.r { background: #ff2d55; }
244
+ .sim-dot.y { background: #ffb800; }
245
+ .sim-dot.g { background: #00ff88; }
246
+ .sim-topbar-title {
247
+ font-family: var(--font-mono); font-size: 10px;
248
+ letter-spacing: 0.2em; color: var(--muted); flex: 1; text-align: center;
249
+ }
250
+ .sim-topbar-badge {
251
+ font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.15em;
252
+ color: var(--green); border: 1px solid rgba(0, 255, 136, 0.25);
253
+ padding: 2px 8px; animation: pulse-badge 2s ease-in-out infinite;
254
+ }
255
+ @keyframes pulse-badge { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
256
+ .sim-body { display: grid; grid-template-columns: 240px 1fr 240px; min-height: 420px; }
257
+ .sim-panel {
258
+ border-right: 1px solid rgba(0, 200, 255, 0.08); padding: 20px 16px;
259
+ }
260
+ .sim-panel:last-child { border-right: none; border-left: 1px solid rgba(0, 200, 255, 0.08); }
261
+ .sim-panel-label {
262
+ font-family: var(--font-mono); font-size: 8px;
263
+ letter-spacing: 0.3em; color: var(--muted); text-transform: uppercase;
264
+ margin-bottom: 16px; padding-bottom: 8px;
265
+ border-bottom: 1px solid rgba(0, 200, 255, 0.08);
266
+ }
267
+ .sim-center { position: relative; overflow: hidden; }
268
+ .sim-center canvas { display: block; width: 100%; height: 100%; }
269
 
270
+ /* agent rows inside sim */
271
+ .agent-row { margin-bottom: 14px; position: relative; }
272
+ .agent-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; }
273
+ .agent-id { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.15em; }
274
+ .agent-id.active { color: var(--cyan); }
275
+ .agent-id.neutral { color: var(--muted); }
276
+ .agent-id.adversarial { color: var(--red); }
277
+ .agent-trust-val { font-family: var(--font-display); font-size: 11px; font-weight: 700; }
278
+ .trust-bar-bg { height: 4px; background: rgba(255,255,255,0.06); border-radius: 2px; overflow: hidden; }
279
+ .trust-bar-fill { height: 100%; border-radius: 2px; transition: width 0.8s ease; }
280
+ .trust-bar-fill.high { background: linear-gradient(90deg, #00a870, var(--green)); box-shadow: 0 0 6px rgba(0,255,136,0.5); }
281
+ .trust-bar-fill.mid { background: linear-gradient(90deg, #cc8800, var(--amber)); box-shadow: 0 0 6px rgba(255,184,0,0.4); }
282
+ .trust-bar-fill.low { background: linear-gradient(90deg, #aa0020, var(--red)); box-shadow: 0 0 6px rgba(255,45,85,0.5); }
283
+ .agent-state { font-family: var(--font-mono); font-size: 8px; letter-spacing: 0.15em; color: var(--muted); margin-top: 3px; }
 
 
 
 
284
 
285
+ /* sim metrics */
286
+ .sim-metric-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid rgba(0, 200, 255, 0.06); }
287
+ .sim-metric-row:last-child { border-bottom: none; }
288
+ .sim-metric-label { font-family: var(--font-mono); font-size: 9px; color: var(--muted); letter-spacing: 0.1em; }
289
+ .sim-metric-val { font-family: var(--font-display); font-size: 13px; font-weight: 700; }
290
+ .sim-metric-val.c { color: var(--cyan); }
291
+ .sim-metric-val.g { color: var(--green); }
292
+ .sim-metric-val.r { color: var(--red); }
293
+ .sim-metric-val.a { color: var(--amber); }
 
 
 
 
 
 
 
 
 
 
 
294
 
295
+ /* log entries */
296
+ .log-entry {
297
+ font-family: var(--font-mono); font-size: 9px; line-height: 1.6;
298
+ color: rgba(232, 244, 255, 0.4); margin-bottom: 6px;
299
+ padding-left: 10px; border-left: 2px solid rgba(0, 200, 255, 0.1);
300
+ animation: log-slide 0.3s ease-out;
301
+ }
302
+ @keyframes log-slide { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
303
+ .log-entry.warn { color: rgba(255, 184, 0, 0.7); border-left-color: var(--amber); }
304
+ .log-entry.alert { color: rgba(255, 45, 85, 0.8); border-left-color: var(--red); }
305
+ .log-entry.ok { color: rgba(0, 255, 136, 0.7); border-left-color: var(--green); }
306
+ .log-time { color: var(--muted); margin-right: 6px; }
307
 
308
+ .sim-footer {
309
+ background: rgba(0, 10, 25, 0.8);
310
+ border-top: 1px solid rgba(0, 200, 255, 0.08);
311
+ padding: 10px 20px; display: flex; gap: 32px;
312
+ font-family: var(--font-mono); font-size: 9px; color: var(--muted);
313
+ flex-wrap: wrap;
314
+ }
315
+ .sim-footer span { display: flex; align-items: center; gap: 6px; }
316
+ .sim-footer b { color: var(--cyan); }
 
317
 
318
+ /* sim controls row */
319
+ .sim-controls-row {
320
+ display: flex; gap: 8px; padding: 12px 20px;
321
+ background: rgba(0, 10, 25, 0.6);
322
+ border-top: 1px solid rgba(0, 200, 255, 0.06);
323
+ flex-wrap: wrap; align-items: center;
324
+ }
325
+ .sim-controls-row .ctrl-label {
326
+ font-family: var(--font-mono); font-size: 8px;
327
+ letter-spacing: 0.25em; color: var(--muted); text-transform: uppercase;
328
+ margin-right: 8px;
329
+ }
330
 
331
+ /* ── ARCHITECTURE ── */
332
+ .arch-flow { display: flex; align-items: center; justify-content: center; gap: 0; flex-wrap: nowrap; overflow-x: auto; padding-bottom: 10px; }
333
+ .arch-node {
334
+ min-width: 140px; border: 1px solid var(--node-color, rgba(0, 200, 255, 0.25));
335
+ padding: 20px 16px; background: rgba(0, 20, 50, 0.5);
336
+ position: relative; text-align: center;
337
+ transition: border-color 0.3s, background 0.3s;
338
+ }
339
+ .arch-node::before { content: ''; position: absolute; inset: 0; background: linear-gradient(135deg, rgba(0, 200, 255, 0.03), transparent); }
340
+ .arch-node:hover { background: rgba(0, 200, 255, 0.08); border-color: var(--cyan); }
341
+ .arch-node-id { font-family: var(--font-mono); font-size: 8px; letter-spacing: 0.25em; color: var(--node-color, var(--muted)); margin-bottom: 8px; }
342
+ .arch-node-name { font-family: var(--font-display); font-size: 10px; font-weight: 600; letter-spacing: 0.1em; color: var(--white); margin-bottom: 8px; }
343
+ .arch-node-desc { font-size: 9px; color: var(--muted); line-height: 1.5; }
344
+ .arch-arrow { padding: 0 4px; flex-shrink: 0; display: flex; flex-direction: column; align-items: center; gap: 4px; }
345
+ .arch-arrow-line { width: 40px; height: 1px; background: linear-gradient(90deg, rgba(0, 200, 255, 0.2), rgba(0, 200, 255, 0.6)); position: relative; }
346
+ .arch-arrow-line::after { content: '▶'; position: absolute; right: -5px; top: -5px; font-size: 10px; color: rgba(0, 200, 255, 0.6); }
347
+ .arch-arrow-label { font-family: var(--font-mono); font-size: 8px; color: var(--muted); letter-spacing: 0.15em; white-space: nowrap; }
348
+ .arch-code-loop {
349
+ margin-top: 40px; padding: 24px;
350
+ background: rgba(0, 20, 50, 0.5);
351
+ border: 1px solid rgba(0, 200, 255, 0.1);
352
+ font-family: var(--font-mono); font-size: 11px;
353
+ color: rgba(232, 244, 255, 0.4); line-height: 2;
354
+ }
355
 
356
+ /* ── METRICS ── */
357
+ .metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 2px; }
358
+ .metric-block {
359
+ background: var(--glass); border: 1px solid var(--glass-border);
360
+ padding: 36px 28px; position: relative; overflow: hidden;
361
+ transition: border-color 0.3s;
362
+ }
363
+ .metric-block::after {
364
+ content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 3px;
365
+ background: linear-gradient(90deg, transparent, var(--m-color, var(--cyan)), transparent);
366
+ opacity: 0.5;
367
+ }
368
+ .metric-block:hover { border-color: rgba(0, 200, 255, 0.25); }
369
+ .metric-ref { font-family: var(--font-mono); font-size: 8px; letter-spacing: 0.25em; color: var(--muted); margin-bottom: 20px; }
370
+ .metric-value {
371
+ font-family: var(--font-display); font-size: 52px; font-weight: 900;
372
+ line-height: 1; color: var(--m-color, var(--cyan));
373
+ text-shadow: 0 0 40px rgba(0, 245, 255, 0.25);
374
+ margin-bottom: 8px; letter-spacing: -0.02em;
375
+ }
376
+ .metric-unit { font-family: var(--font-display); font-size: 24px; font-weight: 400; opacity: 0.5; vertical-align: super; }
377
+ .metric-label { font-family: var(--font-display); font-size: 11px; font-weight: 600; letter-spacing: 0.1em; color: var(--white); margin-bottom: 6px; }
378
+ .metric-sub { font-size: 11px; color: var(--muted); line-height: 1.7; }
379
+ .metric-bar-wrap { margin-top: 20px; }
380
+ .metric-bar-bg { height: 2px; background: rgba(255,255,255,0.05); border-radius: 1px; overflow: hidden; }
381
+ .metric-bar-fill { height: 100%; background: var(--m-color, var(--cyan)); border-radius: 1px; box-shadow: 0 0 8px var(--m-color, var(--cyan)); transition: width 1.2s ease; }
382
+ .metric-bar-label { display: flex; justify-content: space-between; font-family: var(--font-mono); font-size: 9px; color: var(--muted); margin-top: 6px; }
383
 
384
+ /* ── FOOTER ── */
385
+ .site-footer {
386
+ background: var(--bg2); border-top: 1px solid rgba(0, 200, 255, 0.08);
387
+ padding: 40px 48px; display: flex; justify-content: space-between;
388
+ align-items: center; flex-wrap: wrap; gap: 20px;
389
+ }
390
+ .footer-left { font-family: var(--font-mono); font-size: 10px; color: var(--muted); }
391
+ .footer-left strong { color: var(--cyan); font-family: var(--font-display); letter-spacing: 0.2em; }
392
+ .footer-right { font-family: var(--font-mono); font-size: 9px; color: rgba(74, 96, 128, 0.5); }
393
 
394
+ /* ── ANIMATIONS ── */
395
+ @keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
396
+ .anim-1 { animation: fadeInUp 0.8s ease both; }
397
+ .anim-2 { animation: fadeInUp 0.8s 0.15s ease both; }
398
+ .anim-3 { animation: fadeInUp 0.8s 0.3s ease both; }
399
+ .anim-4 { animation: fadeInUp 0.8s 0.45s ease both; }
400
+ .anim-5 { animation: fadeInUp 0.8s 0.6s ease both; }
401
 
402
+ /* ── RESPONSIVE ── */
403
+ @media (max-width: 900px) {
404
+ .nav-bar { padding: 12px 20px; }
405
+ .nav-links { display: none; }
406
+ .hero { padding: 100px 20px 60px; }
407
+ .hero-stats { position: static; flex-direction: row; gap: 24px; margin-top: 40px; text-align: left; }
408
+ .section-block { padding: 60px 20px; }
409
+ .sim-body { grid-template-columns: 1fr; }
410
+ .sim-panel { border-right: none; border-bottom: 1px solid rgba(0,200,255,0.08); }
411
+ .sim-panel:last-child { border-left: none; border-top: 1px solid rgba(0,200,255,0.08); }
412
+ .arch-flow { flex-wrap: wrap; justify-content: center; }
413
+ .divider { margin: 0 20px; width: calc(100% - 40px); }
414
+ }
 
 
 
 
ui/app/hooks/useSentinel.ts CHANGED
@@ -92,12 +92,12 @@ export function useSentinel() {
92
 
93
  /* load evaluation data once */
94
  useEffect(() => {
95
- fetch("/assets/evaluation_results.json")
96
  .then((r) => r.json())
97
  .then(setEval)
98
  .catch(() => null);
99
 
100
- fetch("/assets/trained_policy_replay.jsonl")
101
  .then((r) => r.ok ? r.text() : "")
102
  .then((txt) => {
103
  const table = new Map<string, ReplayRow>();
@@ -151,7 +151,7 @@ export function useSentinel() {
151
  const payload = { task_type: t, seed: s };
152
  setLastReq({ method: "POST", path: "/reset", body: payload });
153
  try {
154
- const res = await fetch("/reset", {
155
  method: "POST",
156
  headers: { "Content-Type": "application/json" },
157
  body: JSON.stringify(payload),
@@ -203,7 +203,7 @@ export function useSentinel() {
203
  };
204
  setLastReq({ method: "POST", path: `/step?session_id=${sid}`, body: payload });
205
  try {
206
- const res = await fetch(`/step?session_id=${encodeURIComponent(sid)}`, {
207
  method: "POST",
208
  headers: { "Content-Type": "application/json" },
209
  body: JSON.stringify(payload),
 
92
 
93
  /* load evaluation data once */
94
  useEffect(() => {
95
+ fetch(`${process.env.NEXT_PUBLIC_API_URL}/assets/evaluation_results.json`)
96
  .then((r) => r.json())
97
  .then(setEval)
98
  .catch(() => null);
99
 
100
+ fetch(`${process.env.NEXT_PUBLIC_API_URL}/assets/trained_policy_replay.jsonl`)
101
  .then((r) => r.ok ? r.text() : "")
102
  .then((txt) => {
103
  const table = new Map<string, ReplayRow>();
 
151
  const payload = { task_type: t, seed: s };
152
  setLastReq({ method: "POST", path: "/reset", body: payload });
153
  try {
154
+ const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/reset`, {
155
  method: "POST",
156
  headers: { "Content-Type": "application/json" },
157
  body: JSON.stringify(payload),
 
203
  };
204
  setLastReq({ method: "POST", path: `/step?session_id=${sid}`, body: payload });
205
  try {
206
+ const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/step?session_id=${encodeURIComponent(sid)}`, {
207
  method: "POST",
208
  headers: { "Content-Type": "application/json" },
209
  body: JSON.stringify(payload),
ui/app/page.tsx CHANGED
@@ -1,54 +1,236 @@
1
  "use client";
2
 
3
- import { useEffect } from "react";
4
  import { useSentinel } from "./hooks/useSentinel";
5
- import Landing from "./components/Landing";
6
- import MissionControl from "./components/MissionControl";
7
- import JudgeWizard from "./components/JudgeWizard";
8
- import type { ViewMode, TaskType } from "./lib/types";
9
-
10
- const TABS: { id: ViewMode; label: string }[] = [
11
- { id: "landing", label: "Overview" },
12
- { id: "mission", label: "Mission Control" },
13
- { id: "judge", label: "Judge Demo" },
14
- ];
15
-
16
- const TASKS: { value: TaskType; label: string }[] = [
17
- { value: "task1", label: "Task 1" },
18
- { value: "task2", label: "Task 2" },
19
- { value: "task3", label: "Task 3" },
20
- ];
21
 
22
  export default function Page() {
23
  const s = useSentinel();
24
 
25
- /* auto-reset on first mount */
26
  useEffect(() => {
27
  void s.resetEpisode();
28
  // eslint-disable-next-line react-hooks/exhaustive-deps
29
  }, []);
30
 
 
 
 
 
 
 
 
 
 
31
  return (
32
- <div className="shell">
33
- <div className="grid-bg" />
34
-
35
- {/* header */}
36
- <header className="hdr">
37
- <span className="hdr-brand">SENTINEL</span>
38
- <nav className="hdr-nav">
39
- {TABS.map((t) => (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  <button
41
- key={t.id}
42
- className={s.view === t.id ? "on" : ""}
43
- onClick={() => s.setView(t.id)}
 
 
 
 
 
 
 
 
 
 
 
44
  >
45
- {t.label}
46
  </button>
47
- ))}
48
- </nav>
49
- <div className="hdr-controls">
50
- <div className="hdr-pill">
51
- <label>Task</label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  <select
53
  value={s.taskType}
54
  onChange={(e) => {
@@ -56,71 +238,58 @@ export default function Page() {
56
  s.setTaskType(next);
57
  void s.resetEpisode(next, s.seed);
58
  }}
 
 
 
 
 
59
  >
60
- {TASKS.map((t) => (
61
- <option key={t.value} value={t.value}>{t.label}</option>
62
- ))}
63
  </select>
64
  </div>
65
- <div className="hdr-pill">
66
- <label>Seed</label>
67
- <input
68
- type="number"
69
- value={s.seed}
70
- onChange={(e) => s.setSeed(Number(e.target.value || 0))}
71
- />
72
- </div>
73
- <button className="btn" onClick={() => void s.resetEpisode()}>
74
- Reset
75
- </button>
76
- <button className="btn btn-ghost" onClick={() => void s.swapProfiles()}>
77
- Swap Profiles
78
- </button>
79
  </div>
80
- </header>
81
-
82
- {/* content */}
83
- <main className="main">
84
- {s.view === "landing" && (
85
- <Landing
86
- proof={s.proof}
87
- onEnterMission={() => s.setView("mission")}
88
- onEnterJudge={() => s.setView("judge")}
89
- />
90
- )}
91
-
92
- {s.view === "mission" && (
93
- <MissionControl
94
- observation={s.observation}
95
- trustDeltas={s.trustDeltas}
96
- activeSpec={s.activeSpec}
97
- recommended={s.recommended}
98
- score={s.info?.score}
99
- detections={s.info?.adversarial_detections}
100
- poisonings={s.info?.adversarial_poisonings}
101
- events={s.events}
102
- running={s.running}
103
- done={s.done}
104
- lastReq={s.lastReq}
105
- lastRes={s.lastRes}
106
- onStep={(action) => void s.stepEpisode(action)}
107
- onAutoRun={(policy) => void s.autoRun(policy)}
108
- onStop={s.stopAutoRun}
109
- />
110
- )}
111
-
112
- {s.view === "judge" && (
113
- <JudgeWizard
114
- autoRun={s.autoRun}
115
- resetEpisode={s.resetEpisode}
116
- swapProfiles={s.swapProfiles}
117
- observation={s.observation}
118
- events={s.events}
119
- info={s.info}
120
- running={s.running}
121
- />
122
- )}
123
- </main>
124
- </div>
125
  );
126
  }
 
1
  "use client";
2
 
3
+ import { useEffect, useMemo } from "react";
4
  import { useSentinel } from "./hooks/useSentinel";
5
+ import HeroCanvas from "./components/HeroCanvas";
6
+ import SystemModules from "./components/SystemModules";
7
+ import AgentTrustMonitor from "./components/AgentTrustMonitor";
8
+ import SimCanvas from "./components/SimCanvas";
9
+ import ExecutionLog from "./components/ExecutionLog";
10
+ import ArchitecturePipeline from "./components/ArchitecturePipeline";
11
+ import MetricsGrid from "./components/MetricsGrid";
12
+ import type { TaskType, AutoPolicy } from "./lib/types";
 
 
 
 
 
 
 
 
13
 
14
  export default function Page() {
15
  const s = useSentinel();
16
 
 
17
  useEffect(() => {
18
  void s.resetEpisode();
19
  // eslint-disable-next-line react-hooks/exhaustive-deps
20
  }, []);
21
 
22
+ const adversarialAgents = useMemo(() => new Set(
23
+ s.events
24
+ .filter(e => e.outcome === "poisoned" || e.outcome === "blocked")
25
+ .map(e => e.specialist)
26
+ .filter((x): x is string => !!x)
27
+ ), [s.events]);
28
+
29
+ const totalReward = s.info?.total_reward ?? 0;
30
+
31
  return (
32
+ <>
33
+ {/* NAV */}
34
+ <nav className="nav-bar">
35
+ <div className="nav-logo">
36
+ <div className="nav-logo-dot" />
37
+ SENTINEL
38
+ </div>
39
+ <ul className="nav-links">
40
+ <li><a href="#overview">MODULES</a></li>
41
+ <li><a href="#simulation">SIMULATION</a></li>
42
+ <li><a href="#architecture">ARCHITECTURE</a></li>
43
+ <li><a href="#metrics">METRICS</a></li>
44
+ </ul>
45
+ <div className={`nav-badge ${s.running ? "running" : s.done ? "complete" : ""}`}>
46
+ <div className="nav-badge-dot" />
47
+ {s.running ? "RUNNING" : s.done ? "COMPLETE" : "SYSTEM ONLINE"}
48
+ </div>
49
+ </nav>
50
+
51
+ {/* HERO */}
52
+ <section className="hero" id="hero">
53
+ <HeroCanvas />
54
+ <div className="hero-scanline" />
55
+
56
+ <div className="hero-content">
57
+ <div className="hero-tag anim-1">
58
+ SYS.CORE // SENTINEL v2.4.1 // MARL FRAMEWORK
59
+ </div>
60
+ <h1 className="anim-2">
61
+ Train AI to <span className="accent">Trust</span> —<br />
62
+ and Survive <span className="accent-red">Adversaries</span>
63
+ </h1>
64
+ <p className="hero-sub anim-3">
65
+ A multi-agent reinforcement learning system where an orchestrator
66
+ learns to detect deception, assign trust, and optimize decisions in
67
+ real-time adversarial environments.
68
+ </p>
69
+ <div className="hero-ctas anim-4">
70
  <button
71
+ className="btn-primary"
72
+ onClick={() => {
73
+ document.getElementById("simulation")?.scrollIntoView({ behavior: "smooth" });
74
+ setTimeout(() => {
75
+ void s.resetEpisode().then(() => s.autoRun("trained" as AutoPolicy));
76
+ }, 400);
77
+ }}
78
+ disabled={s.running}
79
+ >
80
+ ▶ Launch Simulation
81
+ </button>
82
+ <button
83
+ className="btn-secondary"
84
+ onClick={() => document.getElementById("architecture")?.scrollIntoView({ behavior: "smooth" })}
85
  >
86
+ View System Architecture
87
  </button>
88
+ </div>
89
+ </div>
90
+
91
+ <div className="hero-stats anim-5">
92
+ <div className="hero-stat-item">
93
+ <div className="hero-stat-num">{s.observation?.available_specialists?.length ?? 5}</div>
94
+ <div className="hero-stat-label">Active Agents</div>
95
+ </div>
96
+ <div className="hero-stat-item">
97
+ <div className="hero-stat-num">
98
+ {s.proof?.trained
99
+ ? Math.round(s.proof.trained.avg_detection_rate * 100)
100
+ : 92
101
+ }<span style={{ fontSize: 14, opacity: 0.4 }}>%</span>
102
+ </div>
103
+ <div className="hero-stat-label">Trust Accuracy</div>
104
+ </div>
105
+ <div className="hero-stat-item">
106
+ <div className="hero-stat-num">
107
+ {s.proof?.trained
108
+ ? s.proof.trained.avg_score.toFixed(2)
109
+ : "0.91"
110
+ }
111
+ </div>
112
+ <div className="hero-stat-label">Avg Score</div>
113
+ </div>
114
+ </div>
115
+ </section>
116
+
117
+ <div className="divider" />
118
+
119
+ {/* SYSTEM MODULES */}
120
+ <section className="section-block alt-bg" id="overview">
121
+ <div className="section-label">01 // SYSTEM MODULES</div>
122
+ <h2 className="section-title">Core Architecture</h2>
123
+ <p className="section-desc">
124
+ Each module operates as an independent inference layer within the
125
+ trust-calibration pipeline. All components communicate via the
126
+ orchestration bus.
127
+ </p>
128
+ <SystemModules
129
+ running={s.running}
130
+ done={s.done}
131
+ adversarialCount={adversarialAgents.size}
132
+ />
133
+ </section>
134
+
135
+ <div className="divider" />
136
+
137
+ {/* SIMULATION */}
138
+ <section className="section-block" id="simulation">
139
+ <div className="section-label">02 // LIVE PREVIEW</div>
140
+ <h2 className="section-title">Simulation Control Panel</h2>
141
+ <p className="section-desc">
142
+ Real-time orchestrator view. Agent trust scores update per-step. Red
143
+ indicates flagged adversarial behaviour.
144
+ </p>
145
+
146
+ <div className="sim-wrapper">
147
+ <div className="sim-topbar">
148
+ <div className="sim-dots">
149
+ <div className="sim-dot r" />
150
+ <div className="sim-dot y" />
151
+ <div className="sim-dot g" />
152
+ </div>
153
+ <div className="sim-topbar-title">
154
+ SENTINEL // ORCHESTRATOR VIEW // TASK: {s.taskType?.toUpperCase() ?? "TASK3"} // STEP: {s.observation?.step_count ?? 0}
155
+ </div>
156
+ <div className="sim-topbar-badge">
157
+ ● {s.running ? "LIVE" : s.done ? "DONE" : "READY"}
158
+ </div>
159
+ </div>
160
+
161
+ <div className="sim-body">
162
+ {/* LEFT: AGENTS */}
163
+ <AgentTrustMonitor
164
+ observation={s.observation}
165
+ trustDeltas={s.trustDeltas}
166
+ activeSpec={s.activeSpec}
167
+ events={s.events}
168
+ running={s.running}
169
+ totalReward={totalReward}
170
+ />
171
+
172
+ {/* CENTER: CANVAS */}
173
+ <div className="sim-center">
174
+ <SimCanvas
175
+ trustSnapshot={s.observation?.trust_snapshot ?? {}}
176
+ adversarialAgents={adversarialAgents}
177
+ activeSpec={s.activeSpec}
178
+ />
179
+ </div>
180
+
181
+ {/* RIGHT: LOGS */}
182
+ <ExecutionLog
183
+ events={s.events}
184
+ observation={s.observation}
185
+ info={s.info}
186
+ />
187
+ </div>
188
+
189
+ {/* Controls Row */}
190
+ <div className="sim-controls-row">
191
+ <span className="ctrl-label">POLICY:</span>
192
+ <button className="btn-sm-ctrl" onClick={() => void s.autoRun("heuristic" as AutoPolicy)} disabled={s.running || s.done}>
193
+ ▶ HEURISTIC
194
+ </button>
195
+ <button className="btn-sm-ctrl" onClick={() => void s.autoRun("random" as AutoPolicy)} disabled={s.running || s.done}>
196
+ ⚄ RANDOM
197
+ </button>
198
+ <button className="btn-sm-ctrl" onClick={() => void s.autoRun("trained" as AutoPolicy)} disabled={s.running || s.done}>
199
+ 🧠 TRAINED RL
200
+ </button>
201
+ {s.running && (
202
+ <button className="btn-sm-ctrl danger" onClick={s.stopAutoRun}>
203
+ ■ STOP
204
+ </button>
205
+ )}
206
+ <span className="ctrl-label" style={{ marginLeft: "auto" }}>ACTIONS:</span>
207
+ <button className="btn-sm-ctrl" onClick={() => void s.stepEpisode("delegate")} disabled={s.running || s.done}>
208
+ DELEGATE
209
+ </button>
210
+ <button className="btn-sm-ctrl" onClick={() => void s.stepEpisode("verify")} disabled={s.running || s.done}>
211
+ VERIFY
212
+ </button>
213
+ <button className="btn-sm-ctrl" onClick={() => void s.stepEpisode("skip")} disabled={s.running || s.done}>
214
+ SKIP
215
+ </button>
216
+ </div>
217
+
218
+ {/* Sim Footer */}
219
+ <div className="sim-footer">
220
+ <span>TASK: <b>{s.taskType?.toUpperCase() ?? "TASK3"}</b></span>
221
+ <span>SEED: <b>{s.seed}</b></span>
222
+ <span>ALGO: <b>DQN+TCE</b></span>
223
+ <span>SESSION: <b>{s.sessionId?.slice(0, 8) ?? "—"}</b></span>
224
+ <span style={{ marginLeft: "auto" }}>
225
+ <button
226
+ className="btn-sm-ctrl"
227
+ onClick={() => void s.resetEpisode()}
228
+ disabled={s.running}
229
+ style={{ fontSize: 8 }}
230
+ >
231
+ ⟳ RESET EPISODE
232
+ </button>
233
+ </span>
234
  <select
235
  value={s.taskType}
236
  onChange={(e) => {
 
238
  s.setTaskType(next);
239
  void s.resetEpisode(next, s.seed);
240
  }}
241
+ style={{
242
+ background: "transparent", border: "1px solid rgba(0,200,255,0.2)",
243
+ color: "var(--cyan)", fontFamily: "var(--font-mono)", fontSize: 9,
244
+ padding: "2px 6px", cursor: "pointer",
245
+ }}
246
  >
247
+ <option value="task1" style={{ background: "var(--bg)" }}>TASK 1</option>
248
+ <option value="task2" style={{ background: "var(--bg)" }}>TASK 2</option>
249
+ <option value="task3" style={{ background: "var(--bg)" }}>TASK 3</option>
250
  </select>
251
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  </div>
253
+ </section>
254
+
255
+ <div className="divider" />
256
+
257
+ {/* ARCHITECTURE */}
258
+ <section className="section-block alt-bg" id="architecture">
259
+ <div className="section-label">03 // SYSTEM DESIGN</div>
260
+ <h2 className="section-title">Execution Pipeline</h2>
261
+ <p className="section-desc">
262
+ Data flows unidirectionally through the trust-calibrated RL loop. Each
263
+ stage emits telemetry to the monitoring bus.
264
+ </p>
265
+ <ArchitecturePipeline />
266
+ </section>
267
+
268
+ <div className="divider" />
269
+
270
+ {/* METRICS */}
271
+ <section className="section-block" id="metrics">
272
+ <div className="section-label">04 // EVALUATION RESULTS</div>
273
+ <h2 className="section-title">Experimental Benchmarks</h2>
274
+ <p className="section-desc">
275
+ Averaged across evaluation episodes. Adversarial injection ratio fixed at
276
+ 20%. Baseline: naive averaging orchestrator without trust calibration.
277
+ </p>
278
+ <MetricsGrid proof={s.proof} />
279
+ </section>
280
+
281
+ {/* FOOTER */}
282
+ <footer className="site-footer">
283
+ <div className="footer-left">
284
+ <strong>SENTINEL</strong><br />
285
+ Multi-Agent Reinforcement Learning System<br />
286
+ Research prototype — not for production use.
287
+ </div>
288
+ <div className="footer-right">
289
+ BUILD 2.4.1 // MARL-FRAMEWORK // MIT LICENSE<br />
290
+ © 2025 SENTINEL LAB. ALL RIGHTS RESERVED.
291
+ </div>
292
+ </footer>
293
+ </>
 
 
 
 
294
  );
295
  }