922a57fe34
Self-hosted 開源:WASM 零件 + recipe + cypher-executor,跑在你自己的 Cloudflare。 此為重建的乾淨歷史起點(移除曾誤 commit 的 GCP SA 金鑰,舊歷史保留在 richblack/arcrun 與本地 backup 分支)。含: - acr init --self-hosted installer(建 KV/R2 + codeload 拉預編譯 wasm + wrangler deploy + seed recipe) - recipe push 把關(資料外流提醒 + 打通檢查) - 19 個正當零件預編譯 wasm(claude_api/km_writer/kbdb_upsert_block 排除:違反 DECISIONS §1) - CLI / cypher-executor / registry / 完整 SDD Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
93 lines
3.0 KiB
React
93 lines
3.0 KiB
React
// Top nav and sidebar
|
|
|
|
const TopNav = ({ onNav, current }) => {
|
|
const [scrolled, setScrolled] = React.useState(false);
|
|
React.useEffect(() => {
|
|
const onScroll = () => setScrolled(window.scrollY > 8);
|
|
window.addEventListener('scroll', onScroll);
|
|
return () => window.removeEventListener('scroll', onScroll);
|
|
}, []);
|
|
return (
|
|
<nav className={`topnav ${scrolled ? 'scrolled' : ''}`}>
|
|
<div className="flex gap-12" style={{alignItems: 'center'}}>
|
|
<Logo onClick={() => onNav('landing')} />
|
|
<div className="nav-links" style={{marginLeft: 20}}>
|
|
<a>Product</a>
|
|
<a>Docs</a>
|
|
<a>Pricing</a>
|
|
<a>Changelog</a>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-8" style={{alignItems: 'center'}}>
|
|
<button className="btn btn-ghost" onClick={() => onNav('auth')}>Log in</button>
|
|
<button className="btn btn-primary" onClick={() => onNav('auth')}>
|
|
Get started <Icon name="arrow_right" size={14} />
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
);
|
|
};
|
|
|
|
const Footer = ({ onNav }) => (
|
|
<footer className="footer">
|
|
<div className="flex gap-12" style={{alignItems: 'center'}}>
|
|
<Logo size="sm" />
|
|
<span>© 2026 Arcrun Labs</span>
|
|
</div>
|
|
<div className="footer-links">
|
|
<a>Docs</a>
|
|
<a>Pricing</a>
|
|
<a>Changelog</a>
|
|
<a>Status</a>
|
|
<a>Privacy</a>
|
|
</div>
|
|
</footer>
|
|
);
|
|
|
|
// App shell with sidebar for logged-in screens
|
|
const Sidebar = ({ current, onNav }) => {
|
|
const items = [
|
|
{ id: 'dashboard', label: 'Dashboard', icon: 'home' },
|
|
{ id: 'apps', label: 'Apps', icon: 'grid', count: 6 },
|
|
{ id: 'workflows', label: 'Workflows', icon: 'workflow', count: 12 },
|
|
{ id: 'keys', label: 'API Keys', icon: 'key' },
|
|
{ id: 'docs', label: 'Docs', icon: 'book' },
|
|
];
|
|
const bottom = [
|
|
{ id: 'settings', label: 'Settings', icon: 'settings' },
|
|
];
|
|
return (
|
|
<aside className="sidebar">
|
|
<div className="sidebar-head">
|
|
<Logo size="md" onClick={() => onNav('landing')} />
|
|
</div>
|
|
<div className="sidebar-section">Workspace</div>
|
|
{items.map(it => (
|
|
<div key={it.id}
|
|
className={`sidebar-item ${current === it.id ? 'active' : ''}`}
|
|
onClick={() => onNav(it.id)}>
|
|
<span className="sb-ico"><Icon name={it.icon} size={15} /></span>
|
|
<span>{it.label}</span>
|
|
{it.count != null && <span className="sb-count">{it.count}</span>}
|
|
</div>
|
|
))}
|
|
<div style={{flex: 1}} />
|
|
{bottom.map(it => (
|
|
<div key={it.id} className="sidebar-item" onClick={() => onNav(it.id)}>
|
|
<span className="sb-ico"><Icon name={it.icon} size={15} /></span>
|
|
<span>{it.label}</span>
|
|
</div>
|
|
))}
|
|
<div className="sidebar-foot">
|
|
<div className="avatar-circ">MR</div>
|
|
<div className="meta">
|
|
<div className="name">Maya Rivera</div>
|
|
<div className="email">maya@northwind.co</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
);
|
|
};
|
|
|
|
Object.assign(window, { TopNav, Footer, Sidebar });
|