2 Commits

Author SHA1 Message Date
MrSphay
a1d415e449 Revert "Merge pull request 'feat(ui): redesign dashboard in app-style shell' (#42) from codex/modrinth-app-redesign into codex/production-intelligence-terminal"
All checks were successful
Build / test-and-image (pull_request) Successful in 25s
Codex Template Compliance / template-compliance (pull_request) Successful in 4s
This reverts commit 9f2083a324, reversing
changes made to 1c2b48f588.
2026-05-17 22:35:12 +02:00
MrSphay
0f5f9c5f91 Revert "Merge pull request 'feat(ui): finalize app-style dashboard motion and QA' (#43) from codex/modrinth-app-finalization into codex/production-intelligence-terminal"
This reverts commit 096544f6e6, reversing
changes made to 9f2083a324.
2026-05-17 22:35:07 +02:00
5 changed files with 55 additions and 480 deletions

View File

@@ -2,7 +2,7 @@
# Intelligence Terminal
**Modrinth-app-inspired operator dashboard. 27 open sources. Docker-first. No telemetry.**
**Self-hosted intelligence dashboard. 27 open sources. Docker-first. No telemetry.**
[![Gitea Repository](https://img.shields.io/badge/source-Gitea-609926?style=for-the-badge&logo=gitea&logoColor=white)](https://git.wilkensxl.de/MrSphay/intelligence-terminal)
[![Docker Image](https://img.shields.io/badge/image-Gitea%20Registry-0b1220?style=for-the-badge&logo=docker&logoColor=white)](https://git.wilkensxl.de/MrSphay/-/packages/container/intelligence-terminal/latest)
@@ -34,10 +34,8 @@
> Runtime data stays in your configured `runs/` volume and API keys are operator-owned.
> **Source:** [git.wilkensxl.de/MrSphay/intelligence-terminal](https://git.wilkensxl.de/MrSphay/intelligence-terminal)
> Pull the image or clone the repository to run Intelligence Terminal on your own infrastructure.
>
> **Design transparency:** the dashboard is inspired by app-style marketplace UX patterns, especially dark desktop app shells with a strong left navigation. It does not use Modrinth branding, logos, or assets and is not affiliated with Modrinth.
Intelligence Terminal pulls satellite fire detection, flight tracking, radiation monitoring, satellite constellation tracking, economic indicators, live market prices, conflict data, sanctions lists, and social sentiment from 27 open-source intelligence feeds in parallel, every 15 minutes, and renders everything in a dark, app-style operator workspace.
Intelligence Terminal pulls satellite fire detection, flight tracking, radiation monitoring, satellite constellation tracking, economic indicators, live market prices, conflict data, sanctions lists, and social sentiment from 27 open-source intelligence feeds in parallel, every 15 minutes, and renders everything on a single self-contained dashboard.
Hook it up to an LLM and it becomes a **two-way intelligence assistant**: pushing multi-tier alerts to Telegram and Discord when something meaningful changes, responding to commands like `/brief` and `/sweep` from your phone, and generating trade ideas grounded in real cross-domain data.
@@ -303,21 +301,26 @@ Gitea Actions publishes the same image automatically when the repository secret
## What You Get
### Live Dashboard
A self-contained app-style operator dashboard with dark mode by default, a large left navigation rail, rounded content surfaces, and view-specific panels:
- **Home** - sweep status, alert posture, latest feed, source count, macro summary, and high-level signal state
- **Worldview** - Globe.gl 3D globe or flat map, regional focus controls, 9 marker types, flight corridors, and layer focus/hide controls
- **Sources** - sensor grid, source health, API-key degradation signals, and transparent partial-data states
- **Signals** - cross-source signals, sweep delta, scenario watchlist, OSINT feed, and escalation/de-escalation context
- **Markets** - live indexes, crypto, energy, commodities, VIX, high-yield spread, supply-chain pressure, and LLM-assisted ideas
- **Ops** - browser-triggered terminal actions, performance mode, Telegram/Discord operator workflows, and system status
The UI keeps the existing operational features: `/api/data`, SSE live refresh, globe/flat map mode, layer focus/hide, terminal actions, low-performance mode, LLM output, Telegram and Discord alerting, and scenario watchlist data.
A self-contained Jarvis-style HUD with:
- **3D WebGL globe** (Globe.gl) with atmosphere glow, star field, and smooth rotation — plus a classic flat map toggle
- **9 marker types** across both views: fire detections, air traffic, radiation sites, maritime chokepoints, SDR receivers, OSINT events, health alerts, geolocated news, conflict events
- **Animated 3D flight corridor arcs** between air traffic hotspots and global hubs
- **Region filters** (World, Americas, Europe, Middle East, Asia Pacific, Africa) — rotates the globe or zooms the flat map
- **Live market data** — indexes, crypto, energy, commodities via Yahoo Finance (no API key needed)
- **Risk gauges** — VIX, high-yield spread, supply chain pressure index
- **OSINT feed** — English-language posts from 17 Telegram intelligence channels (expandable)
- **News ticker** — merged RSS + GDELT headlines + Telegram posts, auto-scrolling
- **Sweep delta** — live panel showing what changed since last sweep (new signals, escalations, de-escalations with severity)
- **Cross-source signals** — correlated intelligence across satellite, economic, conflict, and social domains
- **Nuclear watch** — real-time radiation readings from Safecast + EPA RadNet
- **Space watch** — CelesTrak satellite tracking: recent launches, ISS, military constellations, Starlink/OneWeb counts
- **Leverageable ideas** — AI-generated trade ideas (with LLM) or signal-correlated ideas (without)
### Performance Modes
The `VISUALS FULL` / `VISUALS LITE` button in the top bar only changes rendering behavior - it does **not** remove data sources or reduce sweep coverage.
When you switch to **VISUALS LITE**, the dashboard:
- Disables decorative background effects such as radial and grid overlays
- Disables decorative background effects such as the radial/grid overlays and scanlines
- Removes expensive blur/backdrop-filter effects on panels and overlays
- Stops non-essential animations like the logo ring blink, conflict rings, and corridor flow effects
- Disables globe auto-rotation and turns off animated flight-arc dashes
@@ -505,7 +508,7 @@ intelligence-terminal/
├── dashboard/
│ ├── inject.mjs # Data synthesis + standalone HTML injection
│ └── public/
│ └── jarvis.html # Self-contained app-style operator dashboard
│ └── jarvis.html # Self-contained Jarvis HUD
├── lib/
│ ├── llm/ # LLM abstraction (8 providers, raw fetch, no SDKs)
@@ -644,7 +647,7 @@ When running `npm run dev`:
| Endpoint | Description |
|----------|-------------|
| `GET /` | App-style operator dashboard |
| `GET /` | Jarvis HUD dashboard |
| `GET /api/data` | Current synthesized intelligence data (JSON) |
| `GET /api/health` | Server status, uptime, source count, LLM status |
| `GET /events` | SSE stream for live push updates |
@@ -723,17 +726,16 @@ Check these in order:
## Screenshots
The `docs/` folder contains dashboard screenshots referenced by this README. The hero screenshot has been refreshed for the app-style shell; regenerate the supporting map/globe images from a running instance when those views materially change.
The `docs/` folder contains dashboard screenshots referenced by this README:
| File | Description |
|------|-------------|
| `docs/dashboard.png` | Full operator dashboard - hero image at the top of this README |
| `docs/design/modrinth-app-final-concept.png` | Final app-style design reference used for the current shell |
| `docs/boot.png` | Boot sequence animation |
| `docs/map.png` | Worldview map with marker types and flight arcs |
| `docs/dashboard.png` | Full dashboard hero image at the top of this README |
| `docs/boot.png` | Cinematic boot sequence animation |
| `docs/map.png` | D3 world map with marker types and flight arcs |
| `docs/globe.png` | 3D WebGL globe view with atmosphere glow and markers |
For a fresh capture, run the dashboard, wait for a sweep to complete, then use your browser's DevTools (`F12` -> `Ctrl+Shift+P` -> "Capture full size screenshot") or a tool like [LICEcap](https://www.cockos.com/licecap/) for GIFs.
To update them: run the dashboard, wait for a sweep to complete, then use your browser's DevTools (`F12` `Ctrl+Shift+P` "Capture full size screenshot") or a tool like [LICEcap](https://www.cockos.com/licecap/) for GIFs.
---

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

View File

@@ -3,9 +3,9 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Intelligence Terminal</title>
<title>CRUCIX — Intelligence Terminal</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;600;700&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;600;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://d3js.org/topojson.v3.min.js"></script>
@@ -17,7 +17,7 @@
--bg:#020408;--panel:rgba(6,14,22,0.82);--glass:rgba(10,20,32,0.55);
--border:rgba(100,240,200,0.12);--border-bright:rgba(100,240,200,0.3);
--text:#e8f4f0;--dim:#6a8a82;--accent:#64f0c8;--accent2:#44ccff;
--warn:#ffb84c;--danger:#ff5f63;--mono:'IBM Plex Mono',monospace;--sans:'Inter',system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
--warn:#ffb84c;--danger:#ff5f63;--mono:'IBM Plex Mono',monospace;--sans:'Space Grotesk',sans-serif;
}
html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--sans);overflow-x:hidden}
.bg-grid{position:fixed;inset:0;pointer-events:none;opacity:0;
@@ -25,6 +25,10 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
background-size:60px 60px;mask-image:radial-gradient(ellipse at 50% 30%,black 20%,transparent 70%)}
.bg-radial{position:fixed;inset:0;pointer-events:none;opacity:0;
background:radial-gradient(ellipse at 50% 0%,rgba(40,120,100,0.15),transparent 50%),radial-gradient(ellipse at 80% 20%,rgba(40,100,180,0.08),transparent 40%)}
.scanline{position:fixed;inset:0;pointer-events:none;overflow:hidden;opacity:0}
.scanline::after{content:'';position:absolute;left:0;width:100%;height:2px;background:linear-gradient(90deg,transparent,rgba(100,240,200,0.12),transparent);animation:scanMove 4s linear infinite}
@keyframes scanMove{0%{top:-2px}100%{top:100%}}
/* BOOT */
#boot{position:fixed;inset:0;z-index:1000;display:flex;flex-direction:column;align-items:center;justify-content:center;background:var(--bg)}
.logo-ring{width:120px;height:120px;border:2px solid var(--border);border-radius:50%;display:flex;align-items:center;justify-content:center;position:relative;opacity:0}
@@ -270,7 +274,7 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
.ideas-src.static{color:var(--dim);border-color:rgba(255,255,255,0.1);background:rgba(255,255,255,0.03)}
/* LOW PERFORMANCE MODE */
body.low-perf .bg-grid,body.low-perf .bg-radial{display:none!important}
body.low-perf .bg-grid,body.low-perf .bg-radial,body.low-perf .scanline{display:none!important}
body.low-perf .topbar,body.low-perf .g-panel,body.low-perf .map-popup,body.low-perf .map-loading{backdrop-filter:none!important}
body.low-perf .logo-ring::before,body.low-perf .logo-ring::after,body.low-perf .regime-chip .blink,body.low-perf .conflict-ring,body.low-perf .corridor-flow{animation:none!important}
body.low-perf .ticker-wrap{overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(100,240,200,0.2) transparent}
@@ -331,298 +335,20 @@ body.low-perf .ticker-wrap::-webkit-scrollbar-thumb{background:rgba(100,240,200,
/* IDEA HORIZON BADGE */
.idea-horizon{font-family:var(--mono);font-size:8px;letter-spacing:0.08em;text-transform:uppercase;padding:1px 5px;border:1px solid rgba(100,240,200,0.15);color:var(--dim);margin-left:6px}
/* APP-STYLE REDESIGN */
:root{
--app-bg:#17191f;
--app-surface:#22252d;
--app-surface-2:#2b2f38;
--app-card:#2a2d35;
--app-card-soft:#242830;
--app-border:#3a3f49;
--app-border-soft:#343942;
--app-text:#f3f5f7;
--app-muted:#aeb6c2;
--app-dim:#77818f;
--app-green:#1bd96a;
--app-green-soft:rgba(27,217,106,0.17);
--app-blue:#5fb4ff;
--app-red:#ff5f6f;
--app-yellow:#ffcc66;
}
body[data-theme="dark"]{
--bg:var(--app-bg);
--panel:var(--app-surface);
--glass:var(--app-card);
--border:var(--app-border);
--border-bright:rgba(27,217,106,0.55);
--text:var(--app-text);
--dim:var(--app-muted);
--accent:var(--app-green);
--accent2:var(--app-blue);
background:var(--app-bg);
}
body[data-theme="light"]{
--bg:#eef1f4;
--panel:#ffffff;
--glass:#ffffff;
--border:#d6dde5;
--border-bright:rgba(18,177,88,0.55);
--text:#161a21;
--dim:#5e6875;
--accent:#11b858;
--accent2:#2677c9;
--app-bg:#eef1f4;
--app-surface:#ffffff;
--app-surface-2:#f5f7fa;
--app-card:#ffffff;
--app-card-soft:#f3f6f9;
--app-border:#d6dde5;
--app-border-soft:#e1e7ee;
--app-text:#161a21;
--app-muted:#5e6875;
--app-dim:#788391;
color:#161a21;
}
body{font-family:var(--sans);letter-spacing:0;background:var(--app-bg)}
.bg-grid,.bg-radial{display:none}
#main.app-root{display:grid;grid-template-columns:112px minmax(0,1fr);gap:18px;min-height:100vh;padding:0;background:var(--app-bg)}
.app-sidebar{position:sticky;top:0;height:100vh;padding:26px 18px 24px;border-right:1px solid rgba(255,255,255,0.05);background:#20232b;display:flex;flex-direction:column;align-items:center;gap:28px}
body[data-theme="light"] .app-sidebar{background:#f8fafc;border-right-color:#dde3ea}
.app-brand-mark{width:54px;height:54px;border:3px solid var(--app-green);border-radius:18px;display:grid;place-items:center;color:var(--app-green);font-weight:800;font-size:19px;letter-spacing:0;background:rgba(27,217,106,0.06)}
.app-brand-mark span{line-height:1}
.app-nav{display:flex;flex-direction:column;gap:12px;width:100%;align-items:center}
.nav-item{width:74px;height:74px;border:0;border-radius:999px;background:transparent;color:var(--app-muted);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;cursor:pointer;transition:background .18s ease,color .18s ease,transform .18s ease}
.nav-item span{width:28px;height:28px;display:grid;place-items:center;font-weight:800;font-size:17px;border:2px solid currentColor;border-radius:10px;line-height:1}
.nav-item small{font-size:9px;font-weight:700;letter-spacing:0;text-transform:none}
.nav-item:hover{background:rgba(255,255,255,0.06);color:var(--app-text);transform:translateY(-1px)}
.nav-item.active{background:rgba(27,217,106,0.22);color:var(--app-green)}
.sidebar-status{margin-top:auto;width:74px;min-height:42px;border-top:1px solid var(--app-border);padding-top:14px;display:flex;align-items:center;justify-content:center;gap:7px;color:var(--app-muted);font-size:11px;font-weight:700}
.sidebar-status-dot{width:8px;height:8px;border-radius:50%;background:var(--app-green);box-shadow:0 0 16px rgba(27,217,106,.5)}
.app-shell{min-width:0;margin:24px 24px 24px 0;border:1px solid var(--app-border);border-radius:30px 0 0 30px;background:#181b21;overflow:hidden;box-shadow:0 24px 70px rgba(0,0,0,.26)}
body[data-theme="light"] .app-shell{background:#eef1f4;box-shadow:0 20px 50px rgba(33,43,54,.12)}
.topbar{border:0;border-bottom:1px solid var(--app-border);border-radius:0;background:var(--app-surface);padding:26px 36px;backdrop-filter:none;display:grid;grid-template-columns:minmax(220px,340px) minmax(240px,1fr);gap:18px;align-items:center}
.top-left,.top-center,.top-right{width:auto}
.top-left{align-items:flex-start;flex-direction:column;gap:6px}
.brand{font-family:var(--sans);font-size:34px;font-weight:800;letter-spacing:0;text-transform:none;color:var(--app-text);line-height:1}
.view-subtitle{font-size:14px;line-height:1.4;color:var(--app-muted);font-weight:600}
.regime-chip{border:0;border-radius:999px;background:var(--app-green-soft);color:var(--app-green);font-family:var(--sans);font-size:12px;letter-spacing:0;text-transform:none;font-weight:800;padding:7px 12px}
.regime-chip .blink{background:var(--app-green);box-shadow:0 0 10px rgba(27,217,106,.6)}
.app-search{height:46px;border:1px solid var(--app-border);border-radius:999px;background:var(--app-card-soft);color:var(--app-dim);display:flex;align-items:center;padding:0 18px;font-size:14px;font-weight:600;min-width:0}
.app-search span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.top-right{grid-column:1/-1;justify-content:flex-start;flex-wrap:wrap}
@media(min-width:1240px){.topbar{grid-template-columns:minmax(260px,1fr) minmax(260px,420px) auto}.top-right{grid-column:auto;justify-content:flex-end}}
.meta-pill,.guide-btn,.alert-badge,.perf-pill{border:1px solid var(--app-border);border-radius:999px;background:var(--app-card-soft);color:var(--app-muted);font-family:var(--sans);font-size:12px;letter-spacing:0;text-transform:none;font-weight:700;padding:8px 12px}
.meta-pill .v{color:var(--app-text)}
.guide-btn{color:var(--app-blue)}
.alert-badge{border-color:rgba(255,95,111,.35);background:rgba(255,95,111,.1);color:#ff9da8}
.theme-switch{display:flex;align-items:center;gap:4px;padding:4px;border:1px solid var(--app-border);border-radius:999px;background:var(--app-card-soft)}
.theme-btn{border:0;border-radius:999px;background:transparent;color:var(--app-muted);font:700 11px var(--sans);padding:7px 10px;cursor:pointer}
.theme-btn.active{background:var(--app-green);color:#06120b}
.grid{display:grid;margin:0;padding:24px;grid-template-columns:300px minmax(0,1fr);gap:16px;min-height:calc(100vh - 122px);background:#181b21}
body[data-theme="light"] .grid{background:#eef1f4}
.col{gap:16px;min-width:0}
#leftRail,#centerCol,#rightRail{order:0}
#rightRail{grid-column:1/-1;display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:16px}
@media(min-width:1320px){.grid{grid-template-columns:280px minmax(0,1fr) 360px}#rightRail{grid-column:auto;display:flex;flex-direction:column}}
.g-panel,.map-region-bar,.map-container,.glossary-panel{border:1px solid var(--app-border);border-radius:18px;background:var(--app-card);box-shadow:none;backdrop-filter:none}
.g-panel{padding:18px;overflow:hidden}
.g-panel::before{display:none}
.sec-head{margin-bottom:14px;gap:10px}
.sec-head h3{font-family:var(--sans);font-size:17px;font-weight:800;letter-spacing:0;text-transform:none;color:var(--app-text)}
.badge{border:1px solid var(--app-border);border-radius:999px;background:var(--app-card-soft);font-family:var(--sans);font-size:11px;font-weight:800;color:var(--app-green);padding:4px 9px}
.layer-item,.site-row,.econ-row,.src-item,.mc,.signal-row,.sm,.idea-card,.ic,.tk-card{border-color:var(--app-border-soft);border-radius:12px;background:var(--app-card-soft)}
.layer-item{margin-bottom:8px;padding:11px}
.layer-item.focused{border-color:var(--app-green);background:var(--app-green-soft)}
.layer-name,.idea-title{font-weight:800;color:var(--app-text)}
.layer-sub,.layer-mode,.idea-text,.ic .ic-t,.tk-head{color:var(--app-muted)}
.layer-count,.site-val,.eval,.sm .smv{color:var(--app-green)}
.mini-btn,.action-btn,.region-btn,.map-ctrl-btn,.proj-toggle,.glossary-close{border:1px solid var(--app-border);border-radius:10px;background:var(--app-card-soft);font-family:var(--sans);letter-spacing:0;color:var(--app-muted)}
.action-grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
.action-btn{font-size:12px;font-weight:800;padding:10px 8px}
.action-btn:hover,.mini-btn:hover,.region-btn:hover{border-color:var(--app-green);color:var(--app-green);background:var(--app-green-soft)}
.terminal-output{border:1px solid var(--app-border);border-radius:14px;background:#151820;color:var(--app-muted)}
body[data-theme="light"] .terminal-output{background:#f6f8fb}
.map-region-bar{padding:12px;gap:8px;margin-bottom:0}
.region-btn{font-weight:800;text-transform:none;font-size:12px;padding:8px 12px}
.region-btn.active{background:var(--app-green);color:#06120b;border-color:var(--app-green)}
.map-container{min-height:590px;background:#101319}
body[data-theme="light"] .map-container{background:#e7ecf3}
.map-legend{border:1px solid var(--app-border);border-radius:14px;background:rgba(24,27,33,.86);padding:9px 11px;backdrop-filter:blur(8px)}
.map-hint,.map-hint-id{top:14px;right:18px;color:var(--app-muted);letter-spacing:0;font-family:var(--sans);font-weight:700}
.lower{gap:16px;margin-top:16px}
.lower .lp-ticker{flex:1.2 1 300px;max-width:460px}
.lower .lp-macro{flex:2.4 1 520px}
.lower .lp-ideas{flex:1.4 1 340px}
.lower .source-health{flex:1.5 1 420px}
.metrics-row{gap:8px}
.src-grid{grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px}
.feed{max-height:none}
body[data-view="worldview"] .grid{grid-template-columns:300px minmax(0,1fr)}
body[data-view="worldview"] #rightRail,body[data-view="worldview"] .lower{display:none}
body[data-view="worldview"] .map-container{min-height:calc(100vh - 250px)}
body[data-view="sources"] .grid{grid-template-columns:360px minmax(0,1fr)}
body[data-view="sources"] #rightRail,body[data-view="sources"] #mapContainer,body[data-view="sources"] #mapRegionBar{display:none!important}
body[data-view="sources"] .lower .g-panel:not(.source-health){display:none}
body[data-view="sources"] .lower{margin-top:0}
body[data-view="signals"] .grid,body[data-view="ops"] .grid{display:block}
body[data-view="signals"] #leftRail,body[data-view="signals"] #centerCol{display:none}
body[data-view="signals"] #rightRail{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px}
body[data-view="signals"] #rightRail .right-actions{display:none}
body[data-view="ops"] #leftRail,body[data-view="ops"] #centerCol{display:none}
body[data-view="ops"] #rightRail{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px}
body[data-view="ops"] #rightRail .right-signals,body[data-view="ops"] #rightRail .right-scenarios,body[data-view="ops"] #rightRail .right-delta,body[data-view="ops"] #rightRail .right-osint{display:none}
body[data-view="markets"] .grid{display:block}
body[data-view="markets"] #leftRail,body[data-view="markets"] #rightRail,body[data-view="markets"] #mapContainer,body[data-view="markets"] #mapRegionBar{display:none!important}
body[data-view="markets"] .lower{margin-top:0}
body[data-view="markets"] .lower .g-panel:not(.lp-macro):not(.lp-ideas){display:none}
body[data-view="markets"] .lp-macro,body[data-view="markets"] .lp-ideas{max-width:none}
@media(max-width:760px){
#main.app-root{display:block;padding:0 0 86px}
.app-sidebar{position:fixed;z-index:30;left:0;right:0;bottom:0;top:auto;height:76px;flex-direction:row;padding:8px 12px;border-right:0;border-top:1px solid var(--app-border);gap:12px}
.app-brand-mark,.sidebar-status{display:none}
.app-nav{flex-direction:row;justify-content:space-between;gap:6px;width:100%}
.nav-item{width:54px;height:54px;border-radius:18px}
.nav-item span{width:23px;height:23px;font-size:13px;border-radius:8px}
.nav-item small{display:none}
.app-shell{margin:10px;border-radius:24px;min-height:calc(100vh - 96px)}
.topbar{grid-template-columns:1fr;padding:22px;gap:12px}
.brand{font-size:30px}
.top-center{display:flex;width:100%;overflow:auto}
.top-right{justify-content:flex-start}
.grid{padding:14px;display:flex;flex-direction:column}
body[data-view="worldview"] .grid,body[data-view="sources"] .grid{display:flex}
body[data-view="signals"] #rightRail,body[data-view="ops"] #rightRail{display:flex;flex-direction:column}
.map-container{min-height:420px}
.map-legend{left:8px;right:8px;bottom:8px}
}
/* FINAL APP-SHELL FIDELITY PASS */
body::before{content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;background-image:linear-gradient(180deg,rgba(10,13,18,.72),rgba(10,13,18,.88)),url('./assets/app-shell-texture.png');background-size:auto,420px 420px;background-blend-mode:normal,soft-light;opacity:.5}
#main.app-root{grid-template-columns:150px minmax(0,1fr);gap:0;background:linear-gradient(180deg,#171a20,#11141a)}
.app-sidebar{width:150px;padding:28px 20px 24px;background:linear-gradient(180deg,#151a21,#10151c);border-right:1px solid rgba(124,138,154,.24);box-shadow:inset -1px 0 rgba(255,255,255,.03)}
.rail-live{height:34px;min-width:86px;padding:0 13px;border-radius:999px;background:rgba(255,255,255,.035);display:flex;align-items:center;justify-content:center;gap:8px;color:var(--app-muted);font-weight:800;font-size:12px}
.app-nav{position:relative;gap:18px;margin-top:6px}
.nav-glider{position:absolute;left:50%;top:0;width:86px;height:86px;border-radius:999px;background:radial-gradient(circle at 50% 46%,rgba(27,217,106,.42),rgba(27,217,106,.2) 48%,rgba(27,217,106,0) 72%);box-shadow:0 0 34px rgba(27,217,106,.34),inset 0 0 0 1px rgba(27,217,106,.22);transform:translate(-50%,0);transition:transform .58s cubic-bezier(.2,.9,.2,1),opacity .28s ease;pointer-events:none}
.nav-glider::after{content:'';position:absolute;inset:13px;border-radius:999px;border:2px solid rgba(27,217,106,.8);box-shadow:0 0 22px rgba(27,217,106,.42)}
.nav-item{position:relative;z-index:1;width:96px;height:92px;border-radius:28px;background:transparent;color:#aeb7c4;gap:8px;transition:transform .24s ease,color .24s ease,filter .24s ease}
.nav-item:hover{background:transparent;color:#f3f7fa;transform:translateY(-2px)}
.nav-item.active{background:transparent;color:#fff;filter:drop-shadow(0 12px 28px rgba(27,217,106,.18))}
.nav-icon{width:46px;height:46px;border:0;border-radius:16px;display:grid;place-items:center;background:rgba(255,255,255,.035);box-shadow:inset 0 0 0 2px currentColor;transition:background .24s ease,box-shadow .24s ease,transform .24s ease}
.nav-icon svg{width:26px;height:26px;fill:none;stroke:currentColor;stroke-width:2.05;stroke-linecap:round;stroke-linejoin:round}
.nav-item.active .nav-icon{background:rgba(27,217,106,.18);box-shadow:inset 0 0 0 2px var(--app-green),0 0 0 10px rgba(27,217,106,.04);transform:scale(1.04)}
.nav-item small{font-size:11px;font-weight:800;line-height:1;color:currentColor;text-shadow:0 1px 14px rgba(0,0,0,.35)}
.sidebar-status{width:96px;border-top-color:rgba(124,138,154,.28);font-size:12px}
.app-shell{margin:14px 16px 14px 0;border-radius:32px;background:#181c23;border-color:#35404c;box-shadow:0 24px 80px rgba(0,0,0,.34),inset 0 1px rgba(255,255,255,.035)}
.topbar{padding:30px 34px 18px;grid-template-columns:minmax(280px,420px) minmax(280px,1fr) auto;background:linear-gradient(180deg,#20242c,#1e222a);gap:26px}
.brand{font-size:28px;letter-spacing:-.01em}
.view-subtitle{max-width:420px;color:#97a2b2}
.regime-chip{display:none}
.app-search{height:56px;border-radius:18px;background:#1a1f27;border-color:#38424e;font-size:15px;box-shadow:inset 0 1px rgba(255,255,255,.03)}
.top-right{grid-column:auto;justify-content:flex-end;align-content:flex-start;gap:10px}
.theme-switch,.meta-pill,.guide-btn,.alert-badge,.perf-pill{min-height:43px;border-radius:20px;border-color:#38424e;background:#1d222b;box-shadow:inset 0 1px rgba(255,255,255,.025)}
.theme-switch{padding:5px}
.theme-btn{min-width:52px;padding:8px 12px;font-size:12px}
.theme-btn.active{box-shadow:0 7px 20px rgba(27,217,106,.22)}
.alert-badge{background:rgba(255,95,111,.13);border-color:rgba(255,95,111,.38)}
.grid{padding:20px 24px 24px;grid-template-columns:300px minmax(0,1fr);gap:18px;background:#181c23}
.top-metric-row{grid-column:1/-1;display:grid;grid-template-columns:repeat(6,minmax(130px,1fr));gap:14px}
.top-metric-card{position:relative;overflow:hidden;min-height:86px;padding:16px 18px;border:1px solid #333d49;border-radius:19px;background:linear-gradient(145deg,#252b34,#1d232c);box-shadow:inset 0 1px rgba(255,255,255,.035)}
.top-metric-card::after{content:'';position:absolute;inset:auto -20% -65% -20%;height:80%;background:radial-gradient(circle,rgba(27,217,106,.13),transparent 62%);opacity:0;transition:opacity .3s ease}
.top-metric-card:hover::after{opacity:1}
.tm-icon{width:38px;height:38px;border-radius:14px;display:grid;place-items:center;background:rgba(27,217,106,.11);color:var(--app-green);margin-bottom:8px}
.tm-icon svg{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round}
.tm-label{font-size:10px;font-weight:900;letter-spacing:.06em;text-transform:uppercase;color:#98a4b3}
.tm-value{margin-top:2px;font-size:20px;font-weight:900;color:#f3f6f8;letter-spacing:-.02em}
.tm-sub{font-size:11px;color:#8e99a8}
.g-panel,.map-region-bar,.map-container,.glossary-panel{border-color:#333d49;background:linear-gradient(180deg,#252a33,#202630)}
.g-panel{border-radius:20px;padding:18px;box-shadow:inset 0 1px rgba(255,255,255,.035)}
.map-container{border-radius:22px;background:#0c1118;box-shadow:inset 0 1px rgba(255,255,255,.04),0 18px 48px rgba(0,0,0,.22)}
.map-region-bar{border-radius:20px;padding:16px}
.layer-item,.site-row,.econ-row,.src-item,.mc,.signal-row,.sm,.idea-card,.ic,.tk-card{background:#202630;border-color:#303946;transition:transform .22s ease,border-color .22s ease,background .22s ease,box-shadow .22s ease}
.layer-item:hover,.signal-row:hover,.ic:hover,.tk-card.clickable:hover,.mc:hover{transform:translateY(-2px);border-color:rgba(27,217,106,.32);box-shadow:0 12px 32px rgba(0,0,0,.14)}
.layer-count,.site-val,.eval,.sm .smv{font-weight:900;color:var(--app-green)}
.sec-head h3{font-size:18px;letter-spacing:-.01em}
.badge{background:#1c222b;border-color:#33404c}
.right-actions .terminal-output{min-height:72px}
.sm .smb span,.mc .mbar span{background:linear-gradient(90deg,#1bd96a,#7ee787)}
.regime-chip .blink,.sidebar-status-dot{animation:status-pulse 2.2s ease-in-out infinite}
.map-container::after{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:linear-gradient(90deg,transparent,rgba(27,217,106,.035),transparent);transform:translateX(-120%);animation:surface-sweep 8s ease-in-out infinite}
body.view-transitioning .grid{animation:view-shift .42s cubic-bezier(.2,.9,.2,1)}
body.theme-transitioning .app-shell{animation:theme-wash .42s ease}
.motion-stagger{animation:panel-rise .52s cubic-bezier(.2,.9,.2,1) both}
.alert-badge,.delta-badge.new{animation:soft-alert 2.4s ease-in-out infinite}
@keyframes status-pulse{0%,100%{box-shadow:0 0 0 0 rgba(27,217,106,.32)}50%{box-shadow:0 0 0 7px rgba(27,217,106,0)}}
@keyframes surface-sweep{0%,72%{transform:translateX(-120%)}100%{transform:translateX(120%)}}
@keyframes panel-rise{from{opacity:0;transform:translateY(16px) scale(.985);filter:blur(4px)}to{opacity:1;transform:none;filter:none}}
@keyframes view-shift{from{opacity:.18;transform:translateX(18px) scale(.985);filter:blur(8px)}to{opacity:1;transform:none;filter:none}}
@keyframes theme-wash{0%{filter:brightness(.75) saturate(.75)}100%{filter:none}}
@keyframes soft-alert{0%,100%{box-shadow:inset 0 1px rgba(255,255,255,.025)}50%{box-shadow:0 0 0 5px rgba(255,95,111,.05),inset 0 1px rgba(255,255,255,.025)}}
body.low-perf .motion-stagger,body.low-perf .alert-badge,body.low-perf .delta-badge.new,body.low-perf .map-container::after,body.low-perf .regime-chip .blink,body.low-perf .sidebar-status-dot{animation:none!important}
body.low-perf .nav-glider,body.low-perf .nav-item,body.low-perf .layer-item,body.low-perf .signal-row,body.low-perf .ic,body.low-perf .tk-card,body.low-perf .mc{transition:none!important}
@media(min-width:1320px){
.grid{grid-template-columns:300px minmax(0,1fr) 380px}
#rightRail{grid-column:auto}
}
@media(max-width:1240px){
.topbar{grid-template-columns:1fr;gap:14px}
.top-right{justify-content:flex-start}
.top-metric-row{grid-template-columns:repeat(3,minmax(0,1fr))}
}
@media(max-width:760px){
body::before{background-size:auto,300px 300px}
#main.app-root{display:block;padding:0 0 88px}
.app-sidebar{width:auto;height:78px;padding:8px 12px}
.rail-live,.sidebar-status{display:none}
.app-nav{margin:0;gap:6px}
.nav-glider{left:0;width:58px;height:58px}
.nav-glider::after{inset:8px}
.nav-item{width:56px;height:58px;border-radius:19px}
.nav-icon{width:30px;height:30px;border-radius:10px}
.nav-icon svg{width:18px;height:18px}
.nav-item small{display:none}
.app-shell{margin:10px;border-radius:24px}
.topbar{padding:22px}
.top-metric-row{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
.top-metric-card{min-height:76px;padding:13px}
}
@media(prefers-reduced-motion:reduce){
*,*::before,*::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}
}
</style>
</head>
<body data-theme="dark" data-view="home">
<body>
<div id="boot">
<div class="logo-ring"><span class="logo-text">IT</span></div>
<div class="logo-ring"><span class="logo-text">CRUCIX</span></div>
<div id="bootLines"></div>
<div id="bootFinal">APP READY</div>
<div id="bootFinal">TERMINAL ACTIVE</div>
</div>
<div class="bg-radial" id="bgRadial"></div>
<div class="bg-grid" id="bgGrid"></div>
<div id="main" class="app-root">
<aside class="app-sidebar" aria-label="Primary views">
<div class="rail-live"><span class="sidebar-status-dot"></span><span>Live</span></div>
<nav class="app-nav" id="appNav">
<div class="nav-glider" id="navGlider" aria-hidden="true"></div>
<button class="nav-item active" data-view-target="home" onclick="setAppView('home')" title="Home" aria-label="Home"><span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V21h14V9.5"/><path d="M9 21v-7h6v7"/></svg></span><small>Home</small></button>
<button class="nav-item" data-view-target="worldview" onclick="setAppView('worldview')" title="Worldview" aria-label="Worldview"><span class="nav-icon"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3c2.8 2.6 4.2 5.6 4.2 9S14.8 18.4 12 21"/><path d="M12 3c-2.8 2.6-4.2 5.6-4.2 9S9.2 18.4 12 21"/></svg></span><small>Worldview</small></button>
<button class="nav-item" data-view-target="sources" onclick="setAppView('sources')" title="Sources" aria-label="Sources"><span class="nav-icon"><svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="7" ry="3"/><path d="M5 5v6c0 1.7 3.1 3 7 3s7-1.3 7-3V5"/><path d="M5 11v6c0 1.7 3.1 3 7 3s7-1.3 7-3v-6"/></svg></span><small>Sources</small></button>
<button class="nav-item" data-view-target="signals" onclick="setAppView('signals')" title="Signals" aria-label="Signals"><span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M3 13h4l3-8 4 14 3-6h4"/><path d="M4 20h16"/></svg></span><small>Signals</small></button>
<button class="nav-item" data-view-target="markets" onclick="setAppView('markets')" title="Markets" aria-label="Markets"><span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M4 19V5"/><path d="M4 19h17"/><path d="M8 16v-5"/><path d="M13 16V8"/><path d="M18 16v-9"/></svg></span><small>Markets</small></button>
<button class="nav-item" data-view-target="ops" onclick="setAppView('ops')" title="Ops" aria-label="Ops"><span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M4 5h16v14H4z"/><path d="m8 9 3 3-3 3"/><path d="M13 15h4"/></svg></span><small>Ops</small></button>
</nav>
<div class="sidebar-status">
<span class="sidebar-status-dot"></span>
<span id="sidebarStatus">Live</span>
</div>
</aside>
<main class="app-shell">
<div class="topbar" id="topbar"></div>
<div class="grid">
<div class="top-metric-row" id="topMetricRow"></div>
<div class="scanline" id="scanline"></div>
<div id="main">
<div class="topbar" id="topbar"></div>
<div class="grid">
<div class="col" id="leftRail"></div>
<div class="col" id="centerCol">
<div class="map-region-bar" id="mapRegionBar"></div>
@@ -643,8 +369,7 @@ body.low-perf .nav-glider,body.low-perf .nav-item,body.low-perf .layer-item,body
<div class="lower" id="lowerGrid"></div>
</div>
<div class="col" id="rightRail"></div>
</div>
</main>
</div>
</div>
<div class="glossary-overlay" id="glossaryOverlay" onclick="if(event.target===this) closeGlossary()">
<div class="glossary-panel">
@@ -709,137 +434,6 @@ let currentRegion = 'world';
let flatSvg, flatProjection, flatPath, flatG, flatZoom, flatW, flatH;
const terminalActionTokenKey = 'crucix_sweep_token';
const appViews = {
home: { title: 'Home', subtitle: 'Sweep summary, alert posture, source status, and the latest operator feed.' },
worldview: { title: 'Worldview', subtitle: 'Globe or flat map with regional focus and layer controls.' },
sources: { title: 'Sources', subtitle: 'Sensor grid, source health, API-key degradation, and data coverage.' },
signals: { title: 'Signals', subtitle: 'Cross-source signals, sweep delta, scenario watchlist, and OSINT context.' },
markets: { title: 'Markets', subtitle: 'Macro indicators, live markets, volatility gauges, and AI-assisted ideas.' },
ops: { title: 'Ops', subtitle: 'Terminal actions, integration state, and low-performance controls.' }
};
let currentView = localStorage.getItem('intelligence_terminal_view') || 'home';
if(!appViews[currentView]) currentView = 'home';
let themePreference = localStorage.getItem('intelligence_terminal_theme') || 'dark';
function resolveTheme(pref){
if(pref === 'auto'){
return window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
}
return pref === 'light' ? 'light' : 'dark';
}
function applyTheme(pref = themePreference){
themePreference = pref;
document.body.dataset.theme = resolveTheme(pref);
document.querySelectorAll('.theme-btn').forEach(btn => btn.classList.toggle('active', btn.dataset.themeChoice === pref));
}
function setTheme(pref){
themePreference = pref;
localStorage.setItem('intelligence_terminal_theme', pref);
document.body.classList.add('theme-transitioning');
window.setTimeout(() => document.body.classList.remove('theme-transitioning'), 480);
applyTheme(pref);
renderTopbar();
}
function iconSvg(name){
const icons = {
sweep:'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/></svg>',
delta:'<svg viewBox="0 0 24 24"><path d="M4 15l5-5 4 4 7-8"/><path d="M16 6h4v4"/></svg>',
signals:'<svg viewBox="0 0 24 24"><path d="M3 13h4l3-8 4 14 3-6h4"/></svg>',
regions:'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3c2.8 2.6 4.2 5.6 4.2 9S14.8 18.4 12 21"/><path d="M12 3c-2.8 2.6-4.2 5.6-4.2 9S9.2 18.4 12 21"/></svg>',
alert:'<svg viewBox="0 0 24 24"><path d="M12 3 5 6v5c0 4.7 3 8.3 7 10 4-1.7 7-5.3 7-10V6z"/><path d="M12 8v5"/><path d="M12 16h.01"/></svg>',
status:'<svg viewBox="0 0 24 24"><path d="M12 3 5 6v6c0 4.2 2.8 7.1 7 9 4.2-1.9 7-4.8 7-9V6z"/><path d="m8.5 12 2.2 2.2 4.8-5"/></svg>'
};
return icons[name] || icons.status;
}
function renderTopMetrics(){
const el = document.getElementById('topMetricRow');
if(!el) return;
const delta = D.delta?.summary;
const signalCount = (D.tSignals||[]).length;
const activeRegions = ['world','americas','europe','middleEast','asiaPacific','africa'].length - (D.meta.sourcesQueried === 0 ? 1 : 0);
const highAlert = (D.tg?.urgent?.length || 0) > 0 || (D.noaa?.totalAlerts || 0) > 0 || (D.acled?.totalFatalities || 0) > 0;
const sourceState = D.meta.sourcesQueried ? `${D.meta.sourcesOk}/${D.meta.sourcesQueried}` : '0/0';
const cards = [
{icon:'sweep',label:'Sweep',value:`${(D.meta.totalDurationMs/1000).toFixed(1)}s`,sub:'Last run'},
{icon:'delta',label:'Delta',value:delta?.direction ? delta.direction.replace('-', ' ') : 'Baseline',sub:'Change'},
{icon:'signals',label:'Signals',value:signalCount || (D.tg?.urgent?.length || 0),sub:'New'},
{icon:'regions',label:'Regions',value:activeRegions,sub:'Active'},
{icon:'alert',label:'Alert posture',value:highAlert ? 'High alert' : 'Normal',sub:highAlert ? 'Elevated' : 'Stable',tone:highAlert?'danger':''},
{icon:'status',label:'System status',value:D.meta.sourcesFailed ? 'Degraded' : 'Operational',sub:`Sources ${sourceState}`,tone:D.meta.sourcesFailed?'warn':'ok'}
];
el.innerHTML = cards.map(card => `
<div class="top-metric-card ${card.tone||''}">
<div class="tm-icon">${iconSvg(card.icon)}</div>
<div class="tm-label">${card.label}</div>
<div class="tm-value">${card.value}</div>
<div class="tm-sub">${card.sub}</div>
</div>`).join('');
}
function positionNavGlider(){
const nav = document.getElementById('appNav');
const glider = document.getElementById('navGlider');
const active = nav?.querySelector('.nav-item.active');
if(!nav || !glider || !active) return;
const navRect = nav.getBoundingClientRect();
const activeRect = active.getBoundingClientRect();
if(isMobileLayout()){
const x = activeRect.left - navRect.left + (activeRect.width - glider.offsetWidth) / 2;
glider.style.transform = `translate(${x}px,0)`;
} else {
const y = activeRect.top - navRect.top + (activeRect.height - glider.offsetHeight) / 2;
glider.style.transform = `translate(-50%,${y}px)`;
}
}
function shouldAnimateUi(){
return !lowPerfMode && !(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
}
function playPanelEntrances(){
if(!shouldAnimateUi()) return;
const items = [...document.querySelectorAll('.top-metric-card,.g-panel,.map-region-bar,.map-container')];
items.forEach((el,i) => {
el.classList.remove('motion-stagger');
el.style.animationDelay = `${Math.min(i * 36, 420)}ms`;
void el.offsetWidth;
el.classList.add('motion-stagger');
});
}
function renderAppNav(){
document.body.dataset.view = currentView;
document.querySelectorAll('.nav-item[data-view-target]').forEach(btn => {
btn.classList.toggle('active', btn.dataset.viewTarget === currentView);
});
const status = document.getElementById('sidebarStatus');
if(status) status.textContent = currentView === 'home' ? 'Live' : appViews[currentView].title;
requestAnimationFrame(positionNavGlider);
}
function setAppView(view){
if(!appViews[view]) return;
const changed = currentView !== view;
currentView = view;
localStorage.setItem('intelligence_terminal_view', view);
if(changed && shouldAnimateUi()){
document.body.classList.add('view-transitioning');
window.setTimeout(() => document.body.classList.remove('view-transitioning'), 460);
}
renderAppNav();
renderTopbar();
renderTopMetrics();
renderLeftRail();
renderLower();
renderRight();
playPanelEntrances();
refreshMapViewport(true);
}
const layerTypeMap = {
air: ['air'],
thermal: ['thermal'],
@@ -1040,35 +634,23 @@ function renderTopbar(){
const d = ts.toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'}).toUpperCase();
const timeStr = ts.toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit',hour12:true});
const hasActionToken = !!getTerminalActionToken();
const view = appViews[currentView] || appViews.home;
const direction = D.delta?.summary?.direction;
const deltaLabel = direction === 'risk-off' ? '&#x25B2; '+t('dashboard.riskOff','RISK-OFF') : direction === 'risk-on' ? '&#x25BC; '+t('dashboard.riskOn','RISK-ON') : '&#x25C6; '+t('dashboard.mixed','MIXED');
document.getElementById('topbar').innerHTML=`
<div class="top-left">
<span class="brand">Intelligence Terminal</span>
<span class="view-subtitle">${view.title} / ${view.subtitle}</span>
<span class="regime-chip"><span class="blink"></span>Operator dashboard</span>
<span class="brand">CRUCIX MONITOR</span>
<span class="regime-chip"><span class="blink"></span>WARTIME STAGFLATION RISK</span>
</div>
<div class="app-search"><span>Search sources, signals, regions, markets...</span></div>
${mobile ? `<div class="top-center">${getRegionControlsMarkup()}</div>` : ''}
<div class="top-right">
<div class="theme-switch" aria-label="Theme selector">
<button class="theme-btn" data-theme-choice="dark" onclick="setTheme('dark')">Dark</button>
<button class="theme-btn" data-theme-choice="auto" onclick="setTheme('auto')">Auto</button>
<button class="theme-btn" data-theme-choice="light" onclick="setTheme('light')">Light</button>
</div>
<button class="meta-pill perf-pill" onclick="togglePerfMode()" title="Reduce visual effects and start mobile in flat mode">${t('dashboard.visuals','Visuals')} <span class="v" id="perfStatus">${lowPerfMode?t('dashboard.visualsLite','Lite'):t('dashboard.visualsFull','Full')}</span></button>
<button class="meta-pill perf-pill" onclick="togglePerfMode()" title="Reduce visual effects and start mobile in flat mode">${t('dashboard.visuals','VISUALS')} <span class="v" id="perfStatus">${lowPerfMode?t('dashboard.visualsLite','LITE'):t('dashboard.visualsFull','FULL')}</span></button>
<span class="meta-pill">${t('dashboard.sweep','SWEEP')} <span class="v">${(D.meta.totalDurationMs/1000).toFixed(1)}s</span></span>
<span class="meta-pill">${d} <span class="v">${timeStr}</span></span>
<span class="meta-pill">${t('dashboard.sources','SOURCES')} <span class="v">${D.meta.sourcesOk}/${D.meta.sourcesQueried}</span></span>
${D.delta?.summary ? `<span class="meta-pill">${t('dashboard.delta','DELTA')} <span class="v">${deltaLabel}</span></span>` : ''}
${D.delta?.summary ? `<span class="meta-pill">${t('dashboard.delta','DELTA')} <span class="v">${D.delta.summary.direction==='risk-off'?'&#x25B2; '+t('dashboard.riskOff','RISK-OFF'):D.delta.summary.direction==='risk-on'?'&#x25BC; '+t('dashboard.riskOn','RISK-ON'):'&#x25C6; '+t('dashboard.mixed','MIXED')}</span></span>` : ''}
<button class="guide-btn" onclick="configureTerminalActionToken()" title="Configure SWEEP_TOKEN for protected terminal actions">${hasActionToken?'TOKEN SET':'SET TOKEN'}</button>
<button class="guide-btn" onclick="openGlossary()">${t('dashboard.guideBtn','What Signals Mean')}</button>
<span class="alert-badge">${t('dashboard.highAlert','HIGH ALERT')}</span>
</div>`;
renderRegionControls();
renderAppNav();
applyTheme(themePreference);
}
function getTerminalActionToken(){
@@ -1152,16 +734,16 @@ function renderLeftRail(){
const claims=D.fred.find(f=>f.id==='ICSA');
document.getElementById('leftRail').innerHTML=`
<div class="g-panel source-layers">
<div class="g-panel">
<div class="sec-head"><h3>${t('panels.sensorGrid','Sensor Grid')}</h3><div class="sensor-actions"><button class="mini-btn" onclick="resetLayerModes();event.stopPropagation()">RESET</button><span class="badge">${t('badges.live','LIVE')}</span></div></div>
${layers.map(l=>`<div class="layer-item ${layerMode(l.key)==='focus'?'focused':''} ${layerMode(l.key)==='hidden'?'hidden-layer':''}" onclick="cycleLayerMode('${l.key}',event)" title="Click to focus. Shift/Ctrl-click to hide."><div class="layer-left"><div class="ldot ${l.dot}"></div><div><div class="layer-name">${l.name}</div><div class="layer-sub">${l.sub}</div><div class="layer-mode">${layerModeLabel(l.key)}</div></div></div><div class="layer-count">${l.count}</div></div>`).join('')}
</div>
<div class="g-panel nuclear-panel">
<div class="g-panel">
<div class="sec-head"><h3>${t('panels.nuclearWatch','Nuclear Watch')}</h3><span class="badge">${t('badges.radiation','RADIATION')}</span></div>
<div class="nuke-ok">${allNormal?'&#9679; '+t('nuclear.allSitesNormal','ALL SITES NORMAL'):'&#9888; '+t('nuclear.anomalyDetected','ANOMALY DETECTED')}</div>
${nukeHtml}
</div>
<div class="g-panel risk-panel">
<div class="g-panel">
<div class="sec-head"><h3>${t('panels.riskGauges','Risk Gauges')}</h3><span class="badge">${t('badges.stress','STRESS')}</span></div>
<div class="econ-row"><span class="elabel">${t('metrics.vix','VIX')} (Fear)</span><span class="eval" style="color:${vix?.value>20?'var(--warn)':'var(--accent)'}">${vix?.value||'--'}</span></div>
<div class="econ-row"><span class="elabel">${t('metrics.hySpread','HY Spread')}</span><span class="eval">${hy?.value||'--'}</span></div>
@@ -1171,7 +753,7 @@ function renderLeftRail(){
<div class="econ-row"><span class="elabel">${t('metrics.m2Supply','M2 Supply')}</span><span class="eval">$${(m2?.value/1000)?.toFixed(1)||'--'}T</span></div>
<div class="econ-row"><span class="elabel">${t('metrics.natDebt','Nat. Debt')}</span><span class="eval">$${(parseFloat(D.treasury.totalDebt)/1e12).toFixed(2)}T</span></div>
</div>
<div class="g-panel space-panel">
<div class="g-panel">
<div class="sec-head"><h3>${t('panels.spaceWatch','Space Watch')}</h3><button class="mini-btn" onclick="toggleSpaceDisplay()">${spaceDisplayMode.toUpperCase()}</button></div>
${D.space ? `
<div class="econ-row"><span class="elabel">New Objects (30d)</span><span class="eval" style="color:var(--accent2)">${D.space.totalNewObjects||0}</span></div>
@@ -2021,12 +1603,7 @@ function renderLower(){
${ideasHtml}
<div class="disclosure">FOR INFORMATIONAL PURPOSES ONLY. This is not financial advice, a recommendation to buy or sell any security, or a solicitation of any kind. All signal-based observations are derived from publicly available OSINT data and should not be relied upon for investment decisions. Consult a licensed financial advisor before making any investment. Past performance does not guarantee future results.</div>
</div>`;
const sourcePanel = `<div class="g-panel source-health">
<div class="sec-head"><h3>Source Health</h3><span class="badge">${D.meta.sourcesOk}/${D.meta.sourcesQueried} online</span></div>
<div class="src-grid">${srcHtml || '<div class="src-item"><div class="sd err"></div><span>No source snapshot</span></div>'}</div>
<div class="disclosure">Sources that require API keys degrade visibly here while the rest of the sweep continues. The dashboard stays useful with partial data instead of hiding failures.</div>
</div>`;
document.getElementById('lowerGrid').innerHTML=`${tickerPanel}${osintPanel}${macroPanel}${ideasPanel}${sourcePanel}`;
document.getElementById('lowerGrid').innerHTML=`${tickerPanel}${osintPanel}${macroPanel}${ideasPanel}`;
}
async function runTerminalAction(action){
@@ -2179,7 +1756,7 @@ function safeExternalUrl(raw){try{const u=new URL(raw,location.href);return u.pr
function runBoot(){
const acledStatus = D.acled?.totalEvents > 0 ? `<span class="ok">${D.acled.totalEvents} EVENTS</span>` : '<span style="color:var(--warn)">DEGRADED</span>';
const lines=[
{text:t('boot.initializing','INITIALIZING INTELLIGENCE TERMINAL'),delay:0},
{text:t('boot.initializing','INITIALIZING CRUCIX ENGINE v2.1.0'),delay:0},
{text:t('boot.connecting','CONNECTING {count} OSINT SOURCES...').replace('{count}',D.meta.sourcesQueried),delay:400},
{text:'&#9500;&#9472; '+t('boot.sourceGroup1','OPENSKY · FIRMS · KIWISDR · MARITIME'),delay:700},
{text:'&#9500;&#9472; '+t('boot.sourceGroup2','FRED · BLS · EIA · TREASURY · GSCPI'),delay:900},
@@ -2191,7 +1768,7 @@ function runBoot(){
{text:t('boot.intelligenceSynthesis','INTELLIGENCE SYNTHESIS')+': <span class="ok">'+t('boot.active','ACTIVE')+'</span>',delay:2400},
];
const container=document.getElementById('bootLines');
document.getElementById('bootFinal').textContent=t('dashboard.terminalActive','APP READY');
document.getElementById('bootFinal').textContent=t('dashboard.terminalActive','TERMINAL ACTIVE');
const tl=gsap.timeline();
tl.to('.logo-ring',{opacity:1,duration:0.6,ease:'power2.out'},0);
tl.to(container,{opacity:1,duration:0.3},0.3);
@@ -2206,6 +1783,7 @@ function runBoot(){
tl.set('#boot',{display:'none'},4.2);
tl.to('#bgRadial',{opacity:1,duration:1},3.8);
tl.to('#bgGrid',{opacity:1,duration:1.2},4.0);
tl.to('#scanline',{opacity:1,duration:0.8},4.3);
tl.to('#main',{opacity:1,duration:0.6},3.9);
tl.call(()=>{
gsap.from('.g-panel,.topbar,.map-container',{opacity:0,y:20,scale:0.97,duration:0.5,stagger:0.06,ease:'power2.out'});
@@ -2220,7 +1798,7 @@ function runBoot(){
},[],4.0);
}
function isMobileLayout(){ return window.innerWidth <= 760; }
function isMobileLayout(){ return window.innerWidth <= 1100; }
function buildOsintPanel(panelClass='', maxHeight=260){
const allPosts=[...D.tg.urgent,...D.tg.topPosts].sort((a,b)=>new Date(b.date||0)-new Date(a.date||0));
@@ -2309,19 +1887,16 @@ function syncResponsiveLayout(force=false){
if(force || lastResponsiveMobile === null || mobileNow !== lastResponsiveMobile){
lastResponsiveMobile = mobileNow;
renderTopbar();
renderTopMetrics();
renderLeftRail();
renderLower();
renderRight();
playPanelEntrances();
}
refreshMapViewport(force && !isFlat);
positionNavGlider();
}
// === REINIT (for live updates without boot sequence) ===
function reinit(){
renderTopbar();renderTopMetrics();renderLeftRail();renderLower();renderRight();playPanelEntrances();
renderTopbar();renderLeftRail();renderLower();renderRight();
plotMarkers();
}
@@ -2360,9 +1935,7 @@ function connectSSE(){
// === INIT ===
let booted = false;
function init(){
applyTheme(themePreference);
renderAppNav();
renderTopbar();renderTopMetrics();renderLeftRail();renderLower();renderRight();playPanelEntrances();
renderTopbar();renderLeftRail();renderLower();renderRight();
renderGlossary();
initMap();
if (!booted) { runBoot(); booted = true; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 881 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB