Files
52d89da5c9 Navbar/Footer: rewire stub /research#anchor links + fix invisible filter buttons (#3)
* Navbar: research AI & Machine Learning button → /models

The "AI & Machine Learning" item in the Research dropdown linked to
/research#ai (anchor on the research page, not a real sub-page).

Pointing it at /models — the canonical Zen Models page. /ai-models is
a redirect to /models, so /models is the source of truth.

* Navbar: research Consensus & Networks → zoo.network

The "Consensus & Networks" item in the Research dropdown linked to
/research#consensus (anchor, not a sub-page). Pointing it at
https://zoo.network — the actual Zoo Network site — and marking
it external so it opens in a new tab.

* Rewire remaining /research#anchor stubs + fix invisible filter button text

Navbar Research dropdown:
- Cryptography  /research#crypto  →  https://papers.zoo.ngo (external)

Navbar Models dropdown:
- Zen Coder  /models#coder  →  /models (anchor doesn't exist)
- Zen Omni   /models#omni   →  /models (anchor doesn't exist)

Footer Research column mirrors:
- AI & Machine Learning  /research#ai        →  /models
- Cryptography           /research#crypto    →  https://papers.zoo.ngo
- Consensus & Networks   /research#consensus →  https://zoo.network (external)
- Papers                 /research#papers    →  https://papers.zoo.ngo (external)

