proteinea / src /app /layout.tsx
Mahmoud Eljendy
feat: Antibody Studio — AI-native antibody design workspace by Proteinea
30cc31a
import type { Metadata } from "next";
import Script from "next/script";
import { Libre_Baskerville } from "next/font/google";
import "./globals.css";
const serif = Libre_Baskerville({
subsets: ["latin"],
weight: ["400", "700"],
variable: "--font-serif",
display: "swap",
});
import TaskList from "./_components/TaskList";
import ProjectHeader from "./_components/ProjectHeader";
import FilesNavLink from "./_components/FilesNavLink";
import Providers from "./_components/Providers";
export const metadata: Metadata = {
title: "Antibody Studio | Proteinea",
description: "AI-native antibody design workspace",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className={serif.variable}>
<body className="h-screen flex bg-background text-foreground antialiased">
{/* Sidebar — desktop only, pure server HTML */}
<aside className="hidden md:flex w-60 shrink-0 bg-card border-r border-border flex-col">
{/* Logo */}
<div className="px-5 py-4 border-b border-border">
<div className="flex items-center gap-2.5">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-accent/15 to-accent/5 flex items-center justify-center border border-accent/10">
<svg className="w-4.5 h-4.5 text-accent" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5}>
<path d="M2 15c6.667-6 13.333 0 20-6" />
<path d="M2 9c6.667 6 13.333 0 20 6" />
</svg>
</div>
<div>
<p className="text-sm font-semibold text-foreground">Antibody Studio</p>
<p className="text-[10px] text-muted-fg">Fc Engineering Workspace</p>
</div>
</div>
</div>
{/* Navigation */}
<nav className="flex-1 p-3 space-y-5">
<div>
<p className="text-[10px] font-semibold uppercase tracking-widest text-muted-fg px-2 mb-1.5">
Workspace
</p>
<NavLink href="/chat" icon="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z">
Chat
</NavLink>
<NavLink href="/campaigns" icon="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z">
Campaigns
</NavLink>
<FilesNavLink />
</div>
<div>
<p className="text-[10px] font-semibold uppercase tracking-widest text-muted-fg px-2 mb-1.5">
Explore
</p>
<NavLink href="/benchmarks" icon="M3 12h4l3-9 4 18 3-9h4">
Benchmarks
</NavLink>
<NavLink href="/models" icon="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5">
Models
</NavLink>
<NavLink href="/tools" icon="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z">
Tools
</NavLink>
</div>
<div>
<ProjectHeader projectName="Quick Tasks" />
<TaskList />
</div>
<div>
<p className="text-[10px] font-semibold uppercase tracking-widest text-muted-fg px-2 mb-1.5">
Resources
</p>
<div className="space-y-1 px-2">
<ResourceItem label="Benchmarks" count={5} />
<ResourceItem label="Models" count={10} />
<ResourceItem label="Targets" count={16} />
<ResourceItem label="Tools" count={15} />
</div>
</div>
</nav>
{/* Footer */}
<div className="px-4 py-3 border-t border-border flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="w-1.5 h-1.5 rounded-full bg-success" />
<span className="text-[11px] text-muted-fg">Engine ready</span>
</div>
<a
href="/help"
className="flex items-center gap-1.5 text-[11px] text-muted-fg hover:text-foreground transition-colors"
title="Help &amp; onboarding"
>
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5}>
<circle cx="12" cy="12" r="10" />
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
Help
</a>
</div>
</aside>
{/* Mobile header */}
<div className="md:hidden fixed top-0 inset-x-0 z-50 h-13 bg-card/95 backdrop-blur border-b border-border flex items-center px-4 gap-3">
<div className="w-7 h-7 rounded-lg bg-accent/10 flex items-center justify-center">
<svg className="w-4 h-4 text-accent" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5}>
<path d="M2 15c6.667-6 13.333 0 20-6" />
<path d="M2 9c6.667 6 13.333 0 20 6" />
</svg>
</div>
<span className="text-sm font-semibold">Antibody Studio</span>
<div className="ml-auto flex gap-1">
<MobileNavLink href="/chat">Chat</MobileNavLink>
<MobileNavLink href="/benchmarks">Data</MobileNavLink>
<MobileNavLink href="/models">Models</MobileNavLink>
<MobileNavLink href="/help">Help</MobileNavLink>
</div>
</div>
{/* Main content — wrapped in client-side providers for user identity */}
<Providers>
<main className="flex-1 min-w-0 overflow-hidden flex flex-col md:pt-0 pt-13">
{children}
</main>
</Providers>
{/* 3Dmol.js — loaded for StructureViewer. Component also has a runtime
loader fallback, but injecting here avoids first-view latency. */}
<Script
src="https://cdn.jsdelivr.net/npm/3dmol@2.4.2/build/3Dmol-min.js"
strategy="afterInteractive"
data-3dmol-loader="true"
/>
</body>
</html>
);
}
function NavLink({
href,
icon,
children,
}: {
href: string;
icon: string;
children: React.ReactNode;
}) {
return (
<a
href={href}
className="flex items-center gap-2.5 px-3 py-2 rounded-lg text-sm text-foreground-dim hover:bg-muted hover:text-foreground transition-colors"
>
<svg className="w-4 h-4 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.5}>
<path d={icon} />
</svg>
{children}
</a>
);
}
function MobileNavLink({ href, children }: { href: string; children: React.ReactNode }) {
return (
<a
href={href}
className="text-[11px] px-2.5 py-1 rounded-md text-foreground-dim hover:bg-muted hover:text-foreground transition-colors"
>
{children}
</a>
);
}
function ResourceItem({ label, count }: { label: string; count: number }) {
return (
<div className="flex items-center justify-between py-1">
<span className="text-[11px] text-foreground-dim">{label}</span>
<span className="text-[10px] font-mono text-muted-fg bg-muted rounded-md px-1.5 py-0.5">{count}</span>
</div>
);
}