mirror of
https://github.com/blackboxprogramming/BlackRoad-Operating-System.git
synced 2026-03-17 07:57:19 -05:00
Port BR-95 desktop to Next.js components
This commit is contained in:
749
br95/app/globals.css
Normal file
749
br95/app/globals.css
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
15
br95/app/layout.tsx
Normal file
15
br95/app/layout.tsx
Normal file
@@ -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 (
|
||||||
|
<html lang="en">
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
7
br95/app/page.tsx
Normal file
7
br95/app/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import DesktopLayout from '../components/DesktopLayout';
|
||||||
|
|
||||||
|
export default function HomePage() {
|
||||||
|
return <DesktopLayout />;
|
||||||
|
}
|
||||||
200
br95/components/DesktopLayout.tsx
Normal file
200
br95/components/DesktopLayout.tsx
Normal file
@@ -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 (
|
||||||
|
<>
|
||||||
|
<div className="scanline"></div>
|
||||||
|
<BootScreen bootActive={!shellReady} />
|
||||||
|
<div className={`shell ${shellReady ? 'ready' : ''}`} id="shell">
|
||||||
|
<div className="menubar">
|
||||||
|
<div className="menu-logo"></div>
|
||||||
|
<div className="menu-items">
|
||||||
|
<div className="menu-item">File</div>
|
||||||
|
<div className="menu-item">View</div>
|
||||||
|
<div className="menu-item">Agents</div>
|
||||||
|
<div className="menu-item">RoadChain</div>
|
||||||
|
</div>
|
||||||
|
<div className="system-info">
|
||||||
|
<span>BlackRoad OS v1.0</span>
|
||||||
|
<span>Agents: 1000</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="desktop-area" id="desktop">
|
||||||
|
{desktopIcons.map((item) => (
|
||||||
|
<div key={item.id} className="icon" onDoubleClick={() => openWindow(item.id)}>
|
||||||
|
<div className="icon-image">{item.icon}</div>
|
||||||
|
<div className="icon-label">{item.label}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LucidiaWindow
|
||||||
|
state={windowStates.lucidia}
|
||||||
|
stats={lucidiaStats}
|
||||||
|
onClose={closeWindow}
|
||||||
|
onMinimize={minimizeWindow}
|
||||||
|
onMaximize={maximizeWindow}
|
||||||
|
onDragStart={startDrag}
|
||||||
|
onFocus={focusWindow}
|
||||||
|
/>
|
||||||
|
<AgentsWindow
|
||||||
|
state={windowStates.agents}
|
||||||
|
onClose={closeWindow}
|
||||||
|
onMinimize={minimizeWindow}
|
||||||
|
onMaximize={maximizeWindow}
|
||||||
|
onDragStart={startDrag}
|
||||||
|
onFocus={focusWindow}
|
||||||
|
/>
|
||||||
|
<RoadchainWindow
|
||||||
|
state={windowStates.roadchain}
|
||||||
|
stats={roadchainStats}
|
||||||
|
onClose={closeWindow}
|
||||||
|
onMinimize={minimizeWindow}
|
||||||
|
onMaximize={maximizeWindow}
|
||||||
|
onDragStart={startDrag}
|
||||||
|
onFocus={focusWindow}
|
||||||
|
/>
|
||||||
|
<WalletWindow
|
||||||
|
state={windowStates.wallet}
|
||||||
|
stats={walletStats}
|
||||||
|
onClose={closeWindow}
|
||||||
|
onMinimize={minimizeWindow}
|
||||||
|
onMaximize={maximizeWindow}
|
||||||
|
onDragStart={startDrag}
|
||||||
|
onFocus={focusWindow}
|
||||||
|
/>
|
||||||
|
<TerminalWindow
|
||||||
|
state={windowStates.terminal}
|
||||||
|
onClose={closeWindow}
|
||||||
|
onMinimize={minimizeWindow}
|
||||||
|
onMaximize={maximizeWindow}
|
||||||
|
onDragStart={startDrag}
|
||||||
|
onFocus={focusWindow}
|
||||||
|
/>
|
||||||
|
<RoadmailWindow
|
||||||
|
state={windowStates.roadmail}
|
||||||
|
onClose={closeWindow}
|
||||||
|
onMinimize={minimizeWindow}
|
||||||
|
onMaximize={maximizeWindow}
|
||||||
|
onDragStart={startDrag}
|
||||||
|
onFocus={focusWindow}
|
||||||
|
/>
|
||||||
|
<SocialWindow
|
||||||
|
state={windowStates.social}
|
||||||
|
onClose={closeWindow}
|
||||||
|
onMinimize={minimizeWindow}
|
||||||
|
onMaximize={maximizeWindow}
|
||||||
|
onDragStart={startDrag}
|
||||||
|
onFocus={focusWindow}
|
||||||
|
/>
|
||||||
|
<BlackstreamWindow
|
||||||
|
state={windowStates.blackstream}
|
||||||
|
onClose={closeWindow}
|
||||||
|
onMinimize={minimizeWindow}
|
||||||
|
onMaximize={maximizeWindow}
|
||||||
|
onDragStart={startDrag}
|
||||||
|
onFocus={focusWindow}
|
||||||
|
/>
|
||||||
|
<RoadviewWindow
|
||||||
|
state={windowStates.roadview}
|
||||||
|
onClose={closeWindow}
|
||||||
|
onMinimize={minimizeWindow}
|
||||||
|
onMaximize={maximizeWindow}
|
||||||
|
onDragStart={startDrag}
|
||||||
|
onFocus={focusWindow}
|
||||||
|
/>
|
||||||
|
<PiWindow
|
||||||
|
state={windowStates.pi}
|
||||||
|
onClose={closeWindow}
|
||||||
|
onMinimize={minimizeWindow}
|
||||||
|
onMaximize={maximizeWindow}
|
||||||
|
onDragStart={startDrag}
|
||||||
|
onFocus={focusWindow}
|
||||||
|
/>
|
||||||
|
<MinerWindow
|
||||||
|
state={windowStates.miner}
|
||||||
|
stats={minerStats}
|
||||||
|
onClose={closeWindow}
|
||||||
|
onMinimize={minimizeWindow}
|
||||||
|
onMaximize={maximizeWindow}
|
||||||
|
onDragStart={startDrag}
|
||||||
|
onFocus={focusWindow}
|
||||||
|
/>
|
||||||
|
<RoadcraftWindow
|
||||||
|
state={windowStates.roadcraft}
|
||||||
|
onClose={closeWindow}
|
||||||
|
onMinimize={minimizeWindow}
|
||||||
|
onMaximize={maximizeWindow}
|
||||||
|
onDragStart={startDrag}
|
||||||
|
onFocus={focusWindow}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Taskbar
|
||||||
|
openWindows={openWindows}
|
||||||
|
activeWindow={activeWindow}
|
||||||
|
clockText={clockText}
|
||||||
|
onTaskbarClick={(id) => taskbarToggle(id)}
|
||||||
|
onToggleMenu={toggleRoadMenu}
|
||||||
|
menuButtonRef={menuButtonRef}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<RoadMenu isOpen={roadMenuOpen} onOpenWindow={(id) => openWindow(id)} menuRef={menuRef} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
br95/components/shared/BootScreen.tsx
Normal file
20
br95/components/shared/BootScreen.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
bootActive: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function BootScreen({ bootActive }: Props) {
|
||||||
|
return (
|
||||||
|
<div className="boot-screen" id="boot" style={{ pointerEvents: bootActive ? 'auto' : 'none' }}>
|
||||||
|
<div className="boot-logo-container">
|
||||||
|
<div className="boot-logo">
|
||||||
|
<div className="boot-road"></div>
|
||||||
|
</div>
|
||||||
|
<div className="boot-title">BlackRoad OS</div>
|
||||||
|
<div className="boot-sub">BR‑95 Edition · Safe Passage Through The Chaos</div>
|
||||||
|
<div className="boot-loading">System Loading</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
42
br95/components/shared/RoadMenu.tsx
Normal file
42
br95/components/shared/RoadMenu.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { WindowId } from '../../hooks/useWindowManager';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen: boolean;
|
||||||
|
onOpenWindow: (id: WindowId) => void;
|
||||||
|
menuRef: React.RefObject<HTMLDivElement>;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className={`road-menu ${isOpen ? 'active' : ''}`} id="road-menu" ref={menuRef}>
|
||||||
|
<div className="road-menu-header">
|
||||||
|
<h3>BlackRoad OS</h3>
|
||||||
|
<p>BR‑95 Desktop · Agent Orchestration</p>
|
||||||
|
</div>
|
||||||
|
<div className="road-menu-content">
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<React.Fragment key={item.id}>
|
||||||
|
<div className="road-menu-item" onClick={() => onOpenWindow(item.id)}>
|
||||||
|
<span className="emoji">{item.icon}</span>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</div>
|
||||||
|
{index === 2 || index === 5 ? <div className="road-menu-separator"></div> : null}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
63
br95/components/shared/Taskbar.tsx
Normal file
63
br95/components/shared/Taskbar.tsx
Normal file
@@ -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<HTMLDivElement>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const titles: Record<WindowId, string> = {
|
||||||
|
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 (
|
||||||
|
<div className="taskbar">
|
||||||
|
<div className="road-button" id="road-button" onClick={onToggleMenu} ref={menuButtonRef}>
|
||||||
|
<span className="road-logo"></span>
|
||||||
|
<span>Road</span>
|
||||||
|
</div>
|
||||||
|
<div className="taskbar-apps" id="taskbar-apps">
|
||||||
|
{openWindows.map((id) => (
|
||||||
|
<div
|
||||||
|
key={id}
|
||||||
|
className={`taskbar-app ${activeWindow === id ? 'active-app' : ''}`}
|
||||||
|
onClick={() => onTaskbarClick(id)}
|
||||||
|
>
|
||||||
|
{titles[id] ?? id}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="system-tray">
|
||||||
|
<span>🌐</span>
|
||||||
|
<span>🔊</span>
|
||||||
|
<span>⛓️</span>
|
||||||
|
</div>
|
||||||
|
<div className="clock" id="clock">
|
||||||
|
{clockText}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
67
br95/components/shared/WindowFrame.tsx
Normal file
67
br95/components/shared/WindowFrame.tsx
Normal file
@@ -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 (
|
||||||
|
<div
|
||||||
|
className={`window ${state.isOpen ? 'active' : ''} ${state.isMaximized ? 'maximized' : ''}`}
|
||||||
|
style={style}
|
||||||
|
onMouseDown={() => onFocus(id)}
|
||||||
|
>
|
||||||
|
<div className="title-bar" onMouseDown={(e) => onDragStart(id, e)}>
|
||||||
|
<div className="title-text">
|
||||||
|
<span>{icon}</span>
|
||||||
|
<span>{title}</span>
|
||||||
|
</div>
|
||||||
|
<div className="title-buttons">
|
||||||
|
<div className="title-button" onClick={() => onMinimize(id)}>
|
||||||
|
_
|
||||||
|
</div>
|
||||||
|
<div className="title-button" onClick={() => onMaximize(id)}>
|
||||||
|
□
|
||||||
|
</div>
|
||||||
|
<div className="title-button" onClick={() => onClose(id)}>
|
||||||
|
×
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
46
br95/components/windows/AgentsWindow.tsx
Normal file
46
br95/components/windows/AgentsWindow.tsx
Normal file
@@ -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 (
|
||||||
|
<WindowFrame id="agents" title="AI Agent Orchestration" icon="🤖" state={state} {...handlers}>
|
||||||
|
<div className="window-inner">
|
||||||
|
<div className="content-header">
|
||||||
|
<h2>1,000 Unique Agents</h2>
|
||||||
|
<p>Each with names, memories, families & Unity homes</p>
|
||||||
|
</div>
|
||||||
|
<div className="content-body">
|
||||||
|
<div className="grid grid-2">
|
||||||
|
<div className="card">
|
||||||
|
<div style={{ textAlign: 'center', fontSize: '28px', marginBottom: '6px' }}>🤖</div>
|
||||||
|
<div className="stat-row"><span className="stat-label">ID</span><span className="stat-value">Agent‑042</span></div>
|
||||||
|
<div className="stat-row"><span className="stat-label">Persona</span><span className="stat-value">Alice</span></div>
|
||||||
|
<div className="stat-row"><span className="stat-label">Status</span><span className="badge badge-success">Active</span></div>
|
||||||
|
<div className="stat-row"><span className="stat-label">Tasks Today</span><span className="stat-value">247</span></div>
|
||||||
|
</div>
|
||||||
|
<div className="card">
|
||||||
|
<div style={{ textAlign: 'center', fontSize: '28px', marginBottom: '6px' }}>🤖</div>
|
||||||
|
<div className="stat-row"><span className="stat-label">ID</span><span className="stat-value">Agent‑189</span></div>
|
||||||
|
<div className="stat-row"><span className="stat-label">Persona</span><span className="stat-value">Marcus</span></div>
|
||||||
|
<div className="stat-row"><span className="stat-label">Status</span><span className="badge badge-success">Active</span></div>
|
||||||
|
<div className="stat-row"><span className="stat-label">Tasks Today</span><span className="stat-value">189</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 12, textAlign: 'center' }}>
|
||||||
|
<button className="btn-primary">View All 1000 Agents</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WindowFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
33
br95/components/windows/BlackstreamWindow.tsx
Normal file
33
br95/components/windows/BlackstreamWindow.tsx
Normal file
@@ -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 (
|
||||||
|
<WindowFrame id="blackstream" title="BlackStream" icon="📺" state={state} {...handlers}>
|
||||||
|
<div className="window-inner">
|
||||||
|
<div className="content-header">
|
||||||
|
<h2>Trending Streams</h2>
|
||||||
|
<p>OS demos, agent orchestration runs, and live RoadChain dashboards</p>
|
||||||
|
</div>
|
||||||
|
<div className="content-body">
|
||||||
|
<div
|
||||||
|
className="card"
|
||||||
|
style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 13, color: 'var(--br-muted)' }}
|
||||||
|
>
|
||||||
|
Embedded stream player coming soon.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WindowFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
79
br95/components/windows/LucidiaWindow.tsx
Normal file
79
br95/components/windows/LucidiaWindow.tsx
Normal file
@@ -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 (
|
||||||
|
<WindowFrame id="lucidia" title="Lucidia Core System" icon="🧠" state={state} {...handlers}>
|
||||||
|
<div className="window-inner">
|
||||||
|
<div className="content-header">
|
||||||
|
<h2>Recursive AI Engine</h2>
|
||||||
|
<p>Trinary logic · Paraconsistent reasoning · PS‑SHA∞ memory</p>
|
||||||
|
</div>
|
||||||
|
<div className="content-body">
|
||||||
|
<div className="grid grid-2">
|
||||||
|
<div className="card">
|
||||||
|
<div className="stat-row">
|
||||||
|
<span className="stat-label">Active Agents</span>
|
||||||
|
<span className="stat-value">{stats.activeAgents} / {stats.totalAgents}</span>
|
||||||
|
</div>
|
||||||
|
<div className="stat-row">
|
||||||
|
<span className="stat-label">Memory Journals</span>
|
||||||
|
<span className="stat-value">{stats.memoryJournals} streams</span>
|
||||||
|
</div>
|
||||||
|
<div className="stat-row">
|
||||||
|
<span className="stat-label">Event Bus</span>
|
||||||
|
<span className="stat-value">{stats.eventBusRate} events/sec</span>
|
||||||
|
</div>
|
||||||
|
<div className="stat-row">
|
||||||
|
<span className="stat-label">Uptime</span>
|
||||||
|
<span className="stat-value">{stats.uptime.toFixed(2)}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="card">
|
||||||
|
<div className="stat-row">
|
||||||
|
<span className="stat-label">Mode</span>
|
||||||
|
<span className="stat-value">Trinary (1/0/‑1)</span>
|
||||||
|
</div>
|
||||||
|
<div className="stat-row">
|
||||||
|
<span className="stat-label">Contradictions</span>
|
||||||
|
<span className="stat-value">Paraconsistent</span>
|
||||||
|
</div>
|
||||||
|
<div className="stat-row">
|
||||||
|
<span className="stat-label">Memory Hash</span>
|
||||||
|
<span className="stat-value">PS‑SHA∞</span>
|
||||||
|
</div>
|
||||||
|
<div className="stat-row">
|
||||||
|
<span className="stat-label">Coordination</span>
|
||||||
|
<span className="stat-value">Hybrid P2P</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="card" style={{ marginTop: 10 }}>
|
||||||
|
<div style={{ fontSize: '12px' }}>
|
||||||
|
<div style={{ marginBottom: '6px' }}>
|
||||||
|
<span className="badge badge-success">Agent‑042</span> proposed new routing rule • 2m ago
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: '6px' }}>
|
||||||
|
<span className="badge badge-info">Agent‑189</span> memory journal sync complete • 5m ago
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="badge badge-warning">Agent‑734</span> contradiction quarantined • 12m ago
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WindowFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
33
br95/components/windows/MinerWindow.tsx
Normal file
33
br95/components/windows/MinerWindow.tsx
Normal file
@@ -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 (
|
||||||
|
<WindowFrame id="miner" title="RoadCoin Miner" icon="⛏️" state={state} {...handlers}>
|
||||||
|
<div className="window-inner">
|
||||||
|
<div className="content-header">
|
||||||
|
<h2>Mining Active</h2>
|
||||||
|
<p>Contributing work to RoadChain network</p>
|
||||||
|
</div>
|
||||||
|
<div className="content-body">
|
||||||
|
<div className="card">
|
||||||
|
<div className="stat-row"><span className="stat-label">Hashrate</span><span className="stat-value">{stats.hashRate}</span></div>
|
||||||
|
<div className="stat-row"><span className="stat-label">Accepted Shares</span><span className="stat-value">{stats.sharesAccepted.toLocaleString()}</span></div>
|
||||||
|
<div className="stat-row"><span className="stat-label">Pool</span><span className="stat-value">{stats.poolName}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WindowFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
31
br95/components/windows/PiWindow.tsx
Normal file
31
br95/components/windows/PiWindow.tsx
Normal file
@@ -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 (
|
||||||
|
<WindowFrame id="pi" title="Pi Network Control Panel" icon="🥧" state={state} {...handlers}>
|
||||||
|
<div className="window-inner">
|
||||||
|
<div className="content-header">
|
||||||
|
<h2>Connected Devices</h2>
|
||||||
|
<p>4 devices online · 1 Jetson Orin Nano</p>
|
||||||
|
</div>
|
||||||
|
<div className="content-body">
|
||||||
|
<div className="card">
|
||||||
|
<div className="stat-row"><span className="stat-label">Jetson Orin Nano</span><span className="badge badge-success">Online</span></div>
|
||||||
|
<div className="stat-row"><span className="stat-label">Lucidia‑Pi‑01</span><span className="badge badge-success">Online</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WindowFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
56
br95/components/windows/RoadchainWindow.tsx
Normal file
56
br95/components/windows/RoadchainWindow.tsx
Normal file
@@ -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 (
|
||||||
|
<WindowFrame id="roadchain" title="RoadChain Explorer" icon="⛓️" state={state} {...handlers}>
|
||||||
|
<div className="window-inner">
|
||||||
|
<div className="content-header">
|
||||||
|
<h2>RoadChain Network</h2>
|
||||||
|
<p>Transparent AI governance on a distributed ledger</p>
|
||||||
|
</div>
|
||||||
|
<div className="content-body">
|
||||||
|
<div className="grid grid-2" style={{ marginBottom: 10 }}>
|
||||||
|
<div className="card">
|
||||||
|
<div className="stat-row"><span className="stat-label">Block Height</span><span className="stat-value">{stats.currentBlock.toLocaleString()}</span></div>
|
||||||
|
<div className="stat-row"><span className="stat-label">Network Hash</span><span className="stat-value">{stats.networkHashrate}</span></div>
|
||||||
|
<div className="stat-row"><span className="stat-label">Active Nodes</span><span className="stat-value">{stats.activeNodes.toLocaleString()}</span></div>
|
||||||
|
</div>
|
||||||
|
<div className="card">
|
||||||
|
<div className="stat-row"><span className="stat-label">Your Hashrate</span><span className="stat-value">{stats.yourHashrate}</span></div>
|
||||||
|
<div className="stat-row"><span className="stat-label">Shares</span><span className="stat-value">{stats.shares}</span></div>
|
||||||
|
<div className="stat-row"><span className="stat-label">Daily Earnings</span><span className="stat-value">{stats.dailyEarnings}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="card">
|
||||||
|
<div style={{ fontFamily: 'var(--br-font-mono)', fontSize: '12px' }}>
|
||||||
|
<div style={{ marginBottom: '8px' }}>
|
||||||
|
<strong>Block #1,247,891</strong> • 23s ago<br />
|
||||||
|
<span style={{ color: 'var(--br-muted)' }}>Hash: 0x8f4a2...c7b9d • 247 tx</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: '8px' }}>
|
||||||
|
<strong>Block #1,247,890</strong> • 2m ago<br />
|
||||||
|
<span style={{ color: 'var(--br-muted)' }}>Hash: 0x3c9e1...f2a4b • 189 tx</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Block #1,247,889</strong> • 4m ago<br />
|
||||||
|
<span style={{ color: 'var(--br-muted)' }}>Hash: 0x7d2b3...e8c1f • 312 tx</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WindowFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
28
br95/components/windows/RoadcraftWindow.tsx
Normal file
28
br95/components/windows/RoadcraftWindow.tsx
Normal file
@@ -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 (
|
||||||
|
<WindowFrame id="roadcraft" title="RoadCraft" icon="⛏️" state={state} {...handlers}>
|
||||||
|
<div className="window-inner">
|
||||||
|
<div className="content-header">
|
||||||
|
<h2>Voxel World Builder</h2>
|
||||||
|
<p>Design agent habitats & quantum sandboxes</p>
|
||||||
|
</div>
|
||||||
|
<div className="content-body" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
|
<button className="btn-primary">New World</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WindowFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
31
br95/components/windows/RoadmailWindow.tsx
Normal file
31
br95/components/windows/RoadmailWindow.tsx
Normal file
@@ -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 (
|
||||||
|
<WindowFrame id="roadmail" title="RoadMail" icon="📧" state={state} {...handlers}>
|
||||||
|
<div className="window-inner">
|
||||||
|
<div className="content-header">
|
||||||
|
<h2>Inbox</h2>
|
||||||
|
<p>Secure AI‑aware communication</p>
|
||||||
|
</div>
|
||||||
|
<div className="content-body">
|
||||||
|
<div className="card">
|
||||||
|
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 4 }}>Welcome to BlackRoad OS</div>
|
||||||
|
<div style={{ fontSize: 11, color: 'var(--br-muted)' }}>From: BlackRoad Team • Today</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WindowFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
34
br95/components/windows/RoadviewWindow.tsx
Normal file
34
br95/components/windows/RoadviewWindow.tsx
Normal file
@@ -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 (
|
||||||
|
<WindowFrame id="roadview" title="RoadView Browser" icon="🌍" state={state} {...handlers}>
|
||||||
|
<div className="window-inner">
|
||||||
|
<div className="content-header">
|
||||||
|
<h2>Welcome to BlackRoad</h2>
|
||||||
|
<p>The complete AI orchestration ecosystem, in one desktop.</p>
|
||||||
|
</div>
|
||||||
|
<div className="content-body">
|
||||||
|
<div className="card">
|
||||||
|
<p style={{ fontSize: 13, color: 'var(--br-muted)' }}>
|
||||||
|
This browser surface will eventually connect to your real BlackRoad services:
|
||||||
|
<code>core.blackroad.systems</code>, <code>api.blackroad.systems</code>,
|
||||||
|
<code>agents.blackroad.systems</code>, and more.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WindowFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
33
br95/components/windows/SocialWindow.tsx
Normal file
33
br95/components/windows/SocialWindow.tsx
Normal file
@@ -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 (
|
||||||
|
<WindowFrame id="social" title="BlackRoad Social" icon="👥" state={state} {...handlers}>
|
||||||
|
<div className="window-inner">
|
||||||
|
<div className="content-header">
|
||||||
|
<h2>Your Feed</h2>
|
||||||
|
<p>Operators, agents, and ledgers talking in real time</p>
|
||||||
|
</div>
|
||||||
|
<div className="content-body">
|
||||||
|
<div className="card">
|
||||||
|
<div style={{ fontSize: 12 }}>
|
||||||
|
<strong>Prism Console ·</strong> New deployment shipped to RoadChain.<br />
|
||||||
|
<span style={{ color: 'var(--br-muted)', fontSize: 11 }}>2 minutes ago</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WindowFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
44
br95/components/windows/TerminalWindow.tsx
Normal file
44
br95/components/windows/TerminalWindow.tsx
Normal file
@@ -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 (
|
||||||
|
<WindowFrame id="terminal" title="C:\\BLACKROAD\\TERMINAL.EXE" icon="💻" state={state} {...handlers}>
|
||||||
|
<div className="window-inner" style={{ padding: 8 }}>
|
||||||
|
<div className="terminal-screen">
|
||||||
|
<div className="terminal-line">BlackRoad OS Terminal v2.4.1</div>
|
||||||
|
<div className="terminal-line">Copyright (c) 2024 BlackRoad Inc.</div>
|
||||||
|
<div className="terminal-line" style={{ marginTop: 10 }}>────────────────────────────────────────────────────</div>
|
||||||
|
<div className="terminal-line" style={{ marginTop: 10 }}>
|
||||||
|
<span className="terminal-prompt">blackroad@cecilia:~$</span> lucidia status
|
||||||
|
</div>
|
||||||
|
<div className="terminal-line" style={{ marginLeft: 18 }}>✓ Lucidia Core: OPERATIONAL</div>
|
||||||
|
<div className="terminal-line" style={{ marginLeft: 18 }}>✓ Active Agents: 1000/1000</div>
|
||||||
|
<div className="terminal-line" style={{ marginLeft: 18 }}>✓ Memory Journals: 1000 active streams</div>
|
||||||
|
<div className="terminal-line" style={{ marginLeft: 18 }}>✓ Event Bus: 847 events/sec</div>
|
||||||
|
<div className="terminal-line" style={{ marginLeft: 18 }}>✓ System Health: 99.95%</div>
|
||||||
|
<div className="terminal-line" style={{ marginTop: 10 }}>
|
||||||
|
<span className="terminal-prompt">blackroad@cecilia:~$</span> roadchain sync
|
||||||
|
</div>
|
||||||
|
<div className="terminal-line" style={{ marginLeft: 18 }}>Syncing with RoadChain network...</div>
|
||||||
|
<div className="terminal-line" style={{ marginLeft: 18 }}>✓ Block height: 1,247,891</div>
|
||||||
|
<div className="terminal-line" style={{ marginLeft: 18 }}>✓ Peers: 2847 connected</div>
|
||||||
|
<div className="terminal-line" style={{ marginTop: 10 }}>
|
||||||
|
<span className="terminal-prompt">blackroad@cecilia:~$</span>
|
||||||
|
<span className="terminal-cursor"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WindowFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
52
br95/components/windows/WalletWindow.tsx
Normal file
52
br95/components/windows/WalletWindow.tsx
Normal file
@@ -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 (
|
||||||
|
<WindowFrame id="wallet" title="RoadCoin Wallet" icon="💰" state={state} {...handlers}>
|
||||||
|
<div className="window-inner">
|
||||||
|
<div className="content-header">
|
||||||
|
<h2 style={{ fontSize: 26 }}>{stats.balanceRC.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} RC</h2>
|
||||||
|
<p>≈ ${stats.balanceUSD.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })} USD • Synced with RoadChain</p>
|
||||||
|
</div>
|
||||||
|
<div className="content-body">
|
||||||
|
<div style={{ display: 'flex', gap: 8, marginBottom: 10 }}>
|
||||||
|
<button className="btn-primary">📤 Send</button>
|
||||||
|
<button className="btn-primary" style={{ background: 'rgba(255,255,255,0.06)', color: 'var(--br-white)' }}>
|
||||||
|
📥 Receive
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="card">
|
||||||
|
<div className="stat-row">
|
||||||
|
<div>
|
||||||
|
<div style={{ fontWeight: 600, marginBottom: 2 }}>Received</div>
|
||||||
|
<div style={{ fontSize: 11, color: 'var(--br-muted)' }}>Mining rewards • 2h ago</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ color: '#22c55e', fontWeight: 600 }}>+47.23 RC</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="card">
|
||||||
|
<div className="stat-row">
|
||||||
|
<div>
|
||||||
|
<div style={{ fontWeight: 600, marginBottom: 2 }}>Sent</div>
|
||||||
|
<div style={{ fontSize: 11, color: 'var(--br-muted)' }}>Payment to Alice • Yesterday</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ color: '#ef4444', fontWeight: 600 }}>‑12.50 RC</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WindowFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
412
br95/hooks/useWindowManager.ts
Normal file
412
br95/hooks/useWindowManager.ts
Normal file
@@ -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<WindowId, WindowState> = {
|
||||||
|
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<Record<WindowId, WindowState>>(WINDOW_PRESETS);
|
||||||
|
const [openWindows, setOpenWindows] = useState<WindowId[]>([]);
|
||||||
|
const [activeWindow, setActiveWindow] = useState<WindowId | null>(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<HTMLDivElement | null>(null);
|
||||||
|
const menuButtonRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const wsRef = useRef<WebSocket | null>(null);
|
||||||
|
const reconnectRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
|
const [lucidiaStats, setLucidiaStats] = useState<LucidiaStats>({
|
||||||
|
status: 'OPERATIONAL',
|
||||||
|
activeAgents: 1000,
|
||||||
|
totalAgents: 1000,
|
||||||
|
memoryJournals: 1000,
|
||||||
|
eventBusRate: 847,
|
||||||
|
uptime: 99.95,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [roadchainStats, setRoadchainStats] = useState<RoadChainStats>({
|
||||||
|
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<WalletStats>({
|
||||||
|
balanceRC: 1247.89,
|
||||||
|
balanceUSD: 18705,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [minerStats, setMinerStats] = useState<MinerStats>({
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
5
br95/next-env.d.ts
vendored
Normal file
5
br95/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||||
9
br95/next.config.mjs
Normal file
9
br95/next.config.mjs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
experimental: {
|
||||||
|
appDir: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
21
br95/package.json
Normal file
21
br95/package.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
br95/tsconfig.json
Normal file
21
br95/tsconfig.json
Normal file
@@ -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"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user