refactor: externalize tool definitions and polish app shell

- Create lib/tools.tsx as single source of truth for all tool metadata
  (title, shortTitle, navTitle, description, summary, icon, etc.)
- Update AppSidebar to render nav from centralized tools list with
  descriptions, remove collapse footer button
- Update AppHeader with sidebar collapse toggle, tool short title,
  and app logo; remove breadcrumbs
- Update AppPage to auto-resolve tool icon from pathname
- Update ToolsGrid/ToolCard to use shared tools data, remove per-card
  gradients for uniform styling
- Add per-tool HTML title via metadata exports (title template in root
  layout)
- Style landing page and 404 headings with primary theme color
- Add Toolbox icon to hero CTA, GitFork icon link in footer
- Remove footer from error page and "View on Dev" buttons
- Extract ColorPage client component for RSC metadata compatibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 17:46:54 +01:00
parent 5a0d1863ec
commit a400f694fe
18 changed files with 679 additions and 912 deletions

View File

@@ -1,63 +1,42 @@
'use client';
import * as React from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { Menu, ChevronRight, X } from 'lucide-react';
import { Menu, X, PanelLeftClose, PanelLeftOpen } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils/cn';
import { useSidebar } from './SidebarProvider';
import Logo from '@/components/Logo';
export function AppHeader() {
const pathname = usePathname();
const { toggle, isOpen } = useSidebar();
// Custom breadcrumb logic
const pathSegments = pathname.split('/').filter(Boolean);
const { toggle, isOpen, isCollapsed, toggleCollapse } = useSidebar();
return (
<header className="h-16 border-b border-border bg-background/10 backdrop-blur-xl sticky top-0 z-40 flex items-center justify-between pl-8 pr-5 md:pr-9 shadow-[0_1px_3px_0_rgb(0_0_0/0.05)]">
<div className="flex items-center gap-4">
<nav className="flex items-center text-sm font-medium">
<Link href="/" className="flex items-center gap-2">
<Logo size={20} className="lg:hidden" />
<span className="font-medium transition-colors text-foreground/80 hover:text-foreground">
Kit
</span>
</Link>
{pathSegments.map((segment, index) => {
const href = `/${pathSegments.slice(0, index + 1).join('/')}`;
const isLast = index === pathSegments.length - 1;
return (
<React.Fragment key={href}>
<ChevronRight className="h-4 w-4 mx-1 text-muted-foreground/50" />
<Link
href={href}
className={cn(
"capitalize transition-colors text-foreground/80 hover:text-foreground",
isLast ? "font-semibold" : "font-medium"
)}
>
{segment.replace(/-/g, ' ')}
</Link>
</React.Fragment>
);
})}
</nav>
<header className="h-16 border-b border-border bg-background/10 backdrop-blur-xl sticky top-0 z-40 flex items-center justify-between px-6 shadow-[0_1px_3px_0_rgb(0_0_0/0.05)]">
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
className="hidden lg:inline-flex text-muted-foreground hover:text-foreground"
onClick={toggleCollapse}
>
{isCollapsed ? (
<PanelLeftOpen className="h-5 w-5" />
) : (
<PanelLeftClose className="h-5 w-5" />
)}
</Button>
<Link href="/" className="lg:hidden shrink-0 ml-2">
<Logo size={24} />
</Link>
</div>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
className="lg:hidden text-muted-foreground hover:text-foreground"
onClick={toggle}
>
{isOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
</Button>
</div>
<Button
variant="ghost"
size="icon"
className="lg:hidden text-muted-foreground hover:text-foreground"
onClick={toggle}
>
{isOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
</Button>
</header>
);
}
}