fix: replace hardcoded dark mode colors with theme-aware semantic tokens in tool-search page

- Replace bg-black with bg-background
- Replace text-zinc-100 with text-foreground
- Replace text-zinc-400 with text-foreground-secondary
- Replace text-zinc-500 with text-foreground-tertiary
- Ensures page respects light/dark theme toggle

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-11-27 15:37:46 +10:00
parent 036b04b0bd
commit 70f87a60b2
21 changed files with 1458 additions and 79 deletions

View file

@ -92,6 +92,10 @@
--tracking-wide: 0.025em;
--tracking-wider: 0.05em;
--tracking-widest: 0.1em;
/* Brutalist Accent Colors */
--brutalist-accent: 221 83% 53%; /* #0066ff - Electric blue for light mode */
--brutalist-accent-hover: 221 83% 43%;
}
/* Dark mode (opt-in) - Vercel/Cursor/Perplexity aesthetic */
@ -148,6 +152,10 @@
/* Card */
--card: 210 10% 8%;
--card-foreground: 210 10% 90%;
/* Brutalist Accent Colors */
--brutalist-accent: 158 100% 50%; /* #00ff88 - Neon green for dark mode */
--brutalist-accent-hover: 158 100% 40%;
}
/* Base element styles */
@ -321,4 +329,134 @@
);
background-size: 1000px 100%;
}
/* Brutalist Typography */
.brutalist-heading {
font-size: clamp(48px, 8vw, 120px);
font-weight: 800;
letter-spacing: -0.05em;
line-height: 0.9;
text-transform: uppercase;
}
.brutalist-subheading {
font-size: clamp(24px, 4vw, 56px);
font-weight: 700;
letter-spacing: -0.025em;
line-height: 1.1;
text-transform: uppercase;
}
/* Brutalist Borders */
.brutalist-border {
border: 6px solid hsl(var(--foreground));
border-radius: 0;
}
.brutalist-border-thick {
border: 8px solid hsl(var(--foreground));
border-radius: 0;
}
.brutalist-border-mega {
border: 12px solid hsl(var(--foreground));
border-radius: 0;
}
/* Brutalist Shadows - Hard, not soft */
.brutalist-shadow {
box-shadow: 0 10px 0 0 hsl(var(--brutalist-accent));
}
.brutalist-shadow-hover {
box-shadow: 0 12px 0 0 hsl(var(--brutalist-accent));
}
.brutalist-shadow-sm {
box-shadow: 0 6px 0 0 hsl(var(--brutalist-accent));
}
/* Glitch Animation */
@keyframes glitch {
0%,
100% {
transform: translateX(0);
}
20% {
transform: translateX(-5px);
}
40% {
transform: translateX(5px);
}
60% {
transform: translateX(-3px);
}
80% {
transform: translateX(3px);
}
}
.animate-glitch {
animation: glitch 500ms ease-in-out;
}
/* Brutalist Entrance Animation */
@keyframes brutalist-entrance {
from {
transform: translateY(40px) scale(0.9);
opacity: 0;
}
to {
transform: translateY(0) scale(1);
opacity: 1;
}
}
.animate-brutalist-entrance {
animation: brutalist-entrance 600ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
/* Bar Growth Animation */
@keyframes bar-grow {
from {
transform: scaleY(0);
transform-origin: bottom;
}
to {
transform: scaleY(1);
transform-origin: bottom;
}
}
.animate-bar-grow {
animation: bar-grow 800ms ease-out forwards;
}
/* Stagger delays for entrance animations */
.stagger-1 {
animation-delay: 80ms;
}
.stagger-2 {
animation-delay: 160ms;
}
.stagger-3 {
animation-delay: 240ms;
}
.stagger-4 {
animation-delay: 320ms;
}
.stagger-5 {
animation-delay: 400ms;
}
/* Motion Preferences - Accessibility */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
}

View file

