diff --git a/br95/app/globals.css b/br95/app/globals.css new file mode 100644 index 0000000..195b0df --- /dev/null +++ b/br95/app/globals.css @@ -0,0 +1,749 @@ + :root { + /* Brand system */ + --br-black: #02030a; + --br-bg-elevated: #050816; + --br-bg-alt: #090c1f; + --br-white: #ffffff; + --br-muted: #a7b0c7; + --br-border-subtle: rgba(255,255,255,0.08); + --br-accent-warm: #ff9a3c; /* top of logo gradient */ + --br-accent-mid: #ff4fa3; /* magenta */ + --br-accent-cool: #327cff; /* electric blue */ + --br-accent-neo: #69f7ff; /* cyan */ + --br-radius-lg: 24px; + --br-radius-md: 16px; + --br-radius-sm: 10px; + --br-shadow-soft: 0 18px 45px rgba(0,0,0,0.45); + --br-font-sans: system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif; + --br-font-mono: "JetBrains Mono", ui-monospace, Menlo, Monaco, Consolas, monospace; + + /* BR‑95 chrome (retro-style, brand-colored) */ + --br95-gray: #181c2a; + --br95-gray-light: #232842; + --br95-gray-lighter: #2f3554; + --br95-border-light: #4b5378; + --br95-border-dark: #050816; + --br95-border-darkest: #000000; + } + + * { + margin: 0; + padding: 0; + box-sizing: border-box; + } + + body { + font-family: var(--br-font-sans); + background: radial-gradient(circle at top, #121735 0, var(--br-black) 60%); + height: 100vh; + overflow: hidden; + color: var(--br-white); + -webkit-font-smoothing: antialiased; + } + + /* CRT scanline overlay */ + .scanline { + position: fixed; + inset: 0; + background: linear-gradient(to bottom, transparent 50%, rgba(255,255,255,0.06) 50%); + background-size: 100% 3px; + mix-blend-mode: soft-light; + opacity: 0.4; + pointer-events: none; + z-index: 9999; + } + + /* Boot screen (from Desktop OS vibe) */ + .boot-screen { + position: fixed; + inset: 0; + background: var(--br-black); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 9000; + animation: bootFadeOut 0.6s ease 2.6s forwards; + } + + @keyframes bootFadeOut { + to { + opacity: 0; + visibility: hidden; + } + } + + .boot-logo-container { + text-align: center; + animation: bootIn 0.7s ease-out; + } + + @keyframes bootIn { + from { opacity: 0; transform: translateY(16px); } + to { opacity: 1; transform: translateY(0); } + } + + .boot-logo { + width: 180px; + height: 180px; + margin: 0 auto 28px; + background: conic-gradient(from 180deg, + var(--br-accent-warm), + var(--br-accent-mid), + var(--br-accent-cool), + var(--br-accent-warm)); + border-radius: 38px; + display: flex; + align-items: center; + justify-content: center; + position: relative; + box-shadow: + 0 0 60px rgba(255,154,60,0.35), + 0 0 100px rgba(255,79,163,0.28), + 0 20px 40px rgba(0,0,0,0.8); + animation: logoGlow 2s ease-in-out infinite; + } + + @keyframes logoGlow { + 0%,100% { + box-shadow: + 0 0 60px rgba(255,154,60,0.35), + 0 0 100px rgba(255,79,163,0.28), + 0 20px 40px rgba(0,0,0,0.8); + } + 50% { + box-shadow: + 0 0 80px rgba(255,154,60,0.5), + 0 0 120px rgba(255,79,163,0.4), + 0 20px 40px rgba(0,0,0,0.9); + } + } + + .boot-logo::before { + content: 'R'; + font-size: 130px; + font-weight: 900; + color: var(--br-black); + font-family: var(--br-font-sans); + position: relative; + line-height: 1; + } + + .boot-road { + position: absolute; + width: 34px; + height: 110px; + background: linear-gradient(to bottom, + transparent 0%, + rgba(255,255,255,0.98) 30%, + rgba(255,255,255,0.98) 70%, + transparent 100%); + border-radius: 17px; + left: 50%; + top: 50%; + transform: translate(-50%, -50%) rotate(-18deg); + opacity: 0.96; + } + + .boot-title { + background: linear-gradient(120deg, + var(--br-accent-warm), + var(--br-accent-mid), + var(--br-accent-cool)); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + font-size: 28px; + font-weight: 700; + letter-spacing: 0.14em; + text-transform: uppercase; + margin-bottom: 10px; + } + + .boot-sub { + font-size: 12px; + color: var(--br-muted); + letter-spacing: 0.08em; + text-transform: uppercase; + } + + .boot-loading { + margin-top: 18px; + font-size: 11px; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--br-muted); + } + + .boot-loading::after { + content: '...'; + animation: dots 1.2s infinite; + } + + @keyframes dots { + 0%,20% { content: '.'; } + 40% { content: '..'; } + 60%,100% { content: '...'; } + } + + /* Desktop shell */ + .shell { + position: relative; + display: flex; + flex-direction: column; + height: 100vh; + opacity: 0; + pointer-events: none; + transition: opacity 0.4s ease; + } + + .shell.ready { + opacity: 1; + pointer-events: auto; + } + + /* Top menubar (from Desktop OS) */ + .menubar { + background: linear-gradient(180deg, rgba(5,8,22,0.98) 0%, rgba(2,3,10,0.98) 100%); + backdrop-filter: blur(18px); + padding: 8px 16px; + display: flex; + align-items: center; + border-bottom: 1px solid var(--br-border-subtle); + box-shadow: 0 4px 24px rgba(0,0,0,0.5); + z-index: 50; + } + + .menu-logo { + width: 28px; + height: 28px; + border-radius: 8px; + background: conic-gradient(from 180deg, + var(--br-accent-warm), + var(--br-accent-mid), + var(--br-accent-cool), + var(--br-accent-warm)); + margin-right: 14px; + box-shadow: 0 2px 10px rgba(0,0,0,0.7); + } + + .menu-items { + display: flex; + gap: 6px; + flex: 1; + } + + .menu-item { + padding: 4px 10px; + border-radius: 8px; + font-size: 12px; + color: var(--br-white); + cursor: pointer; + transition: background 0.14s ease, transform 0.14s ease; + } + + .menu-item:hover { + background: rgba(255,255,255,0.05); + transform: translateY(-1px); + } + + .system-info { + display: flex; + gap: 10px; + font-size: 11px; + color: var(--br-muted); + font-family: var(--br-font-mono); + } + + .system-info span { + padding: 3px 8px; + border-radius: 6px; + border: 1px solid rgba(255,255,255,0.1); + background: rgba(255,255,255,0.03); + } + + /* Desktop icons grid (retro layout) */ + .desktop-area { + flex: 1; + padding: 20px; + display: grid; + grid-template-columns: repeat(auto-fill, 90px); + grid-auto-rows: 95px; + gap: 22px; + align-content: start; + background-image: + linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px); + background-size: 24px 24px; + position: relative; + overflow: auto; + } + + .icon { + width: 86px; + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + user-select: none; + transition: transform 0.15s ease; + } + + .icon:hover { + transform: translateY(-3px); + } + + .icon-image { + width: 64px; + height: 64px; + border-radius: var(--br-radius-md); + border: 1px solid var(--br-border-subtle); + background: linear-gradient(135deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02)); + display: flex; + align-items: center; + justify-content: center; + font-size: 34px; + margin-bottom: 8px; + box-shadow: 0 4px 18px rgba(0,0,0,0.55); + position: relative; + backdrop-filter: blur(10px); + } + + .icon-image::after { + content: ''; + position: absolute; + inset: 0; + border-radius: var(--br-radius-md); + background: radial-gradient(circle at 18% 18%, rgba(255,255,255,0.18), transparent 60%); + pointer-events: none; + } + + .icon-label { + text-align: center; + font-size: 12px; + font-weight: 500; + text-shadow: 0 2px 8px rgba(0,0,0,0.8); + } + + /* BR‑95 window chrome (retro + brand) */ + .window { + position: absolute; + background: var(--br95-gray); + border-radius: 10px; + border-top: 2px solid var(--br95-border-light); + border-left: 2px solid var(--br95-border-light); + border-right: 2px solid var(--br95-border-darkest); + border-bottom: 2px solid var(--br95-border-darkest); + box-shadow: + inset 1px 1px 0 var(--br95-gray-lighter), + 0 14px 40px rgba(0,0,0,0.7); + min-width: 420px; + min-height: 260px; + display: none; + z-index: 10; + overflow: hidden; + } + + .window.active { + display: flex; + flex-direction: column; + } + + .window.maximized { + left: 0 !important; + top: 36px !important; + width: 100% !important; + height: calc(100vh - 36px - 40px) !important; + border-radius: 0; + z-index: 100; + } + + .title-bar { + height: 26px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 3px 4px 3px 6px; + background: linear-gradient(90deg, + var(--br-accent-warm), + var(--br-accent-mid), + var(--br-accent-cool)); + color: var(--br-white); + cursor: move; + user-select: none; + font-size: 12px; + font-weight: 600; + } + + .title-text { + display: flex; + align-items: center; + gap: 6px; + text-shadow: 0 1px 2px rgba(0,0,0,0.6); + } + + .title-buttons { + display: flex; + gap: 2px; + } + + .title-button { + width: 18px; + height: 18px; + background: var(--br95-gray-light); + border-top: 1px solid var(--br95-border-light); + border-left: 1px solid var(--br95-border-light); + border-right: 1px solid var(--br95-border-darkest); + border-bottom: 1px solid var(--br95-border-darkest); + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + font-weight: 700; + color: var(--br-black); + cursor: pointer; + box-shadow: inset 1px 1px 0 rgba(255,255,255,0.4); + } + + .title-button:active { + border-top: 1px solid var(--br95-border-darkest); + border-left: 1px solid var(--br95-border-darkest); + border-right: 1px solid var(--br95-border-light); + border-bottom: 1px solid var(--br95-border-light); + box-shadow: inset 1px 1px 2px rgba(0,0,0,0.4); + } + + .window-inner { + flex: 1; + display: flex; + flex-direction: column; + background: var(--br-bg-elevated); + padding: 10px; + color: var(--br-white); + font-size: 13px; + } + + .content-header { + padding: 10px 12px; + border-radius: 8px; + background: linear-gradient(135deg, + rgba(255,154,60,0.08), + rgba(255,79,163,0.08), + rgba(50,124,255,0.08)); + border: 1px solid rgba(255,255,255,0.06); + margin-bottom: 10px; + } + + .content-header h2 { + font-size: 15px; + margin-bottom: 4px; + } + + .content-header p { + font-size: 12px; + color: var(--br-muted); + } + + .content-body { + flex: 1; + overflow: auto; + padding: 4px 2px 2px; + } + + .grid { + display: grid; + gap: 10px; + } + + .grid-2 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); } + + .card { + background: rgba(4,10,32,0.98); + border-radius: 14px; + border: 1px solid var(--br-border-subtle); + padding: 10px 12px; + box-shadow: 0 8px 20px rgba(0,0,0,0.55); + } + + .stat-row { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 12px; + padding: 4px 0; + border-bottom: 1px solid rgba(255,255,255,0.04); + } + + .stat-row:last-child { border-bottom: none; } + + .stat-label { color: var(--br-muted); } + .stat-value { font-family: var(--br-font-mono); } + + .badge { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: 999px; + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + } + + .badge-success { + background: rgba(34,197,94,0.16); + border: 1px solid rgba(34,197,94,0.5); + color: #22c55e; + } + + .badge-info { + background: rgba(105,247,255,0.16); + border: 1px solid rgba(105,247,255,0.45); + color: var(--br-accent-neo); + } + + .badge-warning { + background: rgba(251,191,36,0.16); + border: 1px solid rgba(251,191,36,0.45); + color: #fbbf24; + } + + .btn-primary { + padding: 7px 16px; + border-radius: 999px; + border: none; + background: linear-gradient(135deg, var(--br-accent-warm), var(--br-accent-mid)); + color: var(--br-black); + font-size: 12px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 6px 16px rgba(0,0,0,0.7); + transition: transform 0.14s ease, box-shadow 0.14s ease, filter 0.14s ease; + } + + .btn-primary:hover { + transform: translateY(-1px); + filter: brightness(1.05); + box-shadow: 0 10px 22px rgba(0,0,0,0.9); + } + + .btn-primary:active { + transform: translateY(1px) scale(0.99); + box-shadow: 0 3px 10px rgba(0,0,0,0.8); + } + + /* Terminal styling */ + .terminal-screen { + background: var(--br-black); + color: var(--br-accent-neo); + font-family: var(--br-font-mono); + padding: 12px; + font-size: 12px; + height: 100%; + border-radius: 10px; + overflow-y: auto; + } + + .terminal-line { margin-bottom: 4px; } + + .terminal-prompt { color: var(--br-accent-mid); } + + .terminal-cursor { + display: inline-block; + width: 8px; + height: 14px; + background: var(--br-accent-neo); + margin-left: 2px; + animation: blink 1s steps(1) infinite; + } + + @keyframes blink { + 0%,49% { opacity: 1; } + 50%,100% { opacity: 0; } + } + + /* Taskbar – BR‑95 style */ + .taskbar { + height: 40px; + background: var(--br95-gray-light); + border-top: 2px solid var(--br95-border-light); + display: flex; + align-items: center; + padding: 4px; + gap: 4px; + box-shadow: 0 -6px 18px rgba(0,0,0,0.7); + z-index: 60; + } + + .road-button { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + background: var(--br95-gray); + border-top: 2px solid var(--br95-border-light); + border-left: 2px solid var(--br95-border-light); + border-right: 2px solid var(--br95-border-darkest); + border-bottom: 2px solid var(--br95-border-darkest); + cursor: pointer; + font-size: 13px; + font-weight: 700; + color: var(--br-white); + box-shadow: inset 1px 1px 0 rgba(255,255,255,0.4); + } + + .road-logo { + width: 22px; + height: 22px; + border-radius: 4px; + background: conic-gradient(from 180deg, + var(--br-accent-warm), + var(--br-accent-mid), + var(--br-accent-cool), + var(--br-accent-warm)); + display: inline-block; + } + + .taskbar-apps { + flex: 1; + display: flex; + gap: 4px; + padding: 0 6px; + overflow-x: auto; + } + + .taskbar-app { + min-width: 120px; + max-width: 160px; + height: 30px; + background: var(--br95-gray); + border-top: 2px solid var(--br95-border-light); + border-left: 2px solid var(--br95-border-light); + border-right: 2px solid var(--br95-border-darkest); + border-bottom: 2px solid var(--br95-border-darkest); + padding: 0 8px; + display: flex; + align-items: center; + font-size: 12px; + color: var(--br-white); + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + box-shadow: inset 1px 1px 0 rgba(255,255,255,0.25); + } + + .taskbar-app.active-app { + border-top: 2px solid var(--br95-border-darkest); + border-left: 2px solid var(--br95-border-darkest); + border-right: 2px solid var(--br95-border-light); + border-bottom: 2px solid var(--br95-border-light); + box-shadow: inset 1px 1px 2px rgba(0,0,0,0.5); + background: var(--br95-gray-lighter); + } + + .system-tray { + display: flex; + align-items: center; + gap: 6px; + padding: 0 6px; + color: var(--br-white); + font-size: 14px; + } + + .clock { + min-width: 80px; + padding: 3px 8px; + border-top: 2px solid var(--br95-border-darkest); + border-left: 2px solid var(--br95-border-darkest); + border-right: 2px solid var(--br95-border-light); + border-bottom: 2px solid var(--br95-border-light); + font-size: 12px; + text-align: center; + background: var(--br95-gray); + font-family: var(--br-font-mono); + } + + /* Road menu (Start menu but BlackRoad) */ + .road-menu { + position: fixed; + left: 4px; + bottom: 44px; + width: 280px; + background: rgba(4,10,32,0.98); + border-radius: 12px; + border: 1px solid rgba(255,255,255,0.18); + box-shadow: 0 18px 50px rgba(0,0,0,0.85); + display: none; + flex-direction: column; + overflow: hidden; + z-index: 80; + } + + .road-menu.active { display: flex; } + + .road-menu-header { + padding: 14px; + background: linear-gradient(135deg, + rgba(255,154,60,0.2), + rgba(255,79,163,0.18), + rgba(50,124,255,0.18)); + border-bottom: 1px solid rgba(255,255,255,0.18); + } + + .road-menu-header h3 { + font-size: 15px; + margin-bottom: 4px; + } + + .road-menu-header p { + font-size: 11px; + color: var(--br-muted); + } + + .road-menu-content { + padding: 6px; + max-height: 60vh; + overflow-y: auto; + background: rgba(2,5,18,0.96); + } + + .road-menu-item { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 8px; + border-radius: 8px; + font-size: 12px; + cursor: pointer; + color: var(--br-muted); + transition: background 0.12s ease, color 0.12s ease; + } + + .road-menu-item span.emoji { font-size: 18px; } + + .road-menu-item:hover { + background: rgba(255,255,255,0.07); + color: var(--br-white); + } + + .road-menu-separator { + height: 1px; + margin: 4px 4px; + background: rgba(255,255,255,0.12); + } + + /* Scrollbars */ + ::-webkit-scrollbar { width: 12px; height: 12px; } + ::-webkit-scrollbar-track { + background: var(--br95-gray); + border-left: 1px solid var(--br95-border-darkest); + } + ::-webkit-scrollbar-thumb { + background: var(--br95-gray-light); + border-top: 2px solid var(--br95-border-light); + border-left: 2px solid var(--br95-border-light); + border-right: 2px solid var(--br95-border-darkest); + border-bottom: 2px solid var(--br95-border-darkest); + } + \ No newline at end of file diff --git a/br95/app/layout.tsx b/br95/app/layout.tsx new file mode 100644 index 0000000..47f42ab --- /dev/null +++ b/br95/app/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from 'next'; +import './globals.css'; + +export const metadata: Metadata = { + title: 'BlackRoad OS – BR‑95 Edition', + description: 'BlackRoad OS retro desktop rebuilt as modern Next.js components.', +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/br95/app/page.tsx b/br95/app/page.tsx new file mode 100644 index 0000000..96f0604 --- /dev/null +++ b/br95/app/page.tsx @@ -0,0 +1,7 @@ +'use client'; + +import DesktopLayout from '../components/DesktopLayout'; + +export default function HomePage() { + return ; +} diff --git a/br95/components/DesktopLayout.tsx b/br95/components/DesktopLayout.tsx new file mode 100644 index 0000000..58d0d3e --- /dev/null +++ b/br95/components/DesktopLayout.tsx @@ -0,0 +1,200 @@ +import React from 'react'; +import AgentsWindow from './windows/AgentsWindow'; +import BlackstreamWindow from './windows/BlackstreamWindow'; +import LucidiaWindow from './windows/LucidiaWindow'; +import MinerWindow from './windows/MinerWindow'; +import PiWindow from './windows/PiWindow'; +import RoadchainWindow from './windows/RoadchainWindow'; +import RoadcraftWindow from './windows/RoadcraftWindow'; +import RoadmailWindow from './windows/RoadmailWindow'; +import RoadviewWindow from './windows/RoadviewWindow'; +import SocialWindow from './windows/SocialWindow'; +import TerminalWindow from './windows/TerminalWindow'; +import WalletWindow from './windows/WalletWindow'; +import BootScreen from './shared/BootScreen'; +import Taskbar from './shared/Taskbar'; +import RoadMenu from './shared/RoadMenu'; +import { useWindowManager, WindowId } from '../hooks/useWindowManager'; + +const desktopIcons: { id: WindowId; icon: string; label: string }[] = [ + { id: 'lucidia', icon: '🧠', label: 'Lucidia Core' }, + { id: 'agents', icon: '🤖', label: 'AI Agents' }, + { id: 'roadchain', icon: '⛓️', label: 'RoadChain' }, + { id: 'wallet', icon: '💰', label: 'Wallet' }, + { id: 'terminal', icon: '💻', label: 'Terminal' }, + { id: 'roadmail', icon: '📧', label: 'RoadMail' }, + { id: 'social', icon: '👥', label: 'Social' }, + { id: 'blackstream', icon: '📺', label: 'BlackStream' }, + { id: 'roadview', icon: '🌍', label: 'RoadView' }, + { id: 'pi', icon: '🥧', label: 'Pi Network' }, + { id: 'miner', icon: '⛏️', label: 'Miner' }, + { id: 'roadcraft', icon: '⛏️', label: 'RoadCraft' }, +]; + +export default function DesktopLayout() { + const { + windowStates, + openWindows, + activeWindow, + roadMenuOpen, + shellReady, + clockText, + lucidiaStats, + roadchainStats, + walletStats, + minerStats, + menuRef, + menuButtonRef, + openWindow, + closeWindow, + minimizeWindow, + maximizeWindow, + startDrag, + focusWindow, + toggleRoadMenu, + taskbarToggle, + } = useWindowManager(); + + return ( + <> +
+ +
+
+
+
+
File
+
View
+
Agents
+
RoadChain
+
+
+ BlackRoad OS v1.0 + Agents: 1000 +
+
+ +
+ {desktopIcons.map((item) => ( +
openWindow(item.id)}> +
{item.icon}
+
{item.label}
+
+ ))} +
+ + + + + + + + + + + + + + + taskbarToggle(id)} + onToggleMenu={toggleRoadMenu} + menuButtonRef={menuButtonRef} + /> + + openWindow(id)} menuRef={menuRef} /> +
+ + ); +} diff --git a/br95/components/shared/BootScreen.tsx b/br95/components/shared/BootScreen.tsx new file mode 100644 index 0000000..fedaad2 --- /dev/null +++ b/br95/components/shared/BootScreen.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +type Props = { + bootActive: boolean; +}; + +export default function BootScreen({ bootActive }: Props) { + return ( +
+
+
+
+
+
BlackRoad OS
+
BR‑95 Edition · Safe Passage Through The Chaos
+
System Loading
+
+
+ ); +} diff --git a/br95/components/shared/RoadMenu.tsx b/br95/components/shared/RoadMenu.tsx new file mode 100644 index 0000000..12d1a30 --- /dev/null +++ b/br95/components/shared/RoadMenu.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { WindowId } from '../../hooks/useWindowManager'; + +type Props = { + isOpen: boolean; + onOpenWindow: (id: WindowId) => void; + menuRef: React.RefObject; +}; + +const items: { id: WindowId; icon: string; label: string }[] = [ + { id: 'lucidia', icon: '🧠', label: 'Lucidia Core' }, + { id: 'agents', icon: '🤖', label: 'AI Agents' }, + { id: 'roadchain', icon: '⛓️', label: 'RoadChain Explorer' }, + { id: 'wallet', icon: '💰', label: 'RoadCoin Wallet' }, + { id: 'roadmail', icon: '📧', label: 'RoadMail' }, + { id: 'terminal', icon: '💻', label: 'Terminal' }, + { id: 'pi', icon: '🥧', label: 'Pi Network' }, + { id: 'miner', icon: '⛏️', label: 'RoadCoin Miner' }, + { id: 'roadcraft', icon: '⛏️', label: 'RoadCraft' }, +]; + +export default function RoadMenu({ isOpen, onOpenWindow, menuRef }: Props) { + return ( +
+
+

BlackRoad OS

+

BR‑95 Desktop · Agent Orchestration

+
+
+ {items.map((item, index) => ( + +
onOpenWindow(item.id)}> + {item.icon} + {item.label} +
+ {index === 2 || index === 5 ?
: null} +
+ ))} +
+
+ ); +} diff --git a/br95/components/shared/Taskbar.tsx b/br95/components/shared/Taskbar.tsx new file mode 100644 index 0000000..da960c3 --- /dev/null +++ b/br95/components/shared/Taskbar.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { WindowId } from '../../hooks/useWindowManager'; + +type Props = { + openWindows: WindowId[]; + activeWindow: WindowId | null; + clockText: string; + onTaskbarClick: (id: WindowId) => void; + onToggleMenu: () => void; + menuButtonRef: React.RefObject; +}; + +const titles: Record = { + lucidia: '🧠 Lucidia', + agents: '🤖 Agents', + roadchain: '⛓️ Chain', + wallet: '💰 Wallet', + roadmail: '📧 Mail', + social: '👥 Social', + blackstream: '📺 Stream', + roadview: '🌍 RoadView', + terminal: '💻 Terminal', + pi: '🥧 Pi', + miner: '⛏️ Miner', + roadcraft: '⛏️ RoadCraft', +}; + +export default function Taskbar({ + openWindows, + activeWindow, + clockText, + onTaskbarClick, + onToggleMenu, + menuButtonRef, +}: Props) { + return ( +
+
+ + Road +
+
+ {openWindows.map((id) => ( +
onTaskbarClick(id)} + > + {titles[id] ?? id} +
+ ))} +
+
+ 🌐 + 🔊 + ⛓️ +
+
+ {clockText} +
+
+ ); +} diff --git a/br95/components/shared/WindowFrame.tsx b/br95/components/shared/WindowFrame.tsx new file mode 100644 index 0000000..751714b --- /dev/null +++ b/br95/components/shared/WindowFrame.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { WindowState } from '../../hooks/useWindowManager'; + +type Props = { + id: string; + title: string; + icon: string; + state: WindowState; + children: React.ReactNode; + onClose: (id: string) => void; + onMinimize: (id: string) => void; + onMaximize: (id: string) => void; + onDragStart: (id: string, event: React.MouseEvent) => void; + onFocus: (id: string) => void; +}; + +export default function WindowFrame({ + id, + title, + icon, + state, + children, + onClose, + onMinimize, + onMaximize, + onDragStart, + onFocus, +}: Props) { + if (!state) return null; + + const style = state.isMaximized + ? { zIndex: state.zIndex } + : { + left: state.position.x, + top: state.position.y, + width: state.size.width, + height: state.size.height, + zIndex: state.zIndex, + }; + + return ( +
onFocus(id)} + > +
onDragStart(id, e)}> +
+ {icon} + {title} +
+
+
onMinimize(id)}> + _ +
+
onMaximize(id)}> + □ +
+
onClose(id)}> + × +
+
+
+ {children} +
+ ); +} diff --git a/br95/components/windows/AgentsWindow.tsx b/br95/components/windows/AgentsWindow.tsx new file mode 100644 index 0000000..699b702 --- /dev/null +++ b/br95/components/windows/AgentsWindow.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import WindowFrame from '../shared/WindowFrame'; +import { WindowState } from '../../hooks/useWindowManager'; + +type Props = { + state: WindowState; + onClose: (id: string) => void; + onMinimize: (id: string) => void; + onMaximize: (id: string) => void; + onDragStart: (id: string, event: React.MouseEvent) => void; + onFocus: (id: string) => void; +}; + +export default function AgentsWindow({ state, ...handlers }: Props) { + return ( + +
+
+

1,000 Unique Agents

+

Each with names, memories, families & Unity homes

+
+
+
+
+
🤖
+
IDAgent‑042
+
PersonaAlice
+
StatusActive
+
Tasks Today247
+
+
+
🤖
+
IDAgent‑189
+
PersonaMarcus
+
StatusActive
+
Tasks Today189
+
+
+
+ +
+
+
+
+ ); +} diff --git a/br95/components/windows/BlackstreamWindow.tsx b/br95/components/windows/BlackstreamWindow.tsx new file mode 100644 index 0000000..ad9b5c4 --- /dev/null +++ b/br95/components/windows/BlackstreamWindow.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import WindowFrame from '../shared/WindowFrame'; +import { WindowState } from '../../hooks/useWindowManager'; + +type Props = { + state: WindowState; + onClose: (id: string) => void; + onMinimize: (id: string) => void; + onMaximize: (id: string) => void; + onDragStart: (id: string, event: React.MouseEvent) => void; + onFocus: (id: string) => void; +}; + +export default function BlackstreamWindow({ state, ...handlers }: Props) { + return ( + +
+
+

Trending Streams

+

OS demos, agent orchestration runs, and live RoadChain dashboards

+
+
+
+ Embedded stream player coming soon. +
+
+
+
+ ); +} diff --git a/br95/components/windows/LucidiaWindow.tsx b/br95/components/windows/LucidiaWindow.tsx new file mode 100644 index 0000000..6eb6ba4 --- /dev/null +++ b/br95/components/windows/LucidiaWindow.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import WindowFrame from '../shared/WindowFrame'; +import { LucidiaStats, WindowState } from '../../hooks/useWindowManager'; + +type Props = { + state: WindowState; + stats: LucidiaStats; + onClose: (id: string) => void; + onMinimize: (id: string) => void; + onMaximize: (id: string) => void; + onDragStart: (id: string, event: React.MouseEvent) => void; + onFocus: (id: string) => void; +}; + +export default function LucidiaWindow({ state, stats, ...handlers }: Props) { + return ( + +
+
+

Recursive AI Engine

+

Trinary logic · Paraconsistent reasoning · PS‑SHA∞ memory

+
+
+
+
+
+ Active Agents + {stats.activeAgents} / {stats.totalAgents} +
+
+ Memory Journals + {stats.memoryJournals} streams +
+
+ Event Bus + {stats.eventBusRate} events/sec +
+
+ Uptime + {stats.uptime.toFixed(2)}% +
+
+
+
+ Mode + Trinary (1/0/‑1) +
+
+ Contradictions + Paraconsistent +
+
+ Memory Hash + PS‑SHA∞ +
+
+ Coordination + Hybrid P2P +
+
+
+
+
+
+ Agent‑042 proposed new routing rule • 2m ago +
+
+ Agent‑189 memory journal sync complete • 5m ago +
+
+ Agent‑734 contradiction quarantined • 12m ago +
+
+
+
+
+
+ ); +} diff --git a/br95/components/windows/MinerWindow.tsx b/br95/components/windows/MinerWindow.tsx new file mode 100644 index 0000000..31502b8 --- /dev/null +++ b/br95/components/windows/MinerWindow.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import WindowFrame from '../shared/WindowFrame'; +import { MinerStats, WindowState } from '../../hooks/useWindowManager'; + +type Props = { + state: WindowState; + stats: MinerStats; + onClose: (id: string) => void; + onMinimize: (id: string) => void; + onMaximize: (id: string) => void; + onDragStart: (id: string, event: React.MouseEvent) => void; + onFocus: (id: string) => void; +}; + +export default function MinerWindow({ state, stats, ...handlers }: Props) { + return ( + +
+
+

Mining Active

+

Contributing work to RoadChain network

+
+
+
+
Hashrate{stats.hashRate}
+
Accepted Shares{stats.sharesAccepted.toLocaleString()}
+
Pool{stats.poolName}
+
+
+
+
+ ); +} diff --git a/br95/components/windows/PiWindow.tsx b/br95/components/windows/PiWindow.tsx new file mode 100644 index 0000000..2ddd5e4 --- /dev/null +++ b/br95/components/windows/PiWindow.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import WindowFrame from '../shared/WindowFrame'; +import { WindowState } from '../../hooks/useWindowManager'; + +type Props = { + state: WindowState; + onClose: (id: string) => void; + onMinimize: (id: string) => void; + onMaximize: (id: string) => void; + onDragStart: (id: string, event: React.MouseEvent) => void; + onFocus: (id: string) => void; +}; + +export default function PiWindow({ state, ...handlers }: Props) { + return ( + +
+
+

Connected Devices

+

4 devices online · 1 Jetson Orin Nano

+
+
+
+
Jetson Orin NanoOnline
+
Lucidia‑Pi‑01Online
+
+
+
+
+ ); +} diff --git a/br95/components/windows/RoadchainWindow.tsx b/br95/components/windows/RoadchainWindow.tsx new file mode 100644 index 0000000..0d3a4f5 --- /dev/null +++ b/br95/components/windows/RoadchainWindow.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import WindowFrame from '../shared/WindowFrame'; +import { RoadChainStats, WindowState } from '../../hooks/useWindowManager'; + +type Props = { + state: WindowState; + stats: RoadChainStats; + onClose: (id: string) => void; + onMinimize: (id: string) => void; + onMaximize: (id: string) => void; + onDragStart: (id: string, event: React.MouseEvent) => void; + onFocus: (id: string) => void; +}; + +export default function RoadchainWindow({ state, stats, ...handlers }: Props) { + return ( + +
+
+

RoadChain Network

+

Transparent AI governance on a distributed ledger

+
+
+
+
+
Block Height{stats.currentBlock.toLocaleString()}
+
Network Hash{stats.networkHashrate}
+
Active Nodes{stats.activeNodes.toLocaleString()}
+
+
+
Your Hashrate{stats.yourHashrate}
+
Shares{stats.shares}
+
Daily Earnings{stats.dailyEarnings}
+
+
+
+
+
+ Block #1,247,891 • 23s ago
+ Hash: 0x8f4a2...c7b9d • 247 tx +
+
+ Block #1,247,890 • 2m ago
+ Hash: 0x3c9e1...f2a4b • 189 tx +
+
+ Block #1,247,889 • 4m ago
+ Hash: 0x7d2b3...e8c1f • 312 tx +
+
+
+
+
+
+ ); +} diff --git a/br95/components/windows/RoadcraftWindow.tsx b/br95/components/windows/RoadcraftWindow.tsx new file mode 100644 index 0000000..ecb502b --- /dev/null +++ b/br95/components/windows/RoadcraftWindow.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import WindowFrame from '../shared/WindowFrame'; +import { WindowState } from '../../hooks/useWindowManager'; + +type Props = { + state: WindowState; + onClose: (id: string) => void; + onMinimize: (id: string) => void; + onMaximize: (id: string) => void; + onDragStart: (id: string, event: React.MouseEvent) => void; + onFocus: (id: string) => void; +}; + +export default function RoadcraftWindow({ state, ...handlers }: Props) { + return ( + +
+
+

Voxel World Builder

+

Design agent habitats & quantum sandboxes

+
+
+ +
+
+
+ ); +} diff --git a/br95/components/windows/RoadmailWindow.tsx b/br95/components/windows/RoadmailWindow.tsx new file mode 100644 index 0000000..f7e7c33 --- /dev/null +++ b/br95/components/windows/RoadmailWindow.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import WindowFrame from '../shared/WindowFrame'; +import { WindowState } from '../../hooks/useWindowManager'; + +type Props = { + state: WindowState; + onClose: (id: string) => void; + onMinimize: (id: string) => void; + onMaximize: (id: string) => void; + onDragStart: (id: string, event: React.MouseEvent) => void; + onFocus: (id: string) => void; +}; + +export default function RoadmailWindow({ state, ...handlers }: Props) { + return ( + +
+
+

Inbox

+

Secure AI‑aware communication

+
+
+
+
Welcome to BlackRoad OS
+
From: BlackRoad Team • Today
+
+
+
+
+ ); +} diff --git a/br95/components/windows/RoadviewWindow.tsx b/br95/components/windows/RoadviewWindow.tsx new file mode 100644 index 0000000..a384649 --- /dev/null +++ b/br95/components/windows/RoadviewWindow.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import WindowFrame from '../shared/WindowFrame'; +import { WindowState } from '../../hooks/useWindowManager'; + +type Props = { + state: WindowState; + onClose: (id: string) => void; + onMinimize: (id: string) => void; + onMaximize: (id: string) => void; + onDragStart: (id: string, event: React.MouseEvent) => void; + onFocus: (id: string) => void; +}; + +export default function RoadviewWindow({ state, ...handlers }: Props) { + return ( + +
+
+

Welcome to BlackRoad

+

The complete AI orchestration ecosystem, in one desktop.

+
+
+
+

+ This browser surface will eventually connect to your real BlackRoad services: + core.blackroad.systems, api.blackroad.systems, + agents.blackroad.systems, and more. +

+
+
+
+
+ ); +} diff --git a/br95/components/windows/SocialWindow.tsx b/br95/components/windows/SocialWindow.tsx new file mode 100644 index 0000000..a48b151 --- /dev/null +++ b/br95/components/windows/SocialWindow.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import WindowFrame from '../shared/WindowFrame'; +import { WindowState } from '../../hooks/useWindowManager'; + +type Props = { + state: WindowState; + onClose: (id: string) => void; + onMinimize: (id: string) => void; + onMaximize: (id: string) => void; + onDragStart: (id: string, event: React.MouseEvent) => void; + onFocus: (id: string) => void; +}; + +export default function SocialWindow({ state, ...handlers }: Props) { + return ( + +
+
+

Your Feed

+

Operators, agents, and ledgers talking in real time

+
+
+
+
+ Prism Console · New deployment shipped to RoadChain.
+ 2 minutes ago +
+
+
+
+
+ ); +} diff --git a/br95/components/windows/TerminalWindow.tsx b/br95/components/windows/TerminalWindow.tsx new file mode 100644 index 0000000..7710131 --- /dev/null +++ b/br95/components/windows/TerminalWindow.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import WindowFrame from '../shared/WindowFrame'; +import { WindowState } from '../../hooks/useWindowManager'; + +type Props = { + state: WindowState; + onClose: (id: string) => void; + onMinimize: (id: string) => void; + onMaximize: (id: string) => void; + onDragStart: (id: string, event: React.MouseEvent) => void; + onFocus: (id: string) => void; +}; + +export default function TerminalWindow({ state, ...handlers }: Props) { + return ( + +
+
+
BlackRoad OS Terminal v2.4.1
+
Copyright (c) 2024 BlackRoad Inc.
+
────────────────────────────────────────────────────
+
+ blackroad@cecilia:~$ lucidia status +
+
✓ Lucidia Core: OPERATIONAL
+
✓ Active Agents: 1000/1000
+
✓ Memory Journals: 1000 active streams
+
✓ Event Bus: 847 events/sec
+
✓ System Health: 99.95%
+
+ blackroad@cecilia:~$ roadchain sync +
+
Syncing with RoadChain network...
+
✓ Block height: 1,247,891
+
✓ Peers: 2847 connected
+
+ blackroad@cecilia:~$ + +
+
+
+
+ ); +} diff --git a/br95/components/windows/WalletWindow.tsx b/br95/components/windows/WalletWindow.tsx new file mode 100644 index 0000000..f86da0a --- /dev/null +++ b/br95/components/windows/WalletWindow.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import WindowFrame from '../shared/WindowFrame'; +import { WalletStats, WindowState } from '../../hooks/useWindowManager'; + +type Props = { + state: WindowState; + stats: WalletStats; + onClose: (id: string) => void; + onMinimize: (id: string) => void; + onMaximize: (id: string) => void; + onDragStart: (id: string, event: React.MouseEvent) => void; + onFocus: (id: string) => void; +}; + +export default function WalletWindow({ state, stats, ...handlers }: Props) { + return ( + +
+
+

{stats.balanceRC.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} RC

+

≈ ${stats.balanceUSD.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} USD • Synced with RoadChain

+
+
+
+ + +
+
+
+
+
Received
+
Mining rewards • 2h ago
+
+
+47.23 RC
+
+
+
+
+
+
Sent
+
Payment to Alice • Yesterday
+
+
‑12.50 RC
+
+
+
+
+
+ ); +} diff --git a/br95/hooks/useWindowManager.ts b/br95/hooks/useWindowManager.ts new file mode 100644 index 0000000..f625dbc --- /dev/null +++ b/br95/hooks/useWindowManager.ts @@ -0,0 +1,412 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; + +export type WindowState = { + id: string; + position: { x: number; y: number }; + size: { width: number; height: number }; + isOpen: boolean; + isMaximized: boolean; + zIndex: number; +}; + +export type LucidiaStats = { + status: string; + activeAgents: number; + totalAgents: number; + memoryJournals: number; + eventBusRate: number; + uptime: number; +}; + +export type RoadChainStats = { + currentBlock: number; + networkHashrate: string; + activeNodes: number; + yourHashrate: string; + shares: string; + dailyEarnings: string; +}; + +export type WalletStats = { + balanceRC: number; + balanceUSD: number; +}; + +export type MinerStats = { + hashRate: string; + sharesAccepted: number; + poolName: string; +}; + +export type WindowId = + | 'lucidia' + | 'agents' + | 'roadchain' + | 'wallet' + | 'terminal' + | 'roadmail' + | 'social' + | 'blackstream' + | 'roadview' + | 'pi' + | 'miner' + | 'roadcraft'; + +const API_BASE = '/api/br95'; + +const WINDOW_PRESETS: Record = { + lucidia: { id: 'lucidia', position: { x: 60, y: 90 }, size: { width: 680, height: 420 }, isOpen: false, isMaximized: false, zIndex: 10 }, + agents: { id: 'agents', position: { x: 120, y: 120 }, size: { width: 760, height: 460 }, isOpen: false, isMaximized: false, zIndex: 10 }, + roadchain: { id: 'roadchain', position: { x: 180, y: 80 }, size: { width: 760, height: 440 }, isOpen: false, isMaximized: false, zIndex: 10 }, + wallet: { id: 'wallet', position: { x: 220, y: 130 }, size: { width: 520, height: 380 }, isOpen: false, isMaximized: false, zIndex: 10 }, + terminal: { id: 'terminal', position: { x: 140, y: 180 }, size: { width: 720, height: 420 }, isOpen: false, isMaximized: false, zIndex: 10 }, + roadmail: { id: 'roadmail', position: { x: 80, y: 80 }, size: { width: 640, height: 380 }, isOpen: false, isMaximized: false, zIndex: 10 }, + social: { id: 'social', position: { x: 160, y: 90 }, size: { width: 640, height: 380 }, isOpen: false, isMaximized: false, zIndex: 10 }, + blackstream: { id: 'blackstream', position: { x: 200, y: 100 }, size: { width: 720, height: 420 }, isOpen: false, isMaximized: false, zIndex: 10 }, + roadview: { id: 'roadview', position: { x: 120, y: 70 }, size: { width: 820, height: 460 }, isOpen: false, isMaximized: false, zIndex: 10 }, + pi: { id: 'pi', position: { x: 220, y: 140 }, size: { width: 540, height: 320 }, isOpen: false, isMaximized: false, zIndex: 10 }, + miner: { id: 'miner', position: { x: 260, y: 120 }, size: { width: 560, height: 320 }, isOpen: false, isMaximized: false, zIndex: 10 }, + roadcraft: { id: 'roadcraft', position: { x: 300, y: 160 }, size: { width: 600, height: 360 }, isOpen: false, isMaximized: false, zIndex: 10 }, +}; + +export function useWindowManager() { + const [windowStates, setWindowStates] = useState>(WINDOW_PRESETS); + const [openWindows, setOpenWindows] = useState([]); + const [activeWindow, setActiveWindow] = useState(null); + const [roadMenuOpen, setRoadMenuOpen] = useState(false); + const [shellReady, setShellReady] = useState(false); + const [clock, setClock] = useState(() => new Date()); + const zIndexRef = useRef(10); + const dragRef = useRef<{ id: WindowId; offsetX: number; offsetY: number } | null>(null); + const menuRef = useRef(null); + const menuButtonRef = useRef(null); + const wsRef = useRef(null); + const reconnectRef = useRef(null); + + const [lucidiaStats, setLucidiaStats] = useState({ + status: 'OPERATIONAL', + activeAgents: 1000, + totalAgents: 1000, + memoryJournals: 1000, + eventBusRate: 847, + uptime: 99.95, + }); + + const [roadchainStats, setRoadchainStats] = useState({ + currentBlock: 1247891, + networkHashrate: '847.3 TH/s', + activeNodes: 2847, + yourHashrate: '1.2 GH/s', + shares: '8,423 accepted', + dailyEarnings: '47.23 RC', + }); + + const [walletStats, setWalletStats] = useState({ + balanceRC: 1247.89, + balanceUSD: 18705, + }); + + const [minerStats, setMinerStats] = useState({ + hashRate: '1.2 GH/s', + sharesAccepted: 8423, + poolName: 'BR‑Global‑01', + }); + + useEffect(() => { + const timer = setTimeout(() => setShellReady(true), 2400); + return () => clearTimeout(timer); + }, []); + + useEffect(() => { + const interval = setInterval(() => setClock(new Date()), 1000); + return () => clearInterval(interval); + }, []); + + useEffect(() => { + const handleClick = (event: MouseEvent) => { + if (!roadMenuOpen) return; + const target = event.target as Node; + if (menuRef.current?.contains(target) || menuButtonRef.current?.contains(target)) { + return; + } + setRoadMenuOpen(false); + }; + + document.addEventListener('click', handleClick); + return () => document.removeEventListener('click', handleClick); + }, [roadMenuOpen]); + + const focusWindow = useCallback((id: WindowId) => { + setWindowStates((prev) => { + const current = prev[id]; + if (!current) return prev; + const nextZ = ++zIndexRef.current; + return { ...prev, [id]: { ...current, zIndex: nextZ } }; + }); + setActiveWindow(id); + }, []); + + const openWindow = useCallback((id: WindowId) => { + setWindowStates((prev) => { + const current = prev[id]; + if (!current) return prev; + const nextZ = ++zIndexRef.current; + return { ...prev, [id]: { ...current, isOpen: true, zIndex: nextZ } }; + }); + setActiveWindow(id); + setOpenWindows((prev) => (prev.includes(id) ? prev : [...prev, id])); + setRoadMenuOpen(false); + }, []); + + const closeWindow = useCallback((id: WindowId) => { + setWindowStates((prev) => { + const current = prev[id]; + if (!current) return prev; + return { ...prev, [id]: { ...current, isOpen: false } }; + }); + setOpenWindows((prev) => prev.filter((win) => win !== id)); + setActiveWindow((prev) => (prev === id ? null : prev)); + }, []); + + const minimizeWindow = useCallback((id: WindowId) => { + setWindowStates((prev) => { + const current = prev[id]; + if (!current) return prev; + return { ...prev, [id]: { ...current, isOpen: false } }; + }); + setActiveWindow((prev) => (prev === id ? null : prev)); + }, []); + + const maximizeWindow = useCallback((id: WindowId) => { + setWindowStates((prev) => { + const current = prev[id]; + if (!current) return prev; + const nextZ = ++zIndexRef.current; + return { ...prev, [id]: { ...current, isMaximized: !current.isMaximized, zIndex: nextZ } }; + }); + setActiveWindow(id); + }, []); + + const startDrag = useCallback((id: WindowId, event: React.MouseEvent) => { + event.preventDefault(); + setWindowStates((prev) => { + const current = prev[id]; + if (!current || current.isMaximized) return prev; + dragRef.current = { + id, + offsetX: event.clientX - current.position.x, + offsetY: event.clientY - current.position.y, + }; + const nextZ = ++zIndexRef.current; + return { ...prev, [id]: { ...current, zIndex: nextZ } }; + }); + setActiveWindow(id); + }, []); + + useEffect(() => { + const handleMove = (event: MouseEvent) => { + if (!dragRef.current) return; + setWindowStates((prev) => { + const current = prev[dragRef.current!.id]; + if (!current || current.isMaximized) return prev; + const nextPosition = { + x: event.clientX - dragRef.current!.offsetX, + y: event.clientY - dragRef.current!.offsetY, + }; + return { ...prev, [current.id]: { ...current, position: nextPosition } }; + }); + }; + + const handleUp = () => { + dragRef.current = null; + }; + + window.addEventListener('mousemove', handleMove); + window.addEventListener('mouseup', handleUp); + return () => { + window.removeEventListener('mousemove', handleMove); + window.removeEventListener('mouseup', handleUp); + }; + }, []); + + const toggleRoadMenu = useCallback(() => { + setRoadMenuOpen((prev) => !prev); + }, []); + + const taskbarToggle = useCallback( + (id: WindowId) => { + if (windowStates[id]?.isOpen && activeWindow === id) { + minimizeWindow(id); + } else { + openWindow(id); + } + }, + [activeWindow, minimizeWindow, openWindow, windowStates], + ); + + const clockText = useMemo(() => clock.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }), [clock]); + + const fetchLucidia = useCallback(async () => { + try { + const response = await fetch(`${API_BASE}/lucidia`); + const data = await response.json(); + setLucidiaStats((prev) => ({ + status: data.status ? String(data.status).toUpperCase() : prev.status, + activeAgents: data.active_agents ?? prev.activeAgents, + totalAgents: data.total_agents ?? prev.totalAgents, + memoryJournals: data.memory_journals ?? prev.memoryJournals, + eventBusRate: data.event_bus_rate ?? prev.eventBusRate, + uptime: data.system_health ?? prev.uptime, + })); + } catch (error) { + console.error('Failed to fetch Lucidia stats:', error); + } + }, []); + + const fetchRoadchain = useCallback(async () => { + try { + const response = await fetch(`${API_BASE}/roadchain`); + const data = await response.json(); + setRoadchainStats((prev) => ({ + currentBlock: data.current_block ?? prev.currentBlock, + networkHashrate: data.network_hashrate ?? prev.networkHashrate, + activeNodes: data.active_nodes ?? prev.activeNodes, + yourHashrate: data.network_hashrate ?? prev.yourHashrate, + shares: prev.shares, + dailyEarnings: prev.dailyEarnings, + })); + } catch (error) { + console.error('Failed to fetch RoadChain stats:', error); + } + }, []); + + const fetchWallet = useCallback(async () => { + try { + const response = await fetch(`${API_BASE}/wallet`); + const data = await response.json(); + setWalletStats((prev) => ({ + balanceRC: data.balance_rc ?? prev.balanceRC, + balanceUSD: data.balance_usd ?? prev.balanceUSD, + })); + } catch (error) { + console.error('Failed to fetch Wallet stats:', error); + } + }, []); + + const fetchMiner = useCallback(async () => { + try { + const response = await fetch(`${API_BASE}/miner`); + const data = await response.json(); + setMinerStats((prev) => ({ + hashRate: data.hash_rate ?? prev.hashRate, + sharesAccepted: data.shares_accepted ?? prev.sharesAccepted, + poolName: data.pool_name ?? prev.poolName, + })); + } catch (error) { + console.error('Failed to fetch Miner stats:', error); + } + }, []); + + const connectWebSocket = useCallback(() => { + if (typeof window === 'undefined') return; + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const wsUrl = `${protocol}//${window.location.host}${API_BASE}/ws`; + + const socket = new WebSocket(wsUrl); + wsRef.current = socket; + + socket.onmessage = (event) => { + try { + const message = JSON.parse(event.data); + switch (message.type) { + case 'miner_update': + setMinerStats((prev) => ({ + ...prev, + hashRate: message.data?.hash_rate ?? prev.hashRate, + sharesAccepted: message.data?.shares_accepted ?? prev.sharesAccepted, + })); + break; + case 'roadchain_update': + setRoadchainStats((prev) => ({ + ...prev, + currentBlock: message.data?.current_block ?? prev.currentBlock, + activeNodes: message.data?.active_nodes ?? prev.activeNodes, + })); + break; + case 'wallet_update': + setWalletStats((prev) => ({ + ...prev, + balanceRC: message.data?.balance_rc ?? prev.balanceRC, + balanceUSD: message.data?.balance_usd ?? prev.balanceUSD, + })); + break; + default: + break; + } + } catch (error) { + console.error('Failed to parse WebSocket message:', error); + } + }; + + socket.onclose = () => { + if (reconnectRef.current) { + clearTimeout(reconnectRef.current); + } + reconnectRef.current = setTimeout(connectWebSocket, 5000); + }; + + socket.onerror = (error) => { + console.error('WebSocket error:', error); + }; + }, []); + + useEffect(() => { + if (!shellReady) return; + + let lucidiaInterval: NodeJS.Timeout | null = null; + const startApis = () => { + fetchLucidia(); + fetchRoadchain(); + fetchWallet(); + fetchMiner(); + connectWebSocket(); + lucidiaInterval = setInterval(fetchLucidia, 30000); + }; + + const timer = setTimeout(startApis, 3000); + + return () => { + clearTimeout(timer); + if (lucidiaInterval) { + clearInterval(lucidiaInterval); + } + if (reconnectRef.current) { + clearTimeout(reconnectRef.current); + } + wsRef.current?.close(); + }; + }, [connectWebSocket, fetchLucidia, fetchMiner, fetchRoadchain, fetchWallet, shellReady]); + + return { + windowStates, + openWindows, + activeWindow, + roadMenuOpen, + shellReady, + clockText, + lucidiaStats, + roadchainStats, + walletStats, + minerStats, + menuRef, + menuButtonRef, + openWindow, + closeWindow, + minimizeWindow, + maximizeWindow, + startDrag, + focusWindow, + toggleRoadMenu, + taskbarToggle, + }; +} diff --git a/br95/next-env.d.ts b/br95/next-env.d.ts new file mode 100644 index 0000000..4f11a03 --- /dev/null +++ b/br95/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/br95/next.config.mjs b/br95/next.config.mjs new file mode 100644 index 0000000..3b18c5c --- /dev/null +++ b/br95/next.config.mjs @@ -0,0 +1,9 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + experimental: { + appDir: true, + }, +}; + +export default nextConfig; diff --git a/br95/package.json b/br95/package.json new file mode 100644 index 0000000..dfb38c6 --- /dev/null +++ b/br95/package.json @@ -0,0 +1,21 @@ +{ + "name": "br95", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "next": "14.2.3", + "react": "18.2.0", + "react-dom": "18.2.0" + }, + "devDependencies": { + "@types/node": "20.12.7", + "@types/react": "18.2.79", + "typescript": "5.4.3" + } +} diff --git a/br95/tsconfig.json b/br95/tsconfig.json new file mode 100644 index 0000000..f6962a8 --- /dev/null +++ b/br95/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "baseUrl": "." + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +}