v0.9.6: InfoNet hashchain, Wormhole gate encryption, mesh reputation, 16 community contributors

Gate messages now propagate via the Infonet hashchain as encrypted blobs — every node syncs them
through normal chain sync while only Gate members with MLS keys can decrypt. Added mesh reputation
system, peer push workers, voluntary Wormhole opt-in for node participation, fork recovery,
killwormhole scripts, obfuscated terminology, and hardened the self-updater to protect encryption
keys and chain state during updates.

New features: Shodan search, train tracking, Sentinel Hub imagery, 8 new intelligence layers,
CCTV expansion to 11,000+ cameras across 6 countries, Mesh Terminal CLI, prediction markets,
desktop-shell scaffold, and comprehensive mesh test suite (215 frontend + backend tests passing).

Community contributors: @wa1id, @AlborzNazari, @adust09, @Xpirix, @imqdcr, @csysp, @suranyami,
@chr0n1x, @johan-martensson, @singularfailure, @smithbh, @OrfeoTerkuci, @deuza, @tm-const,
@Elhard1, @ttulttul
This commit is contained in:
anoracleofra-code
2026-03-26 05:58:04 -06:00
parent d363013742
commit 668ce16dc7
363 changed files with 170456 additions and 23229 deletions
+224 -48
View File
@@ -5,16 +5,27 @@
* the client bundle or the build manifest.
*
* Set BACKEND_URL in docker-compose `environment:` (e.g. http://backend:8000)
* to use Docker internal networking. Defaults to http://localhost:8000 for
* to use Docker internal networking. Defaults to http://127.0.0.1:8000 for
* local development where both services run on the same host.
*/
import { NextRequest, NextResponse } from "next/server";
import { NextRequest, NextResponse } from 'next/server';
import fs from 'fs';
import path from 'path';
import { resolveAdminSessionToken } from '@/lib/server/adminSessionStore';
// Headers that must not be forwarded to the backend.
const STRIP_REQUEST = new Set([
"connection", "keep-alive", "proxy-authenticate", "proxy-authorization",
"te", "trailers", "transfer-encoding", "upgrade", "host",
'connection',
'keep-alive',
'proxy-authenticate',
'proxy-authorization',
'x-admin-key',
'te',
'trailers',
'transfer-encoding',
'upgrade',
'host',
]);
// Headers that must not be forwarded back to the browser.
@@ -22,60 +33,222 @@ const STRIP_REQUEST = new Set([
// automatically decompresses gzip/br responses — forwarding these headers
// would cause ERR_CONTENT_DECODING_FAILED in the browser.
const STRIP_RESPONSE = new Set([
"connection", "keep-alive", "proxy-authenticate", "proxy-authorization",
"te", "trailers", "transfer-encoding", "upgrade",
"content-encoding", "content-length",
'connection',
'keep-alive',
'proxy-authenticate',
'proxy-authorization',
'te',
'trailers',
'transfer-encoding',
'upgrade',
'content-encoding',
'content-length',
]);
async function proxy(req: NextRequest, path: string[]): Promise<NextResponse> {
const backendUrl = process.env.BACKEND_URL ?? "http://localhost:8000";
const targetUrl = new URL(`/api/${path.join("/")}`, backendUrl);
targetUrl.search = req.nextUrl.search;
const ADMIN_COOKIE = 'sb_admin_session';
const NO_STORE_PROXY_HEADERS = {
'Cache-Control': 'no-store, max-age=0',
Pragma: 'no-cache',
};
// Forward relevant request headers
const forwardHeaders = new Headers();
req.headers.forEach((value, key) => {
if (!STRIP_REQUEST.has(key.toLowerCase())) {
forwardHeaders.set(key, value);
}
});
function isSensitiveProxyPath(pathSegments: string[]): boolean {
const joined = pathSegments.join('/');
if (!joined) return false;
if (pathSegments[0] === 'wormhole') return true;
if (joined === 'refresh') return true;
if (joined === 'debug-latest') return true;
if (joined === 'system/update') return true;
if (pathSegments[0] === 'settings') return true;
if (joined === 'mesh/infonet/ingest') return true;
return false;
}
const isBodyless = req.method === "GET" || req.method === "HEAD";
let upstream: Response;
async function proxy(req: NextRequest, pathSegments: string[]): Promise<NextResponse> {
try {
upstream = await fetch(targetUrl.toString(), {
const isMesh = pathSegments[0] === 'mesh';
const meshSegments = pathSegments.slice(1);
const isSensitiveMeshPath = isMesh && meshSegments[0] === 'dm';
const isAnonymousMeshWritePath =
isMesh &&
!isSensitiveMeshPath &&
['POST', 'PUT', 'DELETE'].includes(req.method.toUpperCase()) &&
(meshSegments.join('/') === 'send' ||
meshSegments.join('/') === 'vote' ||
meshSegments.join('/') === 'report' ||
meshSegments.join('/') === 'gate/create' ||
(meshSegments[0] === 'gate' && meshSegments[2] === 'message') ||
meshSegments.join('/') === 'oracle/predict' ||
meshSegments.join('/') === 'oracle/resolve' ||
meshSegments.join('/') === 'oracle/stake' ||
meshSegments.join('/') === 'oracle/resolve-stakes');
const backendUrl = process.env.BACKEND_URL ?? 'http://127.0.0.1:8000';
let targetBase = backendUrl;
if (isMesh) {
const envEnabled = (process.env.WORMHOLE_ENABLED || '').toLowerCase();
let wormholeEnabled = ['1', 'true', 'yes'].includes(envEnabled);
let privacyProfile = (process.env.WORMHOLE_PRIVACY_PROFILE || '').toLowerCase();
let anonymousMode = ['1', 'true', 'yes'].includes(
(process.env.WORMHOLE_ANONYMOUS_MODE || '').toLowerCase(),
);
let wormholeReady = false;
let effectiveTransport = '';
if (!wormholeEnabled || !privacyProfile || !anonymousMode) {
try {
const cwd = process.cwd();
const repoRoot = cwd.endsWith(path.sep + 'frontend') ? path.resolve(cwd, '..') : cwd;
const wormholeFile = path.join(repoRoot, 'backend', 'data', 'wormhole.json');
if (fs.existsSync(wormholeFile)) {
const raw = fs.readFileSync(wormholeFile, 'utf8');
const data = JSON.parse(raw);
if (!wormholeEnabled) {
wormholeEnabled = Boolean(data && data.enabled);
}
privacyProfile = privacyProfile || String(data?.privacy_profile || '').toLowerCase();
if (!anonymousMode) {
anonymousMode = Boolean(data?.anonymous_mode);
}
}
const wormholeStatusFile = path.join(repoRoot, 'backend', 'data', 'wormhole_status.json');
if (fs.existsSync(wormholeStatusFile)) {
const raw = fs.readFileSync(wormholeStatusFile, 'utf8');
const data = JSON.parse(raw);
wormholeReady = Boolean(data?.running) && Boolean(data?.ready);
effectiveTransport = String(data?.transport_active || data?.transport || '').toLowerCase();
}
} catch {
wormholeEnabled = false;
}
}
if (privacyProfile === 'high' && !wormholeEnabled && isSensitiveMeshPath) {
return new NextResponse(
JSON.stringify({
ok: false,
detail: 'High privacy requires Wormhole. Enable it in Settings and restart.',
}),
{ status: 428, headers: { 'Content-Type': 'application/json' } },
);
}
if (wormholeEnabled && isSensitiveMeshPath) {
if (!wormholeReady) {
return new NextResponse(
JSON.stringify({
ok: false,
detail: 'Wormhole is enabled but not connected yet. Start Wormhole to use secure DM features.',
}),
{ status: 503, headers: { 'Content-Type': 'application/json' } },
);
}
targetBase = process.env.WORMHOLE_URL ?? 'http://127.0.0.1:8787';
}
if (anonymousMode && isAnonymousMeshWritePath) {
if (!wormholeEnabled) {
return new NextResponse(
JSON.stringify({
ok: false,
detail: 'Anonymous mode requires Wormhole to be enabled before public posting.',
}),
{ status: 428, headers: { 'Content-Type': 'application/json' } },
);
}
const hiddenReady = wormholeReady && ['tor', 'i2p', 'mixnet'].includes(effectiveTransport);
if (!hiddenReady) {
return new NextResponse(
JSON.stringify({
ok: false,
detail: 'Anonymous mode requires Wormhole hidden transport (Tor/I2P/Mixnet) to be ready.',
}),
{ status: 428, headers: { 'Content-Type': 'application/json' } },
);
}
targetBase = process.env.WORMHOLE_URL ?? 'http://127.0.0.1:8787';
}
}
const targetUrl = new URL(`/api/${pathSegments.join('/')}`, targetBase);
targetUrl.search = req.nextUrl.search;
const forwardHeaders = new Headers();
req.headers.forEach((value, key) => {
if (!STRIP_REQUEST.has(key.toLowerCase())) {
forwardHeaders.set(key, value);
}
});
if (isSensitiveProxyPath(pathSegments)) {
const cookieToken = req.cookies.get(ADMIN_COOKIE)?.value || '';
const injectedAdmin = process.env.ADMIN_KEY || resolveAdminSessionToken(cookieToken) || '';
if (injectedAdmin) {
forwardHeaders.set('X-Admin-Key', injectedAdmin);
}
}
const isBodyless = req.method === 'GET' || req.method === 'HEAD';
let upstream: Response;
const requestInit: RequestInit & { duplex?: 'half' } = {
method: req.method,
headers: forwardHeaders,
body: isBodyless ? undefined : req.body,
cache: 'no-store',
};
if (!isBodyless) {
requestInit.body = req.body;
// Required for streaming request bodies in Node.js fetch
// @ts-ignore
duplex: "half",
});
} catch (err) {
// Backend unreachable — return a clean 502 so the UI can handle it gracefully
return new NextResponse(JSON.stringify({ error: "Backend unavailable" }), {
status: 502,
headers: { "Content-Type": "application/json" },
});
}
// Forward response headers
const responseHeaders = new Headers();
upstream.headers.forEach((value, key) => {
if (!STRIP_RESPONSE.has(key.toLowerCase())) {
responseHeaders.set(key, value);
requestInit.duplex = 'half';
}
try {
upstream = await fetch(targetUrl.toString(), requestInit);
} catch {
return new NextResponse(JSON.stringify({ error: 'Backend unavailable' }), {
status: 502,
headers: { 'Content-Type': 'application/json' },
});
}
});
// 304 responses must have no body
if (upstream.status === 304) {
return new NextResponse(null, { status: 304, headers: responseHeaders });
const responseHeaders = new Headers();
upstream.headers.forEach((value, key) => {
if (!STRIP_RESPONSE.has(key.toLowerCase())) {
responseHeaders.set(key, value);
}
});
if (isSensitiveProxyPath(pathSegments) || isSensitiveMeshPath) {
Object.entries(NO_STORE_PROXY_HEADERS).forEach(([key, value]) => {
responseHeaders.set(key, value);
});
}
if (upstream.status === 304) {
return new NextResponse(null, { status: 304, headers: responseHeaders });
}
// Stream the upstream body directly instead of buffering the full response.
// This reduces TTFB and memory pressure for large payloads (flights, ships).
return new NextResponse(upstream.body, {
status: upstream.status,
headers: responseHeaders,
});
} catch (error) {
console.error('api proxy unexpected error', {
pathSegments,
method: req.method,
error,
});
return new NextResponse(
JSON.stringify({
error: 'Proxy failed',
detail: error instanceof Error ? error.message : 'unknown_error',
}),
{
status: 500,
headers: {
'Content-Type': 'application/json',
...NO_STORE_PROXY_HEADERS,
},
},
);
}
return new NextResponse(upstream.body, {
status: upstream.status,
headers: responseHeaders,
});
}
export async function GET(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
@@ -90,6 +263,9 @@ export async function PUT(req: NextRequest, { params }: { params: Promise<{ path
return proxy(req, (await params).path);
}
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
export async function DELETE(
req: NextRequest,
{ params }: { params: Promise<{ path: string[] }> },
) {
return proxy(req, (await params).path);
}
+106
View File
@@ -0,0 +1,106 @@
import { NextRequest, NextResponse } from 'next/server';
import {
clearAdminSessionToken,
createAdminSessionToken,
hasAdminSessionToken,
} from '@/lib/server/adminSessionStore';
const COOKIE_NAME = 'sb_admin_session';
const COOKIE_MAX_AGE = 60 * 60 * 8;
const NO_STORE_HEADERS = {
'Cache-Control': 'no-store, max-age=0',
Pragma: 'no-cache',
};
function cookieOptions() {
return {
httpOnly: true,
sameSite: 'strict' as const,
secure: process.env.NODE_ENV === 'production',
path: '/',
maxAge: COOKIE_MAX_AGE,
};
}
async function verifyAdminKey(adminKey: string): Promise<{ ok: true } | { ok: false; detail: string }> {
const backendUrl = process.env.BACKEND_URL ?? 'http://127.0.0.1:8000';
const verifyAgainstBackend = async (): Promise<
{ ok: true } | { ok: false; detail: string }
> => {
try {
const res = await fetch(`${backendUrl}/api/settings/privacy-profile`, {
method: 'GET',
headers: { 'X-Admin-Key': adminKey },
cache: 'no-store',
});
if (res.ok) return { ok: true };
const data = await res.json().catch(() => ({}));
return {
ok: false,
detail: String(data?.detail || data?.message || 'Unable to verify admin key'),
};
} catch {
return {
ok: false,
detail: 'Unable to verify admin key against backend',
};
}
};
const configuredAdmin = String(process.env.ADMIN_KEY || '').trim();
if (configuredAdmin) {
if (adminKey !== configuredAdmin) {
return { ok: false, detail: 'Invalid admin key' };
}
return verifyAgainstBackend();
}
return verifyAgainstBackend();
}
export async function POST(req: NextRequest) {
const body = await req.json().catch(() => ({}));
const adminKey = String(body?.adminKey || '').trim();
if (!adminKey) {
return NextResponse.json(
{ ok: false, detail: 'Missing admin key' },
{ status: 400, headers: NO_STORE_HEADERS },
);
}
const verification = await verifyAdminKey(adminKey);
if (!verification.ok) {
return NextResponse.json(
{ ok: false, detail: verification.detail },
{ status: 403, headers: NO_STORE_HEADERS },
);
}
const existingToken = req.cookies.get(COOKIE_NAME)?.value || '';
if (existingToken) {
clearAdminSessionToken(existingToken);
}
const sessionToken = createAdminSessionToken(adminKey, COOKIE_MAX_AGE);
const res = NextResponse.json({ ok: true }, { headers: NO_STORE_HEADERS });
res.cookies.set(COOKIE_NAME, sessionToken, cookieOptions());
return res;
}
export async function DELETE(req: NextRequest) {
const existingToken = req.cookies.get(COOKIE_NAME)?.value || '';
if (existingToken) {
clearAdminSessionToken(existingToken);
}
const res = NextResponse.json({ ok: true }, { headers: NO_STORE_HEADERS });
res.cookies.set(COOKIE_NAME, '', {
...cookieOptions(),
maxAge: 0,
});
return res;
}
export async function GET(req: NextRequest) {
const token = req.cookies.get(COOKIE_NAME)?.value || '';
return NextResponse.json(
{ ok: true, hasSession: hasAdminSessionToken(token) },
{ headers: NO_STORE_HEADERS },
);
}
+323 -66
View File
@@ -1,4 +1,4 @@
@import "tailwindcss";
@import 'tailwindcss';
:root {
--background: #000000;
@@ -7,47 +7,75 @@
--bg-secondary: rgb(5, 5, 8);
--bg-tertiary: rgb(12, 12, 16);
--bg-panel: rgba(0, 0, 0, 0.85);
--border-primary: rgb(10, 12, 15);
--border-secondary: rgb(20, 24, 28);
--border-primary: rgba(8, 145, 178, 0.18);
--border-secondary: rgba(8, 145, 178, 0.30);
--border-glow: rgba(6, 182, 212, 0.12);
--text-primary: rgb(243, 244, 246);
--text-secondary: rgb(34, 211, 238);
--text-muted: rgb(8, 145, 178);
--text-heading: rgb(236, 254, 255);
--hover-accent: rgba(8, 51, 68, 0.2);
--scrollbar-thumb: rgba(8, 145, 178, 0.3);
--scrollbar-thumb-hover: rgba(8, 145, 178, 0.5);
--scrollbar-thumb: rgba(255, 255, 255, 0.12);
--scrollbar-thumb-hover: rgba(255, 255, 255, 0.25);
--font-geist-sans:
ui-sans-serif, system-ui, -apple-system, 'Segoe UI', 'Helvetica Neue', Arial, 'Noto Sans',
sans-serif;
--font-roboto-mono:
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
monospace;
}
/* Light theme: only the map basemap changes — UI stays dark */
[data-theme="light"] {
[data-theme='light'] {
--background: #000000;
--foreground: #ededed;
--bg-primary: #000000;
--bg-secondary: rgb(5, 5, 8);
--bg-tertiary: rgb(12, 12, 16);
--bg-panel: rgba(0, 0, 0, 0.85);
--border-primary: rgb(10, 12, 15);
--border-secondary: rgb(20, 24, 28);
--border-primary: rgba(8, 145, 178, 0.18);
--border-secondary: rgba(8, 145, 178, 0.30);
--border-glow: rgba(6, 182, 212, 0.12);
--text-primary: rgb(243, 244, 246);
--text-secondary: rgb(34, 211, 238);
--text-muted: rgb(8, 145, 178);
--text-heading: rgb(236, 254, 255);
--hover-accent: rgba(8, 51, 68, 0.2);
--scrollbar-thumb: rgba(8, 145, 178, 0.3);
--scrollbar-thumb-hover: rgba(8, 145, 178, 0.5);
--scrollbar-thumb: rgba(255, 255, 255, 0.12);
--scrollbar-thumb-hover: rgba(255, 255, 255, 0.25);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--font-mono: var(--font-roboto-mono);
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
font-family: var(--font-roboto-mono), 'Roboto Mono', monospace;
}
/* Global interactive cursor hints */
button:not(:disabled),
[role='button']:not([aria-disabled='true']),
a[href],
summary,
label[for],
input[type='button']:not(:disabled),
input[type='submit']:not(:disabled),
input[type='reset']:not(:disabled) {
cursor: pointer;
}
button:disabled,
[role='button'][aria-disabled='true'],
input:disabled,
select:disabled,
textarea:disabled {
cursor: not-allowed;
}
/* Styled thin scrollbar for dark HUD UI */
@@ -70,15 +98,49 @@ body {
.styled-scrollbar {
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-thumb) transparent;
}
/* DOS block cursor blink */
@keyframes blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
/* ── TERMINAL UTILITY CLASSES ── */
/* Subtle text glow for cyan headings */
.text-glow {
text-shadow: 0 0 8px rgba(34, 211, 238, 0.3);
}
/* Terminal input — prompt style */
.terminal-input {
border-radius: 0;
border: 1px solid rgba(8, 145, 178, 0.25);
background: rgba(0, 0, 0, 0.4);
}
.terminal-input:focus {
border-color: rgba(34, 211, 238, 0.5);
box-shadow: 0 0 6px rgba(34, 211, 238, 0.15);
outline: none;
}
/* Map popup shared utilities */
.map-popup {
background: rgba(10, 14, 26, 0.95);
border-radius: 6px;
border-radius: 2px;
border: 1px solid rgba(8, 145, 178, 0.25);
padding: 10px 14px;
color: #e0e6f0;
font-family: monospace;
font-family:
var(--font-roboto-mono), 'Roboto Mono', monospace, 'Microsoft YaHei', 'PingFang SC',
'Noto Sans SC', 'Noto Sans JP', 'Noto Sans KR', sans-serif;
font-size: 11px;
min-width: 220px;
max-width: 320px;
@@ -116,77 +178,181 @@ body {
/* ── MATRIX HUD COLOR THEME ── */
/* Remaps cyan accents → green within .hud-zone containers only */
[data-hud="matrix"] .hud-zone {
[data-hud='matrix'] .hud-zone {
--text-secondary: #4ade80;
--text-muted: #16a34a;
--text-heading: #bbf7d0;
--hover-accent: rgba(5, 46, 22, 0.2);
--scrollbar-thumb: rgba(22, 163, 74, 0.3);
--scrollbar-thumb-hover: rgba(22, 163, 74, 0.5);
--scrollbar-thumb: rgba(255, 255, 255, 0.12);
--scrollbar-thumb-hover: rgba(255, 255, 255, 0.25);
--border-primary: rgba(22, 163, 74, 0.18);
--border-secondary: rgba(22, 163, 74, 0.30);
--border-glow: rgba(34, 197, 94, 0.12);
}
[data-hud='matrix'] .hud-zone .text-glow {
text-shadow: 0 0 8px rgba(74, 222, 128, 0.3);
}
/* --- Text color overrides --- */
[data-hud="matrix"] .hud-zone .text-cyan-300 { color: #86efac !important; }
[data-hud="matrix"] .hud-zone .text-cyan-400 { color: #4ade80 !important; }
[data-hud="matrix"] .hud-zone .text-cyan-500 { color: #22c55e !important; }
[data-hud="matrix"] .hud-zone .text-cyan-600 { color: #16a34a !important; }
[data-hud="matrix"] .hud-zone .text-cyan-700 { color: #15803d !important; }
[data-hud="matrix"] .hud-zone .text-cyan-500\/50 { color: rgba(34, 197, 94, 0.5) !important; }
[data-hud="matrix"] .hud-zone .text-cyan-500\/70 { color: rgba(34, 197, 94, 0.7) !important; }
[data-hud="matrix"] .hud-zone .text-cyan-500\/80 { color: rgba(34, 197, 94, 0.8) !important; }
[data-hud='matrix'] .hud-zone .text-cyan-300 {
color: #86efac !important;
}
[data-hud='matrix'] .hud-zone .text-cyan-400 {
color: #4ade80 !important;
}
[data-hud='matrix'] .hud-zone .text-cyan-500 {
color: #22c55e !important;
}
[data-hud='matrix'] .hud-zone .text-cyan-600 {
color: #16a34a !important;
}
[data-hud='matrix'] .hud-zone .text-cyan-700 {
color: #15803d !important;
}
[data-hud='matrix'] .hud-zone .text-cyan-500\/50 {
color: rgba(34, 197, 94, 0.5) !important;
}
[data-hud='matrix'] .hud-zone .text-cyan-500\/70 {
color: rgba(34, 197, 94, 0.7) !important;
}
[data-hud='matrix'] .hud-zone .text-cyan-500\/80 {
color: rgba(34, 197, 94, 0.8) !important;
}
/* --- Background color overrides --- */
[data-hud="matrix"] .hud-zone .bg-cyan-400 { background-color: #4ade80 !important; }
[data-hud="matrix"] .hud-zone .bg-cyan-300 { background-color: #86efac !important; }
[data-hud="matrix"] .hud-zone .bg-cyan-500 { background-color: #22c55e !important; }
[data-hud="matrix"] .hud-zone .bg-cyan-500\/10 { background-color: rgba(34, 197, 94, 0.1) !important; }
[data-hud="matrix"] .hud-zone .bg-cyan-500\/20 { background-color: rgba(34, 197, 94, 0.2) !important; }
[data-hud="matrix"] .hud-zone .bg-cyan-500\/30 { background-color: rgba(34, 197, 94, 0.3) !important; }
[data-hud="matrix"] .hud-zone .bg-cyan-900\/30 { background-color: rgba(20, 83, 45, 0.3) !important; }
[data-hud="matrix"] .hud-zone .bg-cyan-900\/50 { background-color: rgba(20, 83, 45, 0.5) !important; }
[data-hud="matrix"] .hud-zone .bg-cyan-900\/60 { background-color: rgba(20, 83, 45, 0.6) !important; }
[data-hud="matrix"] .hud-zone .bg-cyan-950\/10 { background-color: rgba(5, 46, 22, 0.1) !important; }
[data-hud="matrix"] .hud-zone .bg-cyan-950\/30 { background-color: rgba(5, 46, 22, 0.3) !important; }
[data-hud="matrix"] .hud-zone .bg-cyan-950\/40 { background-color: rgba(5, 46, 22, 0.4) !important; }
[data-hud='matrix'] .hud-zone .bg-cyan-400 {
background-color: #4ade80 !important;
}
[data-hud='matrix'] .hud-zone .bg-cyan-300 {
background-color: #86efac !important;
}
[data-hud='matrix'] .hud-zone .bg-cyan-500 {
background-color: #22c55e !important;
}
[data-hud='matrix'] .hud-zone .bg-cyan-500\/10 {
background-color: rgba(34, 197, 94, 0.1) !important;
}
[data-hud='matrix'] .hud-zone .bg-cyan-500\/20 {
background-color: rgba(34, 197, 94, 0.2) !important;
}
[data-hud='matrix'] .hud-zone .bg-cyan-500\/30 {
background-color: rgba(34, 197, 94, 0.3) !important;
}
[data-hud='matrix'] .hud-zone .bg-cyan-900\/30 {
background-color: rgba(20, 83, 45, 0.3) !important;
}
[data-hud='matrix'] .hud-zone .bg-cyan-900\/50 {
background-color: rgba(20, 83, 45, 0.5) !important;
}
[data-hud='matrix'] .hud-zone .bg-cyan-900\/60 {
background-color: rgba(20, 83, 45, 0.6) !important;
}
[data-hud='matrix'] .hud-zone .bg-cyan-950\/10 {
background-color: rgba(5, 46, 22, 0.1) !important;
}
[data-hud='matrix'] .hud-zone .bg-cyan-950\/30 {
background-color: rgba(5, 46, 22, 0.3) !important;
}
[data-hud='matrix'] .hud-zone .bg-cyan-950\/40 {
background-color: rgba(5, 46, 22, 0.4) !important;
}
/* --- Border color overrides --- */
[data-hud="matrix"] .hud-zone .border-cyan-400 { border-color: #4ade80 !important; }
[data-hud="matrix"] .hud-zone .border-cyan-500 { border-color: #22c55e !important; }
[data-hud="matrix"] .hud-zone .border-cyan-700 { border-color: #15803d !important; }
[data-hud="matrix"] .hud-zone .border-cyan-800 { border-color: #166534 !important; }
[data-hud="matrix"] .hud-zone .border-cyan-900 { border-color: #14532d !important; }
[data-hud="matrix"] .hud-zone .border-cyan-500\/10 { border-color: rgba(34, 197, 94, 0.1) !important; }
[data-hud="matrix"] .hud-zone .border-cyan-500\/20 { border-color: rgba(34, 197, 94, 0.2) !important; }
[data-hud="matrix"] .hud-zone .border-cyan-500\/30 { border-color: rgba(34, 197, 94, 0.3) !important; }
[data-hud="matrix"] .hud-zone .border-cyan-500\/40 { border-color: rgba(34, 197, 94, 0.4) !important; }
[data-hud="matrix"] .hud-zone .border-cyan-500\/50 { border-color: rgba(34, 197, 94, 0.5) !important; }
[data-hud="matrix"] .hud-zone .border-cyan-800\/40 { border-color: rgba(22, 101, 52, 0.4) !important; }
[data-hud="matrix"] .hud-zone .border-cyan-800\/50 { border-color: rgba(22, 101, 52, 0.5) !important; }
[data-hud="matrix"] .hud-zone .border-cyan-800\/60 { border-color: rgba(22, 101, 52, 0.6) !important; }
[data-hud="matrix"] .hud-zone .border-cyan-900\/50 { border-color: rgba(20, 83, 45, 0.5) !important; }
[data-hud="matrix"] .hud-zone .border-b-cyan-900 { border-bottom-color: #14532d !important; }
[data-hud="matrix"] .hud-zone .border-l-cyan-500 { border-left-color: #22c55e !important; }
[data-hud='matrix'] .hud-zone .border-cyan-400 {
border-color: #4ade80 !important;
}
[data-hud='matrix'] .hud-zone .border-cyan-500 {
border-color: #22c55e !important;
}
[data-hud='matrix'] .hud-zone .border-cyan-700 {
border-color: #15803d !important;
}
[data-hud='matrix'] .hud-zone .border-cyan-800 {
border-color: #166534 !important;
}
[data-hud='matrix'] .hud-zone .border-cyan-900 {
border-color: #14532d !important;
}
[data-hud='matrix'] .hud-zone .border-cyan-500\/10 {
border-color: rgba(34, 197, 94, 0.1) !important;
}
[data-hud='matrix'] .hud-zone .border-cyan-500\/20 {
border-color: rgba(34, 197, 94, 0.2) !important;
}
[data-hud='matrix'] .hud-zone .border-cyan-500\/30 {
border-color: rgba(34, 197, 94, 0.3) !important;
}
[data-hud='matrix'] .hud-zone .border-cyan-500\/40 {
border-color: rgba(34, 197, 94, 0.4) !important;
}
[data-hud='matrix'] .hud-zone .border-cyan-500\/50 {
border-color: rgba(34, 197, 94, 0.5) !important;
}
[data-hud='matrix'] .hud-zone .border-cyan-800\/40 {
border-color: rgba(22, 101, 52, 0.4) !important;
}
[data-hud='matrix'] .hud-zone .border-cyan-800\/50 {
border-color: rgba(22, 101, 52, 0.5) !important;
}
[data-hud='matrix'] .hud-zone .border-cyan-800\/60 {
border-color: rgba(22, 101, 52, 0.6) !important;
}
[data-hud='matrix'] .hud-zone .border-cyan-900\/50 {
border-color: rgba(20, 83, 45, 0.5) !important;
}
[data-hud='matrix'] .hud-zone .border-b-cyan-900 {
border-bottom-color: #14532d !important;
}
[data-hud='matrix'] .hud-zone .border-l-cyan-500 {
border-left-color: #22c55e !important;
}
/* --- Hover text --- */
[data-hud="matrix"] .hud-zone .hover\:text-cyan-300:hover { color: #86efac !important; }
[data-hud="matrix"] .hud-zone .hover\:text-cyan-400:hover { color: #4ade80 !important; }
[data-hud='matrix'] .hud-zone .hover\:text-cyan-300:hover {
color: #86efac !important;
}
[data-hud='matrix'] .hud-zone .hover\:text-cyan-400:hover {
color: #4ade80 !important;
}
/* --- Hover background --- */
[data-hud="matrix"] .hud-zone .hover\:bg-cyan-300:hover { background-color: #86efac !important; }
[data-hud="matrix"] .hud-zone .hover\:bg-cyan-500\/20:hover { background-color: rgba(34, 197, 94, 0.2) !important; }
[data-hud="matrix"] .hud-zone .hover\:bg-cyan-900\/50:hover { background-color: rgba(20, 83, 45, 0.5) !important; }
[data-hud="matrix"] .hud-zone .hover\:bg-cyan-950\/30:hover { background-color: rgba(5, 46, 22, 0.3) !important; }
[data-hud='matrix'] .hud-zone .hover\:bg-cyan-300:hover {
background-color: #86efac !important;
}
[data-hud='matrix'] .hud-zone .hover\:bg-cyan-500\/20:hover {
background-color: rgba(34, 197, 94, 0.2) !important;
}
[data-hud='matrix'] .hud-zone .hover\:bg-cyan-900\/50:hover {
background-color: rgba(20, 83, 45, 0.5) !important;
}
[data-hud='matrix'] .hud-zone .hover\:bg-cyan-950\/30:hover {
background-color: rgba(5, 46, 22, 0.3) !important;
}
/* --- Hover border --- */
[data-hud="matrix"] .hud-zone .hover\:border-cyan-300:hover { border-color: #86efac !important; }
[data-hud="matrix"] .hud-zone .hover\:border-cyan-500:hover { border-color: #22c55e !important; }
[data-hud="matrix"] .hud-zone .hover\:border-cyan-500\/40:hover { border-color: rgba(34, 197, 94, 0.4) !important; }
[data-hud="matrix"] .hud-zone .hover\:border-cyan-500\/50:hover { border-color: rgba(34, 197, 94, 0.5) !important; }
[data-hud="matrix"] .hud-zone .hover\:border-cyan-600:hover { border-color: #16a34a !important; }
[data-hud="matrix"] .hud-zone .hover\:border-cyan-800:hover { border-color: #166534 !important; }
[data-hud='matrix'] .hud-zone .hover\:border-cyan-300:hover {
border-color: #86efac !important;
}
[data-hud='matrix'] .hud-zone .hover\:border-cyan-500:hover {
border-color: #22c55e !important;
}
[data-hud='matrix'] .hud-zone .hover\:border-cyan-500\/40:hover {
border-color: rgba(34, 197, 94, 0.4) !important;
}
[data-hud='matrix'] .hud-zone .hover\:border-cyan-500\/50:hover {
border-color: rgba(34, 197, 94, 0.5) !important;
}
[data-hud='matrix'] .hud-zone .hover\:border-cyan-600:hover {
border-color: #16a34a !important;
}
[data-hud='matrix'] .hud-zone .hover\:border-cyan-800:hover {
border-color: #166534 !important;
}
/* --- Accent (range inputs) --- */
[data-hud="matrix"] .hud-zone .accent-cyan-500 { accent-color: #22c55e !important; }
[data-hud='matrix'] .hud-zone .accent-cyan-500 {
accent-color: #22c55e !important;
}
/* Focus mode: dim the map canvas (tiles + drawn layers) when a popup is active.
Inside MapLibre's DOM, .maplibregl-canvas-container is a SIBLING of .maplibregl-popup,
@@ -200,3 +366,94 @@ body {
.map-focus-active .maplibregl-popup {
z-index: 10 !important;
}
/* ── INFONET CRT TERMINAL EFFECTS ── */
.infonet-font {
font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
}
/* CRT scanline overlay — scoped to .crt containers only */
.crt {
position: relative;
animation: crt-flicker 0.15s infinite;
text-shadow: 0 0 2px rgba(255, 255, 255, 0.2);
}
.crt::before {
content: ' ';
display: block;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background:
linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%),
linear-gradient(
90deg,
rgba(255, 0, 0, 0.06),
rgba(0, 255, 0, 0.02),
rgba(0, 0, 255, 0.06)
);
z-index: 2;
background-size: 100% 2px, 3px 100%;
pointer-events: none;
}
@keyframes crt-flicker {
0% {
opacity: 0.95;
}
5% {
opacity: 0.85;
}
10% {
opacity: 0.95;
}
15% {
opacity: 1;
}
100% {
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.crt {
animation: none;
}
.crt::before {
display: none;
}
}
/* Ticker animation for InfoNet */
@keyframes infonet-ticker {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(-100%);
}
}
.animate-ticker {
display: inline-block;
white-space: nowrap;
animation: infonet-ticker 90s linear infinite;
}
/* Scoped scrollbar for CRT terminal */
.crt ::-webkit-scrollbar {
width: 8px;
}
.crt ::-webkit-scrollbar-track {
background: #0a0a0a;
}
.crt ::-webkit-scrollbar-thumb {
background: #4b5563;
}
.crt ::-webkit-scrollbar-thumb:hover {
background: #9ca3af;
}
+16 -22
View File
@@ -1,21 +1,11 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { ThemeProvider } from "@/lib/ThemeContext";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
import type { Metadata } from 'next';
import DesktopBridgeBootstrap from '@/components/DesktopBridgeBootstrap';
import { ThemeProvider } from '@/lib/ThemeContext';
import './globals.css';
export const metadata: Metadata = {
title: "WORLDVIEW // ORBITAL TRACKING",
description: "Advanced Geopolitical Risk Dashboard",
title: 'WORLDVIEW // ORBITAL TRACKING',
description: 'Advanced Geopolitical Risk Dashboard',
};
export default function RootLayout({
@@ -25,12 +15,16 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<head />
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased bg-[var(--bg-primary)]`}
suppressHydrationWarning
>
<ThemeProvider>{children}</ThemeProvider>
<head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet" />
</head>
<body className="antialiased bg-[var(--bg-primary)]" suppressHydrationWarning>
<ThemeProvider>
<DesktopBridgeBootstrap />
{children}
</ThemeProvider>
</body>
</html>
);
+945 -331
View File
File diff suppressed because it is too large Load Diff