Research papers page filter buttons:
- Active filter buttons used bg-primary text-foreground, which renders
  black text on a black background (invisible). Switched both org="all"
  and active-topic buttons to text-primary-foreground so the text is
  white on black (the theme's contrast pair).

* Navbar: make dropdowns fully opaque (no background bleed-through)

All three dropdown panels (Try Zen, main desktop dropdowns, mobile menu)
used bg-secondary/95 or bg-secondary/98 — 95–98% opacity, letting page
content show through behind the menu items.

Switched to bg-secondary (fully opaque). Kept the backdrop-blur-xl for
the subtle soft edge, but the panel itself is now solid.

* Navbar: switch dropdowns from bg-secondary (50% alpha) to bg-popover (solid)

--secondary in globals.css is intentionally rgba(255,255,255,0.5) for the
brutalist pill design, so any bg-secondary class is inherently translucent.

The theme already defines --popover as a solid color (#ffffff light,
#0a0a0a dark) — that's the right semantic for an opaque dropdown panel.

* Nav/Footer: drop docs.zoo.ngo links (site is down)

Removed every button pointing at docs.zoo.ngo since the docs site is
currently broken:

- Products dropdown: drop "LLM Gateway"
- Developers dropdown: removed entirely (Documentation, Zoo MCP, SDKs,
  LLM Gateway all linked to docs.zoo.ngo)
- Footer Research column: drop "Documentation"

Moved "Case Studies" from the now-empty Developers dropdown into the
Research dropdown (it's the only non-docs item that lived under Developers).

* Navbar: hard-pin dropdown bg to literal colors so it can't ever be transparent

bg-popover still resolved to something translucent in render. Pinning
to literal bg-white (dark:bg-zinc-900) so the panel can't depend on any
theme-variable indirection.

* Navbar: dropdowns pinned to solid white (drop dark-mode variant)

Keeps all the original styling (backdrop-blur-xl, rounded corners, shadow,
border) but the bg is now literal bg-white in every theme, never translucent.

* Models dropdown: wire first three items to distinct, real destinations

All three top items pointed at /models — clicking them while on the
catalog page was a no-op. Adding id={family.id} + scroll-mt-24 to the
model family sections so in-page anchors actually work.

- Zen Models → /models (catalog)
- Zen Coder  → /models#code (scrolls to Code family section)
- Zen Omni   → https://huggingface.co/zenlm/zen-omni-8b (external)

* Logo: swap CMY (black-center) Venn for RGB additive (white-center) Venn

Matches the brand mark from the pitch deck — three overlapping circles
in green, red, blue with yellow/cyan/magenta two-way overlaps and a
clean white center where all three meet.

Same viewBox (1024x1024) so rendered size on the page is unchanged.

Files updated:
- public/zoo-logo.svg  (used by Navbar Logo, About page, Press page)
- app/icon.svg         (Next.js App Router auto-favicon)
- public/favicon.svg   (explicit <link rel=icon>)

Note: public/zoo-logo.png is orphaned (not referenced in code). favicon.ico
is a binary file no PNG/SVG tool can regenerate from here — modern
browsers will use favicon.svg anyway, so ico is a low-priority refresh
for a designer to redo if old-IE fallback matters.

* /models: restyle buttons and boxes to match home page brutalist design

- Hero badge: rounded-full pill → .pill .pill-pink
- Hero CTAs: rounded-full Button → .btn-brutalist .pill-pink / .pill-green
- Model cards: rounded-xl bg-foreground/5 → bg-white + border-2 border-black
  + offset shadow + press-into-shadow hover
- "Research Preview" badge: rounded-full → bordered yellow brutalist chip
- Capability tags: rounded-full muted → black chips with white type
- Tool cards (Infrastructure section): rounded-2xl → brutalist panel style
- Tool icon container: rounded-xl bg-primary → brand-magenta bordered square
- Quick Start code block: rounded-xl bg-secondary → black panel with brand-green
  mono text and the same offset shadow used everywhere else
- Final CTA buttons: rounded-full Button → .btn-brutalist .pill-pink / .pill-green

* Add /api page — Zen Model API docs + ecosystem context

New internal page at /api with:
- Hero: "One endpoint. Every modality." with brutalist CTAs to get a key,
  browse weights on Hugging Face, and the local /models catalog
- Modalities grid: 8 modalities (text, vision, image, video, audio, code,
  3D, agents) with the Zen model family that owns each
- Ecosystem cards: Zen MoDE, open weights, Proof of AI revenue share,
  $AI mining, DSO (ZIP-001),  rails — the load-bearing pieces
  someone needs to understand the network past "another LLM gateway"
- Endpoints table: OpenAI-compatible REST surface, including the
  multimodal endpoints (image, video, audio, agents) and rerank/embeddings
- Quick start: 3-line OpenAI SDK example pointed at api.zoo.network/v1
- Authentication: API key vs Zoo ID (DID-signed requests for agents)
- Pricing: $0 (self-host), per-token (hosted), 25% to OSS via PoAI
- Final CTAs: get key, Hugging Face, papers.zoo.ngo

Navbar Models dropdown: Model API button now links to /api instead of
the external api.zoo.network (which is the actual API host, not docs).

All sections use the home-page brutalist visual language (.pill, .btn-brutalist,
2px black borders + offset shadow) so the page reads as part of the same site.

* Navbar Products: Zoo Industries (the cloud item) → zoo.cloud

First item in the Products dropdown is "Zoo Industries — Full AI platform
and cloud". Pointing it at https://zoo.cloud (the cloud product surface)
instead of zoo.ngo (foundation site).

* /api: Zen Model API kicker — underlined text instead of pill box

* /api: bump space above Zen Model API kicker to match gap below it

* Drop stale favicon.ico so browsers use the new RGB-Venn favicon.svg

favicon.ico was a binary file still carrying the old CMY/black-center
logo, and many browsers auto-fetch /favicon.ico ahead of the <link
rel="icon" href="/favicon.svg"> in the head — so the tab kept showing
the old mark.

Removing the ico drops that override; modern browsers fall back to the
SVG, which matches the header logo. Old-IE loses its tab icon, which is
a fine trade for the header/tab actually matching.

* btn-brutalist: animated rainbow background

Replace the static var(--pill-color, var(--secondary)) fill on .btn-brutalist
with a 6-color linear-gradient (red → yellow → green → cyan → blue → magenta →
red) animated to scroll horizontally on a 6s loop.

The .pill-* modifier classes (.pill-pink, .pill-green, etc.) still set
--pill-color, which now lives only as the background-color fallback for
prefers-reduced-motion. Buttons themselves all share the rainbow.

Respects @media (prefers-reduced-motion: reduce) — the animation pauses
and the static fallback fill is shown instead.

.pill (the static heading kicker) is untouched and keeps its color-block
treatment — only the interactive .btn-brutalist gets the rainbow.

* btn-brutalist rainbow: scope to .pill-blue only

Previous commit blanket-applied the animated rainbow to every
.btn-brutalist. Walking that back — only the blue variant (.btn-brutalist.pill-blue)
gets the rainbow now. Pink, green, cyan, yellow, red, etc. keep their
solid pill-color fill.

Still respects prefers-reduced-motion: animation off, falls back to
solid var(--brand-blue).

* Header wordmark: ZOO AI → ZOO INDUSTRIES

* Header wordmark: drop 'INDUSTRIES' to light weight (ZOO stays extrabold)

* Section heads: 2x the gap between the colored pill and its underlined heading

ZooLabsNarrative SectionHead component and the /api page section heads
shared the same mt-5 md:mt-6 (1.25-1.5rem) gap between the .pill box and
the underlined heading below it. Doubled to mt-10 md:mt-12 (2.5-3rem) so
the box and heading read as distinct lines.

* Favicon: cache-bust the URL so browsers re-fetch the new RGB-Venn

All three favicon assets (public/favicon.svg, app/icon.svg, public/zoo-logo.svg)
were updated to the RGB-additive Venn earlier, and the stale favicon.ico
was removed — but browsers cache favicons aggressively per-origin and
will keep serving the old icon from cache.

Appending ?v=rgb to faviconPath forces a fresh fetch the next time the
page loads.

* Logo: continuous slow rotation in navbar/footer

Adds a 14-second linear infinite spin to the brand mark via a new
.logo-spin utility in globals.css, applied directly to the <Image> inside
the Logo component. Respects prefers-reduced-motion (animation off).

* /research: apply brutalist treatment to match home page

- Hero badge: rounded-full pill chip → .pill .pill-pink + bigger headline
- Research Area cards: rounded-xl bg-foreground/5 → bg-white + 2px black
  border + 6/10px offset shadow + press-into-shadow hover
- "View all on GitHub" / "View on GitHub" buttons: outline Button →
  .btn-brutalist .pill-green
- Open Source project cards: rounded-xl translucent → brutalist white panel
- Paper cards: rounded-xl → brutalist white panel, featured papers get a
  yellow offset shadow instead of black to stand out
- Featured / Org / Topic badges: rounded-full muted chips → flat bordered
  chips (featured = yellow, org/topic = black-on-black)
- Final CTA buttons: rounded-full Button → .btn-brutalist .pill-pink / .pill-green

* Add product pages for /cloud /bot /dev /chat /engine /edge

Six new (or rebuilt) internal pages for the core infrastructure products
that appear in the Products dropdown, all sharing the home-page
brutalist visual language (.pill, .btn-brutalist, 2px black panels with
hard offset shadows).

- /cloud   — Zoo Cloud, the umbrella platform. Cards link to each of the
  other product pages below + identity/storage/billing/audit underneath.
  Replaces the old redirect to /products/lux.
- /bot     — Zoo Bot, "AI team in a box". Six role cards + shared
  memory/tools/voice features + hand-off + budget + audit.
- /dev     — Zoo Dev, the engineering agent. Reads whole repo, runs tests,
  opens real PRs. Guardrails: allowlist commands, read-only by default,
  per-repo budget, audit log.
- /chat    — Zoo Chat. Five-input box (talk/see/draw/listen/code), E2E
  encrypted, on-chain billing, MCP tools.
- /engine  — Zoo Engine. Four chip families (H100/H200, MI300X, Trainium,
  pooled Apple silicon). Token-priced, latency-targeted, multi-region,
  PoAI revenue share, TEE-confidential.
- /edge    — Zoo Edge. Phone/laptop/watch/router targets with
  zen-nano/zen-eco. Offline-first, cloud fall-through, hardware-attested,
  signed OTA updates.

Navbar Products dropdown is rewired to point at these internal pages:
- Zoo Industries (external zoo.cloud) → Zoo Cloud (/cloud)
- Zoo Bot/Dev/Chat/Engine/Edge — now all internal pages, not external apps
- Zoo Team kept external (it's the consulting product, not a SaaS product)

---------

Co-authored-by: Antje Worring <[email protected]>
2026-05-13 14:05:41 -04:00

395 lines
19 KiB
TypeScript

'use client'
import { useState, useEffect, useRef, useCallback } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { Button } from '@hanzo/ui'
import {
ChevronDown, ExternalLink, Menu, X, Bot, Code2, Cloud, Cpu, MessageSquare,
BookOpen, Microscope, Brain, Shield, Network, Boxes, FlaskConical, FileText,
Github, Sparkles, Video, Box, Zap, Server, Smartphone,
} from 'lucide-react'
import { motion, AnimatePresence } from 'framer-motion'
import NavbarContainer from './navigation/NavbarContainer'
import Logo from './Logo'
import { cn } from '@/lib/utils'
import site from '@/site.config'
const zenModels = [
{ name: 'zen-eco', params: '4B', description: 'Fast general-purpose LLM', icon: Brain, href: 'https://huggingface.co/zenlm/zen-eco-4b-instruct' },
{ name: 'zen-omni', params: '8B', description: 'Multimodal vision + audio', icon: Sparkles, href: 'https://huggingface.co/zenlm/zen-omni-8b' },
{ name: 'zen-director', params: '5B', description: 'Text-to-video generation', icon: Video, href: 'https://huggingface.co/zenlm/zen-director-5b' },
{ name: 'zen-3d', params: '3.3B', description: '3D asset generation', icon: Box, href: 'https://huggingface.co/zenlm/zen-3d' },
]
const quickAccess = [
{ label: 'Zoo Dev', desc: 'AI coding agent', href: site.links.dev, external: true },
{ label: 'Zoo Bot', desc: 'AI team in a box', href: site.links.bot, external: true },
{ label: 'Zoo Team', desc: 'Work with Zoo engineers', href: site.links.team, external: true },
{ label: 'All Zen Models', desc: '600M-1T+ parameters', href: '/models', external: false },
]
const loginItems = [
{ label: 'Zoo Industries', href: site.links.platform, external: true },
{ label: 'Zoo Chat', href: site.links.chat, external: true },
{ label: 'Zoo Bot', href: site.links.botApp, external: true },
]
function TryZooDropdown() {
const [isOpen, setIsOpen] = useState(false)
const menuRef = useRef<HTMLDivElement>(null)
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const router = useRouter()
const clearTimeoutRef = useCallback(() => {
if (timeoutRef.current) { clearTimeout(timeoutRef.current); timeoutRef.current = null }
}, [])
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) setIsOpen(false)
}
if (isOpen) document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [isOpen])
return (
<div className="relative" ref={menuRef}
onMouseEnter={() => { clearTimeoutRef(); setIsOpen(true) }}
onMouseLeave={() => { clearTimeoutRef(); timeoutRef.current = setTimeout(() => setIsOpen(false), 800) }}
>
<button onClick={() => setIsOpen(!isOpen)}
className={cn(
'btn-brutalist pill-pink text-sm py-2 px-4',
isOpen && 'translate-x-[2px] translate-y-[2px] shadow-[6px_6px_0_0_#000]'
)}
>
Try Zen
<ChevronDown className={cn('w-4 h-4 transition-transform duration-200', isOpen && 'rotate-180')} />
</button>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.12, ease: 'easeOut' }}
className="absolute right-0 top-full w-[420px] max-w-[calc(100vw-2rem)] z-50"
>
<div aria-hidden className="h-5" />
<div className="backdrop-blur-xl border rounded-2xl shadow-2xl overflow-hidden bg-white border-border shadow-background/50">
{/* Zen AI Models */}
<div className="p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<Brain className="w-4 h-4 text-muted-foreground" />
<span className="text-xs font-semibold uppercase tracking-wider text-foreground">Zen AI Models</span>
</div>
<button onClick={() => { setIsOpen(false); router.push('/models') }}
className="text-xs transition-colors text-muted-foreground hover:text-foreground">
View all &rarr;
</button>
</div>
<div className="grid grid-cols-2 gap-2">
{zenModels.map((model) => {
const ModelIcon = model.icon
return (
<a key={model.name} href={model.href} target="_blank" rel="noopener noreferrer"
onClick={() => setIsOpen(false)}
className="group flex items-start gap-3 p-3 rounded-xl transition-colors bg-foreground/5 hover:bg-accent"
>
<div className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 bg-foreground/10">
<ModelIcon className="w-4 h-4 text-muted-foreground group-hover:text-foreground" />
</div>
<div className="min-w-0">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-foreground">{model.name}</span>
<span className="text-[10px] font-mono text-muted-foreground">{model.params}</span>
</div>
<p className="text-xs truncate text-muted-foreground">{model.description}</p>
</div>
</a>
)
})}
</div>
</div>
<div className="border-t border-border" />
{/* Quick Access */}
<div className="py-2">
<div className="px-4 py-1.5">
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">Quick Access</span>
</div>
{quickAccess.map((item) =>
item.external ? (
<a key={item.label} href={item.href} target="_blank" rel="noopener noreferrer"
onClick={() => setIsOpen(false)}
className="flex items-center justify-between w-full py-2 px-4 transition-colors text-foreground hover:bg-accent"
>
<div>
<span className="text-sm">{item.label}</span>
{item.desc && <span className="text-xs ml-2 text-foreground/30">{item.desc}</span>}
</div>
<ExternalLink className="w-3.5 h-3.5 text-foreground/30" />
</a>
) : (
<button key={item.label}
onClick={() => { setIsOpen(false); router.push(item.href) }}
className="flex items-center justify-between w-full py-2 px-4 text-left transition-colors text-foreground hover:bg-accent"
>
<div>
<span className="text-sm">{item.label}</span>
{item.desc && <span className="text-xs ml-2 text-foreground/30">{item.desc}</span>}
</div>
</button>
)
)}
</div>
<div className="border-t border-border" />
{/* Login */}
<div className="py-2">
<div className="px-4 py-1.5">
<span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">Log in</span>
</div>
{loginItems.map((item) => (
<a key={item.label} href={item.href} target="_blank" rel="noopener noreferrer"
onClick={() => setIsOpen(false)}
className="flex items-center justify-between w-full py-2 px-4 transition-colors text-foreground hover:bg-accent"
>
<span className="text-sm">{item.label}</span>
<ExternalLink className="w-3.5 h-3.5 text-foreground/30" />
</a>
))}
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
)
}
interface MenuItem {
label: string; href: string; description: string
icon?: React.ComponentType<{ className?: string }>; external?: boolean
}
interface MenuConfig { title: string; items: MenuItem[] }
const navMenus: Record<string, MenuConfig> = {
research: {
title: 'Research',
items: [
{ label: 'Overview', href: '/research', description: 'Our research mission and approach', icon: Microscope },
{ label: 'AI & Machine Learning', href: '/models', description: 'Frontier AI models and training', icon: Brain },
{ label: 'Cryptography', href: 'https://papers.zoo.ngo', description: 'Post-quantum and FHE research', icon: Shield, external: true },
{ label: 'Consensus & Networks', href: site.links.network, description: 'Distributed systems and blockchain', icon: Network, external: true },
{ label: 'Papers', href: '/research#papers', description: '130+ published research papers', icon: FileText },
{ label: 'Open Source', href: '/open-source', description: '727+ repos, revenue sharing', icon: Github },
{ label: 'Case Studies', href: '/case-studies', description: 'Real-world implementations', icon: Microscope },
],
},
models: {
title: 'Models',
items: [
{ label: 'Zen Models', href: '/models', description: '600M-1T+ parameter models', icon: Sparkles },
{ label: 'Zen Coder', href: '/models#code', description: 'Code generation and analysis', icon: Code2 },
{ label: 'Zen Omni', href: 'https://huggingface.co/zenlm/zen-omni-8b', description: 'Multimodal vision & audio', icon: Boxes, external: true },
{ label: 'Model API', href: '/api', description: 'API docs, endpoints, ecosystem', icon: Cpu },
{ label: 'Hugging Face', href: site.links.huggingFace, description: 'Download models', icon: Bot, external: true },
],
},
products: {
title: 'Products',
items: [
{ label: 'Zoo Cloud', href: '/cloud', description: 'Full AI platform and cloud', icon: Cloud },
{ label: 'Zoo Bot', href: '/bot', description: 'AI team in a box', icon: Bot },
{ label: 'Zoo Dev', href: '/dev', description: 'AI coding agent', icon: Code2 },
{ label: 'Zoo Chat', href: '/chat', description: 'AI chat & bot manager', icon: MessageSquare },
{ label: 'Zoo Engine', href: '/engine', description: 'Cloud GPU inference engine', icon: Server },
{ label: 'Zoo Edge', href: '/edge', description: 'On-device AI inference', icon: Smartphone },
{ label: 'Zoo Team', href: site.links.team, description: 'Work with Zoo engineers', icon: Boxes, external: true },
],
},
company: {
title: 'Company',
items: [
{ label: 'About', href: '/about', description: 'Our mission and values' },
{ label: 'Team', href: '/team', description: 'Leadership and AI workforce' },
{ label: 'Press', href: '/press', description: 'News and media coverage', icon: FileText },
{ label: 'Careers', href: '/careers', description: 'Join us' },
{ label: 'Contact', href: '/contact', description: 'Get in touch' },
],
},
}
function DropdownMenu({ menu, isOpen, onOpen, onClose }: { menu: MenuConfig; isOpen: boolean; onOpen: () => void; onClose: () => void }) {
const menuRef = useRef<HTMLDivElement>(null)
const router = useRouter()
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const clearTimeoutRef = useCallback(() => {
if (timeoutRef.current) { clearTimeout(timeoutRef.current); timeoutRef.current = null }
}, [])
useEffect(() => { return () => clearTimeoutRef() }, [clearTimeoutRef])
const handleItemClick = (item: MenuItem) => {
onClose()
if (item.external) window.open(item.href, '_blank')
else router.push(item.href)
}
return (
<div className="relative" ref={menuRef}
onMouseEnter={() => { clearTimeoutRef(); onOpen() }}
onMouseLeave={() => { clearTimeoutRef(); timeoutRef.current = setTimeout(onClose, 800) }}
>
<button onClick={() => isOpen ? onClose() : onOpen()}
className={cn(
'flex items-center gap-1 font-medium transition-all duration-200 text-sm py-2 px-3 rounded-lg',
isOpen ? 'text-foreground bg-foreground/10' : 'text-muted-foreground hover:text-foreground hover:bg-accent'
)}
>
{menu.title}
<ChevronDown className={cn('w-4 h-4 transition-transform duration-200', isOpen && 'rotate-180')} />
</button>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.12, ease: 'easeOut' }}
className="absolute left-0 top-full w-80 max-w-[calc(100vw-2rem)] z-50"
onMouseEnter={() => { clearTimeoutRef(); onOpen() }}
onMouseLeave={() => { clearTimeoutRef(); timeoutRef.current = setTimeout(onClose, 800) }}
>
<div aria-hidden className="h-5" />
<div className="backdrop-blur-xl border rounded-xl shadow-2xl overflow-hidden bg-white border-border shadow-background/50">
<div className="py-2">
{menu.items.map((item) => {
const Icon = item.icon
return (
<button key={item.label} onClick={() => handleItemClick(item)}
className="w-full text-left px-4 py-3 transition-all duration-150 group flex items-center gap-3 hover:bg-accent"
>
{Icon && (
<div className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 transition-colors bg-foreground/10 group-hover:bg-accent">
<Icon className="w-4 h-4 text-muted-foreground group-hover:text-foreground" />
</div>
)}
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<span className="font-medium text-sm transition-colors duration-150 text-foreground/90 group-hover:text-foreground">{item.label}</span>
{item.external && <ExternalLink className="w-3.5 h-3.5 flex-shrink-0 text-foreground/30 group-hover:text-muted-foreground" />}
</div>
<p className="text-xs mt-0.5 transition-colors duration-150 truncate text-muted-foreground group-hover:text-muted-foreground">{item.description}</p>
</div>
</button>
)
})}
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
)
}
export default function Navbar() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
const [openMenu, setOpenMenu] = useState<string | null>(null)
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') { setOpenMenu(null); setIsMobileMenuOpen(false) }
}
document.addEventListener('keydown', handleEscape)
return () => document.removeEventListener('keydown', handleEscape)
}, [])
return (
<NavbarContainer>
<Logo size="md" showText={true} />
<div className="hidden md:flex items-center space-x-1">
{Object.entries(navMenus).map(([key, menu]) => (
<DropdownMenu key={key} menu={menu}
isOpen={openMenu === key}
onOpen={() => setOpenMenu(key)}
onClose={() => setOpenMenu(null)}
/>
))}
</div>
<div className="hidden md:flex items-center space-x-3">
<TryZooDropdown />
</div>
<button
className="md:hidden p-2 rounded-lg transition-all duration-200 text-muted-foreground hover:text-foreground hover:bg-accent"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
>
<AnimatePresence mode="wait">
{isMobileMenuOpen ? (
<motion.div key="close" initial={{ rotate: -90, opacity: 0 }} animate={{ rotate: 0, opacity: 1 }} exit={{ rotate: 90, opacity: 0 }} transition={{ duration: 0.15 }}>
<X className="w-6 h-6" />
</motion.div>
) : (
<motion.div key="menu" initial={{ rotate: 90, opacity: 0 }} animate={{ rotate: 0, opacity: 1 }} exit={{ rotate: -90, opacity: 0 }} transition={{ duration: 0.15 }}>
<Menu className="w-6 h-6" />
</motion.div>
)}
</AnimatePresence>
</button>
<AnimatePresence>
{isMobileMenuOpen && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.2 }}
className="absolute top-full left-0 right-0 md:hidden backdrop-blur-xl border-t rounded-b-2xl overflow-hidden bg-white border-border"
>
<div className="py-4 space-y-4 px-4 max-h-[70vh] overflow-y-auto">
{Object.entries(navMenus).map(([key, menu]) => (
<div key={key} className="space-y-2">
<div className="text-xs font-medium uppercase tracking-wider px-2 text-muted-foreground">{menu.title}</div>
{menu.items.map((item) => (
<Link key={item.label}
href={item.external ? '#' : item.href}
onClick={(e) => {
if (item.external) { e.preventDefault(); window.open(item.href, '_blank') }
setIsMobileMenuOpen(false)
}}
className="flex items-center justify-between py-2 px-2 rounded-lg transition-all duration-150 text-muted-foreground hover:text-foreground hover:bg-accent"
>
<span>{item.label}</span>
{item.external && <ExternalLink className="w-3.5 h-3.5 text-foreground/30" />}
</Link>
))}
</div>
))}
<div className="pt-4 space-y-2 border-t border-border">
<Link href="/contact" onClick={() => setIsMobileMenuOpen(false)}>
<Button variant="outline" className="w-full border-border text-foreground hover:bg-accent">Contact</Button>
</Link>
<a href={site.links.platform} target="_blank" rel="noopener noreferrer">
<Button className="w-full bg-primary text-primary-foreground hover:bg-primary/90">Try Zen</Button>
</a>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</NavbarContainer>
)
}