import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Menu, X } from 'lucide-react';
import { useAuth, AuthPanel, AdminConsole } from './Auth.jsx';
const RELEASE = '0.20.0-suite';
// ---------------------------------------------------------------------------
// Routing
// ---------------------------------------------------------------------------
const NAV = [
{ id: 'home', label: 'Solution' },
{ id: 'agents', label: 'Staff' },
{ id: 'contracts', label: 'Pricing' },
{ id: 'signin', label: 'Login' },
{ id: 'booth', label: 'My Booth' },
];
const VALID_ROUTES = ['home', 'agents', 'booth', 'contracts', 'admin', 'signin'];
function useHashRoute() {
const normalize = () => {
const raw = window.location.hash.replace(/^#\/?/, '');
return VALID_ROUTES.includes(raw) ? raw : 'home';
};
const [route, setRouteState] = useState(normalize);
useEffect(() => {
const onHash = () => setRouteState(normalize());
window.addEventListener('hashchange', onHash);
window.addEventListener('popstate', onHash);
return () => {
window.removeEventListener('hashchange', onHash);
window.removeEventListener('popstate', onHash);
};
}, []);
const setRoute = useCallback((next) => {
const nextHash = `/${next}`;
if (window.location.hash !== `#${nextHash}`) {
window.history.pushState(null, '', `${window.location.pathname}${window.location.search}#${nextHash}`);
}
setRouteState(next);
}, []);
return [route, setRoute];
}
// ---------------------------------------------------------------------------
// Route panel copy (carried over from the 3D scene so nothing regresses)
// ---------------------------------------------------------------------------
function panelData(route) {
const panels = {
home: {
title: 'Browse Agents',
subtitle: 'Walk the tavern floor and inspect the mercenary roster.',
rows: ['Vertical site embeds a command booth', 'Customer backend mints a scoped session', 'Agent acts through supplied skills in tinybox'],
stats: ['12,458 sessions', '7,326 deliveries', '+23.7% usage'],
actions: ['Inspect Roster', 'Publish Agent', 'Remote Use', 'Port Mapping', 'Run Directly'],
tint: '#27d8ff',
},
agents: {
title: 'Mercenary Staff',
subtitle: 'Specialists with scoped tools, runtime limits, and visible feedback.',
rows: ['Iron Tusk / site ops / 18 coin per hour', 'Velvet Hex / user consent / 14 coin per hour', 'Socket Wraith / tinybox bridge / 16 coin per hour'],
stats: ['6 active ranks', 'SLA visible', 'skills scoped'],
actions: ['Filter by Skill', 'Inspect Runtime', 'Hire Specialist', 'Watch Feedback', 'Swap Agent'],
tint: '#27d8ff',
},
booth: {
title: 'My Booth',
subtitle: 'Mount, message, inspect, and carry personal agents into websites.',
rows: ['Iron Tusk / running / cart.update / 00:13', 'Socket Wraith / waiting / claim.lookup / 02:41', 'Nightforge / paused / terminal.chat / 18:09'],
stats: ['3 mounted', '4 callbacks', 'healthy'],
actions: ['Mount Agent', 'Open TUI', 'Send Message', 'Review Logs', 'Detach Booth'],
tint: '#36f0b0',
},
contracts: {
title: 'Contract Pricing',
subtitle: 'Three paths: embedded agent, bring-your-own agent, or hosted agent.',
rows: ['Site hires agent / API key to session', 'User brings agent / connect amerc', 'Hosted mercenary / TUI operations'],
stats: ['session', 'skills', 'runtime'],
actions: ['Create API Key', 'Bind Skills', 'Set Budget', 'Publish Terms', 'Start Session'],
tint: '#f2b85f',
},
admin: {
title: 'Quartermaster',
subtitle: 'Tenant, key, session, skill, and risk controls for operators.',
rows: ['northstar-retail / embedded / green', 'claims-lab / byoa / amber', 'sandbox / hosted / red'],
stats: ['tenants', 'keys', 'risk queue'],
actions: ['Audit Tenants', 'Rotate Keys', 'Trace Sessions', 'Block Skill', 'Review Risk'],
tint: '#f2b85f',
},
signin: {
title: 'Open Booth',
subtitle: 'Sign in to the mercenary control surface.',
rows: ['Guild handle / operator@vertical-site.ai', 'Access sigil / ************', `Release / ${RELEASE}`],
stats: ['preview', 'amerc.ai', 'frontend'],
actions: ['Choose Account', 'Link Agent', 'Grant Scope', 'Enter Booth', 'Return to Site'],
tint: '#9d73ff',
},
};
return panels[route] || panels.home;
}
// Characters that live on the tavern floor. Clicking one routes you.
const CHARACTERS = [
{
id: 'orc', sprite: '/scene2d/orc.png', route: 'agents',
name: 'Grommash', role: 'Bartender', say: 'Pick a mercenary, friend.',
left: '72%', h: 460, z: 6, flip: false, bob: 3.4,
},
{
id: 'elf', sprite: '/scene2d/elf.png', route: 'home',
name: 'Aelis', role: 'Guide', say: 'Welcome to amerc. This way.',
left: '46%', h: 420, z: 5, flip: false, bob: 4.2,
},
{
id: 'dwarf', sprite: '/scene2d/dwarf.png', route: 'booth',
name: 'Durn', role: 'Patron', say: 'My booth runs three agents.',
left: '20%', h: 360, z: 4, flip: true, bob: 3.0,
},
];
// ---------------------------------------------------------------------------
// Top bar
// ---------------------------------------------------------------------------
function PixelTopbar({ route, setRoute, auth }) {
const [open, setOpen] = useState(false);
const go = (id) => { setRoute(id); setOpen(false); };
const loggedIn = !!auth?.user;
const isAdmin = auth?.user?.role === 'admin';
const nav = (
<>
{NAV.filter((item) => !(item.id === 'signin' && loggedIn)).map((item) => (
))}
{isAdmin && (
)}
{loggedIn && (
)}
>
);
return (