| 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 & 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> |
| ); |
| } |
|
|