@ -4,9 +4,11 @@ import { Card, CardContent } from '@tpmjs/ui/Card/Card';
import { Container } from '@tpmjs/ui/Container/Container';
import { Header } from '@tpmjs/ui/Header/Header';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { Input } from '@tpmjs/ui/Input/Input';
import { StatCard } from '@tpmjs/ui/StatCard/StatCard';
import Link from 'next/link';
import { ThemeToggle } from '../components/ThemeToggle';
import { CategoryGrid } from '../components/home/CategoryGrid';
import { HeroSection } from '../components/home/HeroSection';
import { categories, featuredTools, statistics } from '../data/homePageData';
export default function HomePage(): React.ReactElement {
@ -50,49 +52,42 @@ export default function HomePage(): React.ReactElement {
/>
<main className="flex-1">
{/* Hero Section */}
<section className="relative py-24 overflow-hidden dotted-grid-background">
<div className="absolute inset-0 bg-background/95" />
<Container size="xl" padding="lg">
<div className="max-w-3xl mx-auto text-center space-y-8 relative z-10">
<h1 className="text-5xl md:text-6xl font-bold tracking-tight">
Tool Registry for AI Agents
</h1>
<p className="text-xl text-foreground-secondary">
Discover, share, and integrate tools that give your agents superpowers. The registry
for AI tools.
</p>
<div className="flex gap-3 items-center max-w-2xl mx-auto">
<Input size="lg" placeholder="Search tools..." className="flex-1" />
<Button size="lg" className="px-8">
Search
</Button>
</div>
</div>
</Container>
</section>
{/* Hero Section - Brutalist Design */}
<HeroSection />
{/* Featured Tools Section */}
<section className="py-16 bg-surface blueprint-background">
{/* Featured Tools Section - Brutalist Cards */}
<section className="py-16 md:py-24 bg-background">
<Container size="xl" padding="lg">
<div className="flex items-center justify-between mb-8">
<h2 className="text-3xl font-semibold">Featured Tools</h2>
<Button variant="ghost">View all tools </Button>
<div className="flex items-center justify-between mb-12">
<h2 className="brutalist-subheading">Featured Tools</h2>
<Button variant="ghost" className="uppercase tracking-wider font-bold">
View all
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{featuredTools.map((tool) => (
<Card key={tool.id} className="hover:shadow-lg transition-shadow">
{featuredTools.map((tool, index) => (
<Card
key={tool.id}
variant="brutalist"
className={`opacity-0 animate-brutalist-entrance stagger-${index % 6}`}
>
<CardContent className="p-6 space-y-4">
<div className="flex items-start gap-4">
<Icon icon={tool.icon} size="lg" className="text-primary" />
<Icon icon={tool.icon} size="lg" className="text-brutalist-accent" />
<div className="flex-1 space-y-2">
<h3 className="text-lg font-semibold">{tool.name}</h3>
<p className="text-sm text-foreground-secondary">{tool.description}</p>
<div className="flex items-center gap-2">
<Badge variant={tool.categoryVariant} size="sm">
<h3 className="text-lg font-bold uppercase tracking-tight">{tool.name}</h3>
<p className="text-sm text-foreground-secondary leading-relaxed">
{tool.description}
</p>
<div className="flex items-center gap-3 pt-2">
<Badge
variant={tool.categoryVariant}
size="sm"
className="uppercase font-mono text-xs"
>
{tool.category}
</Badge>
<span className="text-xs text-foreground-tertiary">
<span className="text-sm font-mono font-bold text-foreground">
{tool.weeklyUsage}
</span>
</div>
@ -105,46 +100,42 @@ export default function HomePage(): React.ReactElement {
</Container>
</section>
{/* Categories Section */}
<section className="py-16">
{/* Categories Section - Brutalist Masonry Grid */}
<section className="py-16 md:py-24 bg-surface">
<Container size="xl" padding="lg">
<h2 className="text-3xl font-semibold mb-8">Browse by Category</h2>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{categories.map((category) => (
<Card
key={category.id}
className="hover:shadow-lg transition-shadow cursor-pointer"
>
<CardContent className={`p-6 ${category.colorClass}`}>
<div className="space-y-3">
<Icon icon={category.icon} size="lg" />
<h3 className="font-semibold">{category.name}</h3>
<p className="text-sm opacity-90">{category.toolCount} tools</p>
</div>
</CardContent>
</Card>
))}
</div>
<h2 className="brutalist-subheading mb-12">Browse by Category</h2>
<CategoryGrid categories={categories} />
</Container>
</section>
{/* Statistics Section */}
<section className="py-16 bg-surface grid-background">
<Container size="xl" padding="lg">
<h2 className="text-3xl font-semibold mb-8 text-center">Platform Statistics</h2>
{/* Statistics Section - Brutalist Stat Cards */}
<section className="py-16 md:py-24 bg-background relative overflow-hidden">
{/* Subtle grid background */}
<div className="absolute inset-0 opacity-[0.02] grid-background" />
<Container size="xl" padding="lg" className="relative z-10">
<h2 className="brutalist-subheading mb-12 text-center">Platform Statistics</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{statistics.map((stat) => (
<Card key={stat.label}>
<CardContent className="p-6 text-center space-y-3">
<Icon icon={stat.icon} size="lg" className="mx-auto text-primary" />
<div className="text-4xl font-bold">{stat.value}</div>
<div className="text-sm font-medium">{stat.label}</div>
{stat.subtext && (
<div className="text-xs text-foreground-tertiary">{stat.subtext}</div>
)}
</CardContent>
</Card>
))}
{statistics.map((stat, index) => {
// Extract number from value string
const numValue = Number.parseInt(stat.value.replace(/[^0-9]/g, ''), 10);
const suffix = stat.value.replace(/[0-9,]/g, '');
return (
<StatCard
key={stat.label}
value={numValue}
label={stat.label}
subtext={stat.subtext}
suffix={suffix}
variant="brutalist"
size="md"
showBar={true}
barProgress={60 + index * 10}
className={`opacity-0 animate-brutalist-entrance stagger-${index + 1}`}
/>
);
})}
</div>
</Container>
</section>

View file

@ -0,0 +1,124 @@
'use client';
import { Card, CardContent } from '@tpmjs/ui/Card/Card';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import type { IconName } from '@tpmjs/ui/Icon/Icon';
import { useScrollReveal } from '@tpmjs/ui/system/hooks/useScrollReveal';
export interface Category {
id: string;
name: string;
icon: IconName;
colorClass: string;
toolCount: number;
href: string;
}
export interface CategoryGridProps {
categories: Category[];
}
/**
* CategoryGrid Component
*
* Brutalist masonry-style grid with variable heights and random entrance animations.
* Each tile has thick borders, hover effects, and intentional chaos.
*/
export function CategoryGrid({ categories }: CategoryGridProps): React.ReactElement {
// Create random heights for masonry effect (in tailwind classes)
const heights = ['h-32', 'h-40', 'h-36', 'h-44'] as const;
return (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{categories.map((category, index) => {
const randomHeight = heights[index % heights.length] || 'h-36';
const randomDelay = Math.random() * 400; // 0-400ms random delay
return (
<CategoryTile
key={category.id}
category={category}
height={randomHeight}
delay={randomDelay}
/>
);
})}
</div>
);
}
function CategoryTile({
category,
height,
delay,
}: {
category: Category;
height: string;
delay: number;
}): React.ReactElement {
const { ref, isVisible } = useScrollReveal<HTMLAnchorElement>({
threshold: 0.1,
once: true,
delay,
});
// Random rotation for entrance animation
const randomRotation = (Math.random() - 0.5) * 20; // -10 to +10 degrees
return (
<a
href={category.href}
ref={ref}
className="group block"
style={{
opacity: isVisible ? 1 : 0,
transform: isVisible ? 'rotate(0deg) scale(1)' : `rotate(${randomRotation}deg) scale(0.8)`,
transition: 'all 600ms cubic-bezier(0.34, 1.56, 0.64, 1)',
}}
>
<Card
variant="brutalist"
padding="none"
className={`${height} relative overflow-hidden cursor-pointer
hover:border-[8px] hover:-translate-y-1 hover:rotate-[-2deg]
active:translate-y-0 active:rotate-0
transition-all duration-200`}
>
<CardContent
className={`${category.colorClass} h-full p-6 flex flex-col justify-between relative`}
>
{/* Background Pattern */}
<div
className="absolute inset-0 opacity-10"
style={{
backgroundImage: `
linear-gradient(45deg, currentColor 25%, transparent 25%),
linear-gradient(-45deg, currentColor 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, currentColor 75%),
linear-gradient(-45deg, transparent 75%, currentColor 75%)
`,
backgroundSize: '20px 20px',
backgroundPosition: '0 0, 0 10px, 10px -10px, -10px 0px',
}}
/>
{/* Content */}
<div className="relative z-10">
<Icon icon={category.icon} size="lg" className="mb-3" />
<h3 className="font-bold text-lg uppercase tracking-tight leading-tight">
{category.name}
</h3>
</div>
{/* Tool Count with Arrow */}
<div className="relative z-10 flex items-center justify-between">
<span className="font-mono text-3xl font-bold tabular-nums">{category.toolCount}</span>
<span className="text-4xl group-hover:translate-x-1 transition-transform duration-200">
</span>
</div>
</CardContent>
</Card>
</a>
);
}

View file

@ -0,0 +1,86 @@
'use client';
import { useEffect, useState } from 'react';
export interface GlitchBarsProps {
/**
* Number of bars to render
* @default 3
*/
count?: number;
/**
* Minimum interval between glitches (ms)
* @default 2000
*/
minInterval?: number;
/**
* Maximum interval between glitches (ms)
* @default 5000
*/
maxInterval?: number;
}
/**
* GlitchBars Component
*
* Renders decorative horizontal bars that randomly glitch/shift position.
* Pure visual element for brutalist aesthetic.
*/
export function GlitchBars({
count = 3,
minInterval = 2000,
maxInterval = 5000,
}: GlitchBarsProps): React.ReactElement {
const [activeBar, setActiveBar] = useState<number | null>(null);
useEffect(() => {
const triggerGlitch = (): void => {
const randomBar = Math.floor(Math.random() * count);
setActiveBar(randomBar);
// Reset after glitch animation completes
setTimeout(() => {
setActiveBar(null);
}, 500);
// Schedule next glitch
const nextInterval = minInterval + Math.random() * (maxInterval - minInterval);
setTimeout(triggerGlitch, nextInterval);
};
const initialDelay = Math.random() * 2000;
const timeout = setTimeout(triggerGlitch, initialDelay);
return () => {
clearTimeout(timeout);
};
}, [count, minInterval, maxInterval]);
return (
<div className="pointer-events-none absolute inset-0 overflow-hidden" aria-hidden="true">
{Array.from({ length: count }).map((_, index) => {
const top = 20 + index * 25; // Distribute vertically
const width = 100 + index * 50; // Varying widths
const isLeft = index % 2 === 0;
return (
<div
key={`glitch-bar-${index}`}
className={`absolute h-1 ${
index === 0 ? 'bg-brutalist-accent' : 'bg-foreground'
} opacity-${index === 0 ? '80' : '20'} transition-transform duration-500 ease-in-out ${
activeBar === index ? 'animate-glitch' : ''
}`}
style={{
top: `${top}%`,
width: `${width}px`,
[isLeft ? 'left' : 'right']: 0,
}}
/>
);
})}
</div>
);
}

View file

@ -0,0 +1,131 @@
'use client';
import { AnimatedCounter } from '@tpmjs/ui/AnimatedCounter/AnimatedCounter';
import { Button } from '@tpmjs/ui/Button/Button';
import { Input } from '@tpmjs/ui/Input/Input';
import { useParallax } from '@tpmjs/ui/system/hooks/useParallax';
import { useState } from 'react';
export function HeroSection(): React.ReactElement {
const [searchQuery, setSearchQuery] = useState('');
const parallaxStyle = useParallax({ speed: 0.5 });
return (
<section className="relative min-h-[90vh] overflow-hidden bg-background">
{/* Brutalist Grid Background - Static, Not Parallax */}
<div className="absolute inset-0 opacity-[0.03]">
<div
className="absolute inset-0"
style={{
backgroundImage: `
linear-gradient(to right, currentColor 1px, transparent 1px),
linear-gradient(to bottom, currentColor 1px, transparent 1px)
`,
backgroundSize: '40px 40px',
}}
/>
</div>
{/* Content Container with Parallax */}
<div
style={parallaxStyle}
className="relative z-10 flex min-h-[90vh] flex-col justify-center px-6 md:px-12 lg:px-24"
>
{/* Glitch Bars - Decorative Elements */}
<div
className="absolute left-0 top-32 h-1.5 w-64 bg-brutalist-accent opacity-80 animate-glitch"
style={{
animationDelay: '0.5s',
animationIterationCount: 'infinite',
animationDuration: '3s',
}}
/>
<div
className="absolute right-0 top-48 h-1 w-48 bg-foreground opacity-30 animate-glitch"
style={{
animationDelay: '1.5s',
animationIterationCount: 'infinite',
animationDuration: '4s',
}}
/>
{/* Main Heading - Brutalist Typography */}
<div className="max-w-7xl">
<h1 className="brutalist-heading mb-8 leading-[0.85] tracking-tighter">
TOOL
<br />
REGISTRY
<br />
<span className="text-brutalist-accent">FOR AI AGENTS</span>
</h1>
{/* Live Metrics Strip */}
<div className="mb-12 flex flex-wrap items-center gap-3 border-l-[6px] border-brutalist-accent pl-6 font-mono text-sm md:text-base font-bold uppercase tracking-wider">
<div className="flex items-center gap-2">
<AnimatedCounter value={2847} separator="," size="md" />
<span className="text-foreground-secondary">TOOLS</span>
</div>
<span className="text-foreground-tertiary">/</span>
<div className="flex items-center gap-2">
<AnimatedCounter value={12000000} suffix="+" size="md" />
<span className="text-foreground-secondary">INVOCATIONS</span>
</div>
<span className="text-foreground-tertiary">/</span>
<div className="flex items-center gap-2">
<AnimatedCounter value={47} suffix="ms" size="md" />
<span className="text-foreground-secondary">LATENCY</span>
</div>
</div>
{/* Subheading */}
<p className="mb-12 max-w-3xl text-xl md:text-2xl font-medium leading-relaxed text-foreground-secondary tracking-tight">
Discover, share, and integrate tools that give your agents superpowers.
<br />
The registry for AI tools.
</p>
{/* Brutalist Search Interface */}
<div className="max-w-3xl">
<div className="relative">
{/* Command Line Prompt */}
<div className="absolute left-0 top-0 bottom-0 flex items-center pl-6 font-mono text-brutalist-accent text-lg font-bold pointer-events-none">
$
</div>
<Input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="search tools..."
className="brutalist-border h-16 md:h-20 pl-14 pr-4 text-lg md:text-xl font-mono placeholder:text-foreground-tertiary placeholder:uppercase focus:ring-4 focus:ring-brutalist-accent focus:ring-offset-0 bg-background"
style={{ borderRadius: 0 }}
/>
{/* Search Button */}
<Button
size="lg"
className="brutalist-border-thick absolute right-2 top-1/2 -translate-y-1/2 h-12 md:h-14 px-8 bg-brutalist-accent text-background hover:bg-brutalist-accent-hover font-bold uppercase tracking-wider"
style={{ borderRadius: 0, borderColor: 'hsl(var(--foreground))' }}
>
Search
</Button>
</div>
{/* Helper Text */}
<div className="mt-4 flex items-center gap-4 font-mono text-xs text-foreground-tertiary uppercase tracking-wider">
<span>Try: &ldquo;web-scraper&rdquo;, &ldquo;github-manager&rdquo;, &ldquo;sql-query&rdquo;</span>
</div>
</div>
</div>
{/* Scroll Indicator */}
<div className="absolute bottom-12 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2 animate-pulse">
<span className="font-mono text-xs uppercase tracking-widest text-foreground-tertiary">
Scroll
</span>
<div className="h-8 w-[2px] bg-brutalist-accent" />
</div>
</div>
</section>
);
}