HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1'use client';2import { Activity, Boxes, Home, MoreHorizontal, Search } from 'lucide-react';3import Link from 'next/link';4import { usePathname } from 'next/navigation';5import { useEffect, useState } from 'react';6import { Sheet } from '@/components/ui/sheet';7import { cn } from '@/lib/cn';8import { moreGroups, routes } from '@/lib/site';9import { useOpenSearch } from './search-context';1011const ICONS = { '/': Home, '/models': Boxes, '/changes': Activity } as const;1213/**14 * Bottom tab bar (< lg): Home · Models · Changes · Search · More. "More" opens a bottom sheet with the full site grouped15 * (Explore / Intelligence / Temporal / About). Safe-area aware; <body> reserves `pb-[calc(var(--tabbar-h)+safe-area)]`.16 */17export function MobileTabBar() {18 const pathname = usePathname();19 const openSearch = useOpenSearch();20 const [more, setMore] = useState(false);21 useEffect(() => setMore(false), [pathname]);22 const isActive = (href: string) => (href === '/' ? pathname === '/' : pathname === href || pathname.startsWith(href + '/'));23 const cls = (active: boolean) => cn('flex h-full w-full flex-col items-center justify-center gap-0.5 text-[10.5px]', active ? 'text-accent' : 'text-ink-2');24 const tabs = [25 { href: '/', label: 'Home' },26 { href: '/models', label: 'Models' },27 { href: '/changes', label: 'Changes' },28 ] as const;29 return (30 <>31 <Sheet open={more} onClose={() => setMore(false)} side="bottom" eyebrow="AI Atlas" title="Everything on the site" id="mobile-more" className="lg:hidden">32 <div className="grid gap-5 pb-2 sm:grid-cols-2">33 {moreGroups.map((g) => (34 <div key={g.label}>35 <p className="eyebrow mb-1">{g.label}</p>36 <ul className="grid grid-cols-2 gap-x-3 sm:grid-cols-1">37 {g.items.map((n) => (38 <li key={n.href + n.label}>39 <Link href={n.href} className={cn('flex min-h-11 items-center py-2 text-[15px]', isActive(n.href) ? 'text-ink font-medium' : 'text-ink-2 hover:text-ink')}>40 {n.label}41 </Link>42 </li>43 ))}44 </ul>45 </div>46 ))}47 </div>48 <p className="border-t border-rule pt-3 text-xs text-ink-3">49 <Link href={routes.explore()} className="link">50 All entity types with live counts →51 </Link>52 </p>53 </Sheet>54 <nav aria-label="Primary (mobile)" className="safe-bottom fixed inset-x-0 bottom-0 z-[46] border-t border-rule bg-canvas/95 backdrop-blur-md lg:hidden">55 <ul className="grid h-[var(--tabbar-h)] grid-cols-5">56 {tabs.map((t) => {57 const Icon = ICONS[t.href];58 const active = isActive(t.href);59 return (60 <li key={t.href} className="min-w-0">61 <Link href={t.href} aria-current={active ? 'page' : undefined} className={cls(active)}>62 <Icon size={21} aria-hidden strokeWidth={active ? 2.25 : 1.75} />63 <span className="truncate">{t.label}</span>64 </Link>65 </li>66 );67 })}68 <li className="min-w-0">69 <button type="button" onClick={openSearch} className={cls(false)} aria-label="Search">70 <Search size={21} aria-hidden strokeWidth={1.75} />71 <span className="truncate">Search</span>72 </button>73 </li>74 <li className="min-w-0">75 <button type="button" onClick={() => setMore((m) => !m)} aria-expanded={more} aria-controls="mobile-more" className={cls(more)} data-mobile-more>76 <MoreHorizontal size={21} aria-hidden strokeWidth={1.75} />77 <span className="truncate">More</span>78 </button>79 </li>80 </ul>81 </nav>82 </>83 );84}85