feat: Build Manager, Crucible ops deck, branding, and portable Cloudflare tunnel

Add Build Manager with pin-to-dropper, Crucible multi-node terminal with SSH probe/wake, Command Deck chart balance and pretty stats, AetherForge logo and sacred geometry UI, Field Guide refresh, and LAUNCH.bat Cloudflare MSI + token service install flow.
This commit is contained in:
AetherForge
2026-05-30 22:38:48 -07:00
parent e6b8d84edf
commit 9232f4c448
45 changed files with 4222 additions and 406 deletions

View File

@@ -43,25 +43,43 @@
.logo-emblem {
position: relative;
width: 48px;
height: 48px;
width: 52px;
height: 52px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.logo-gear {
position: absolute;
inset: 0;
border: 2px dashed rgba(201, 162, 39, 0.4);
inset: -4px;
border: 1px solid rgba(201, 162, 39, 0.22);
border-radius: 50%;
animation: gear-spin 20s linear infinite;
animation: gear-spin 30s linear infinite;
}
.logo-core {
font-size: 1.5rem;
filter: drop-shadow(0 0 8px var(--neon-amber));
.logo-gear::after {
content: '';
position: absolute;
inset: 5px;
border: 1px dashed rgba(201, 162, 39, 0.14);
border-radius: 50%;
animation: gear-spin 18s linear infinite reverse;
}
.logo-af-img {
width: 48px;
height: 48px;
object-fit: contain;
border-radius: 50%;
filter: drop-shadow(0 0 10px rgba(201, 162, 39, 0.7)) drop-shadow(0 0 3px rgba(255, 100, 0, 0.5));
z-index: 1;
transition: filter 0.3s ease;
}
.logo-af-img:hover {
filter: drop-shadow(0 0 16px rgba(201, 162, 39, 0.95)) drop-shadow(0 0 6px rgba(255, 140, 0, 0.7));
}
.logo-text-block {

View File

@@ -4,6 +4,8 @@ import AmbientBackground from '../Ambient/AmbientBackground';
import SystemStatusBar from '../Visual/SystemStatusBar';
import { useWebSocket } from '../../hooks/useWebSocket';
import MatrixRain from './MatrixRain';
import afLogo from '../../assets/af-logo.png';
import CursorFire from '../Visual/CursorFire';
import './Layout.css';
interface LayoutProps {
@@ -13,7 +15,9 @@ interface LayoutProps {
const NAV = [
{ to: '/dashboard', label: 'Command Deck', icon: 'deck' },
{ to: '/agents', label: 'Fleet Roster', icon: 'fleet' },
{ to: '/crucible', label: 'Crucible', icon: 'crucible' },
{ to: '/forge', label: 'Forge', icon: 'forge' },
{ to: '/builds', label: 'Builds', icon: 'builds' },
{ to: '/guide', label: 'Field Guide', icon: 'guide' },
{ to: '/settings', label: 'Calibrate', icon: 'gear' },
] as const;
@@ -41,6 +45,15 @@ function NavIcon({ type }: { type: string }) {
<path d="M8 16l-2 4 4-2" />
</svg>
);
case 'builds':
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
<rect x="3" y="14" width="18" height="5" rx="1" />
<rect x="3" y="8" width="18" height="5" rx="1" />
<rect x="3" y="2" width="18" height="5" rx="1" />
<path d="M7 4.5h10M7 10.5h10M7 16.5h10" strokeOpacity="0.35" />
</svg>
);
case 'guide':
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
@@ -49,6 +62,15 @@ function NavIcon({ type }: { type: string }) {
<path d="M8 7h8M8 11h6" />
</svg>
);
case 'crucible':
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
<path d="M8 3h8l1 5H7L8 3z" />
<path d="M7 8c0 5 2 8 5 10c3-2 5-5 5-10" />
<path d="M4 21h16" />
<path d="M10 12l1.5 2L14 11" />
</svg>
);
default:
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
@@ -118,17 +140,18 @@ export default function Layout({ children }: LayoutProps) {
return (
<div className="layout">
<CursorFire />
<AmbientBackground />
<nav className="sidebar">
<div className="sidebar-header">
<div className="logo">
<div className="logo-emblem">
<span className="logo-gear" />
<span className="logo-core"></span>
<img src={afLogo} alt="AetherForge" className="logo-af-img" />
</div>
<div className="logo-text-block">
<span className="logo-text">AetherForge</span>
<span className="logo-tagline font-tech">LAN MINING COMMAND</span>
<span className="logo-tagline font-tech">COMMAND DECK</span>
</div>
</div>
</div>

View File

@@ -1,5 +1,6 @@
import { useEffect, useRef } from 'react';
import { useWebSocket } from '../../hooks/useWebSocket';
import { useForge } from '../../context/ForgeContext';
// Full matrix alphabet: katakana + hex + braille dots for visual density
const KATAKANA =
@@ -18,10 +19,22 @@ interface Column {
livePos: number;
}
const FORGE_STRINGS = [
'COMPILING', 'LINKING', 'GARBLE', 'GO BUILD', 'INJECT',
'STEALTH', 'PERSIST', 'ENCRYPT', 'OBFUSC', 'PACKAGE',
'WORKER', 'FORGE', 'SIGN', 'BUNDLE', 'AGENT',
'RANDOMX', 'STRATUM', 'C2CONN', 'DEPLOY',
];
export default function MatrixRain() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const wrapRef = useRef<HTMLDivElement>(null);
const { agents, recentShares, commandResults } = useWebSocket();
const { forging, stage } = useForge();
const forgingRef = useRef(false);
const stageRef = useRef('');
forgingRef.current = forging;
stageRef.current = stage;
// ── Live data pool ──────────────────────────────────────────────────────────
// Collect strings from the fleet that will be injected character-by-character
@@ -40,6 +53,13 @@ export default function MatrixRain() {
for (const r of (commandResults ?? []).slice(-5)) {
if (r.action) pool.push(r.action.toUpperCase().padEnd(8, '_'));
}
// When forging, flood the pool with build strings so they dominate the rain
if (forgingRef.current) {
pool.push(...FORGE_STRINGS);
if (stageRef.current) {
pool.push(stageRef.current.replace(/[^A-Z0-9]/gi, '').toUpperCase().slice(0, 16));
}
}
livePoolRef.current = pool.length > 0 ? pool : ['AETHERFORGE', 'MINING', '00E5FF'];
}, [agents, recentShares, commandResults]);
@@ -109,23 +129,27 @@ export default function MatrixRain() {
let raf: number;
let lastTime = 0;
const FPS = 24; // keep CPU gentle
const MS_PER_FRAME = 1000 / FPS;
const draw = (ts: number) => {
raf = requestAnimationFrame(draw);
if (ts - lastTime < MS_PER_FRAME) return;
const isForging = forgingRef.current;
const targetFps = isForging ? 50 : 24;
const msPerFrame = 1000 / targetFps;
if (ts - lastTime < msPerFrame) return;
lastTime = ts;
const W = canvas.width;
const H = canvas.height;
// Fade trail
ctx.fillStyle = 'rgba(0,0,0,0.18)';
// Forge mode: less fade = longer glowing trails; normal: quick fade
ctx.fillStyle = isForging ? 'rgba(0,0,0,0.10)' : 'rgba(0,0,0,0.18)';
ctx.fillRect(0, 0, W, H);
ctx.font = `${FONT_SIZE}px 'Courier New', monospace`;
// Speed multiplier: 3× faster while forging
const speedMult = isForging ? 3.2 : 1.0;
for (let i = 0; i < cols.length; i++) {
const col = cols[i];
const x = i * FONT_SIZE;
@@ -140,33 +164,52 @@ export default function MatrixRain() {
ch = ALPHABET[Math.floor(Math.random() * ALPHABET.length)];
}
// Head character — bright white flash
ctx.fillStyle = 'rgba(255,255,255,0.95)';
ctx.fillText(ch, x, y * FONT_SIZE);
// Second char — bright cyan-green (neon)
if (y > 1) {
ctx.fillStyle = '#00ff41';
ctx.fillText(
ALPHABET[Math.floor(Math.random() * ALPHABET.length)],
x,
(y - 1) * FONT_SIZE,
);
if (isForging) {
// Forge palette: bright amber/orange head, orange body
ctx.fillStyle = 'rgba(255,220,80,0.98)';
ctx.fillText(ch, x, y * FONT_SIZE);
if (y > 1) {
ctx.fillStyle = '#ff8c00';
ctx.fillText(
ALPHABET[Math.floor(Math.random() * ALPHABET.length)],
x,
(y - 1) * FONT_SIZE,
);
}
// Extra mid-column glyph density during forge
if (Math.random() < 0.12) {
const dimY = Math.floor(Math.random() * Math.max(1, y - 2));
ctx.fillStyle = 'rgba(255,120,0,0.35)';
ctx.fillText(
ALPHABET[Math.floor(Math.random() * ALPHABET.length)],
x,
dimY * FONT_SIZE,
);
}
} else {
// Normal palette: white head, cyan-green body
ctx.fillStyle = 'rgba(255,255,255,0.95)';
ctx.fillText(ch, x, y * FONT_SIZE);
if (y > 1) {
ctx.fillStyle = '#00ff41';
ctx.fillText(
ALPHABET[Math.floor(Math.random() * ALPHABET.length)],
x,
(y - 1) * FONT_SIZE,
);
}
if (Math.random() < 0.04) {
const dimY = Math.floor(Math.random() * Math.max(1, y - 2));
ctx.fillStyle = 'rgba(0,180,60,0.22)';
ctx.fillText(
ALPHABET[Math.floor(Math.random() * ALPHABET.length)],
x,
dimY * FONT_SIZE,
);
}
}
// Dim previous chars handled by fade overlay above.
// Occasionally render a mid-column dim glyph for density.
if (Math.random() < 0.04) {
const dimY = Math.floor(Math.random() * (y - 2));
ctx.fillStyle = 'rgba(0,180,60,0.22)';
ctx.fillText(
ALPHABET[Math.floor(Math.random() * ALPHABET.length)],
x,
dimY * FONT_SIZE,
);
}
col.y += col.speed;
col.y += col.speed * speedMult;
if (col.y * FONT_SIZE > H && Math.random() > 0.96) {
col.y = Math.random() * -20;
col.speed = 0.3 + Math.random() * 0.55;
@@ -179,13 +222,16 @@ export default function MatrixRain() {
const logs = eventLogRef.current;
const lineH = FONT_SIZE + 2;
ctx.font = `${FONT_SIZE - 1}px 'Courier New', monospace`;
const isForging2 = forgingRef.current;
for (let j = 0; j < logs.length; j++) {
const entry = logs[logs.length - 1 - j];
const oy = H - 6 - j * lineH;
if (oy < 0) break;
ctx.fillStyle = `rgba(0,255,65,${(entry.alpha * 0.55).toFixed(2)})`;
const logColor = isForging2
? `rgba(255,160,0,${(entry.alpha * 0.75).toFixed(2)})`
: `rgba(0,255,65,${(entry.alpha * 0.55).toFixed(2)})`;
ctx.fillStyle = logColor;
ctx.fillText(`> ${entry.text}`, 4, oy);
// fade over time
entry.alpha = Math.max(0, entry.alpha - 0.003);
}
};