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 (
{open &&
{nav}
}
); } // --------------------------------------------------------------------------- // Scene // --------------------------------------------------------------------------- function TavernFloor({ route, setRoute, auth }) { const stageRef = useRef(null); const [parallax, setParallax] = useState({ x: 0, y: 0 }); const [active, setActive] = useState(null); // hovered/selected character id const onMove = useCallback((e) => { const el = stageRef.current; if (!el) return; const r = el.getBoundingClientRect(); const px = (e.clientX - r.left) / r.width - 0.5; const py = (e.clientY - r.top) / r.height - 0.5; setParallax({ x: px, y: py }); }, []); const embers = useMemo( () => Array.from({ length: 26 }, (_, i) => ({ id: i, left: `${(i * 37) % 100}%`, delay: `${(i % 13) * 0.9}s`, dur: `${7 + (i % 6)}s`, size: 2 + (i % 3), })), [], ); return (
setParallax({ x: 0, y: 0 })}> {/* parallax tavern backdrop */}
{/* floating embers */} {/* characters on the floor */}
{CHARACTERS.map((c) => ( ))}
{/* warm light + vignette */} ); } function RoutePanel({ route, setRoute, auth }) { const data = panelData(route); const wide = route === 'admin' && auth.user?.role === 'admin'; return ( ); } function RouteBody({ data, route, setRoute }) { return ( <>
    {data.rows.map((r, i) => (
  • {r}
  • ))}
{data.stats.map((s, i) => ( {s} ))}
{data.actions.map((a, i) => ( ))}
); } export default function Scene2DApp() { const [route, setRoute] = useHashRoute(); const auth = useAuth(); return (

amerc agent mercenary tavern

); }