refactor: align landing page and 404 with Calculate blueprint

- Hero: remove framer-motion, CSS stagger animations, glass pill CTA button, refined typography and scroll indicator
- Stats: remove framer-motion, Lucide icons, tighter glass cards with mono labels
- ToolsGrid: remove framer-motion, editorial section heading, 4-col xl grid
- ToolCard: replace framer-motion motion.Link with plain Link + CSS hover, compact layout (icon→title→desc→badges+arrow), ElementType icon prop
- Footer: remove framer-motion, matches sidebar footer style
- BackToTop: remove framer-motion, JS scroll progress bar (1px primary line), compact glass button
- not-found: remove framer-motion and shadcn Button, glass pill CTA, 120px mono 404, CSS stagger
- page.tsx: remove unnecessary 'use client' directive

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 09:07:18 +01:00
parent 413c677173
commit 9126589de3
8 changed files with 204 additions and 375 deletions

View File

@@ -1,73 +1,53 @@
'use client';
import Link from 'next/link';
import { motion } from 'framer-motion';
import AnimatedBackground from '@/components/AnimatedBackground';
import Logo from '@/components/Logo';
import { Button } from '@/components/ui/button';
import { Home } from 'lucide-react';
import { ArrowLeft } from 'lucide-react';
export default function NotFound() {
return (
<main className="relative min-h-screen dark text-foreground flex flex-col">
<AnimatedBackground />
<div className="flex-1 flex flex-col items-center justify-center px-4 py-20 relative z-10">
<div className="max-w-6xl mx-auto text-center">
<div className="flex-1 flex flex-col items-center justify-center px-6 py-20 relative z-10 text-center">
{/* Logo */}
<motion.div
className="mb-8 flex justify-center"
initial={{ opacity: 0, y: -50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
>
<Logo size={100} />
</motion.div>
<div style={{ animation: 'fadeIn 0.5s ease-out both' }}>
<Logo size={52} />
</div>
{/* 404 heading */}
<motion.h1
className="text-7xl md:text-9xl font-bold mb-6 text-primary"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.2 }}
{/* 404 */}
<div
className="mt-8"
style={{ animation: 'slideUp 0.5s ease-out 0.15s both' }}
>
<span className="text-[80px] md:text-[120px] font-bold font-mono text-primary leading-none tabular-nums block">
404
</motion.h1>
</span>
</div>
{/* Subtitle */}
<motion.p
className="text-xl md:text-3xl font-medium mb-4"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.4 }}
{/* Message */}
<div
className="mt-4 space-y-1"
style={{ animation: 'slideUp 0.5s ease-out 0.3s both' }}
>
Page Not Found
</motion.p>
<p className="text-sm font-medium text-foreground/70">Page not found</p>
<p className="text-[11px] text-muted-foreground/50 font-mono max-w-xs mx-auto leading-relaxed">
The tool or page you&apos;re looking for doesn&apos;t exist or has been moved.
</p>
</div>
{/* Description */}
<motion.p
className="text-base md:text-lg text-muted-foreground/80 mb-12 max-w-md mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.6 }}
{/* CTA */}
<div
className="mt-8"
style={{ animation: 'slideUp 0.5s ease-out 0.45s both' }}
>
The tool or page you are looking for doesn&apos;t exist or has been moved.
</motion.p>
{/* CTA Button */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.8 }}
<Link
href="/"
className="inline-flex items-center gap-2 px-5 py-2.5 glass rounded-lg border border-primary/30 hover:border-primary/60 hover:bg-primary/10 text-sm font-medium text-foreground/70 hover:text-foreground transition-all duration-200"
>
<Link href="/">
<Button size="lg" className="rounded-full px-8 h-14 text-lg font-semibold bg-gradient-to-r from-purple-500 to-cyan-500 hover:from-purple-600 hover:to-cyan-600 border-none transition-all duration-300">
<Home className="mr-2 h-5 w-5" />
<ArrowLeft className="w-3.5 h-3.5 text-primary" />
Back to Home
</Button>
</Link>
</motion.div>
</div>
</div>
</main>

View File

@@ -1,5 +1,3 @@
'use client';
import AnimatedBackground from '@/components/AnimatedBackground';
import Hero from '@/components/Hero';
import Stats from '@/components/Stats';

View File

@@ -1,76 +1,44 @@
'use client';
import { motion, useScroll, useSpring } from 'framer-motion';
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { ChevronUp } from 'lucide-react';
export default function BackToTop() {
const [isVisible, setIsVisible] = useState(false);
const { scrollYProgress } = useScroll();
const scaleX = useSpring(scrollYProgress, {
stiffness: 100,
damping: 30,
restDelta: 0.001,
});
const barRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const toggleVisibility = () => {
if (window.pageYOffset > 300) {
setIsVisible(true);
} else {
setIsVisible(false);
const onScroll = () => {
setIsVisible(window.scrollY > 300);
if (barRef.current) {
const el = document.documentElement;
const scrolled = el.scrollTop / (el.scrollHeight - el.clientHeight);
barRef.current.style.transform = `scaleX(${scrolled})`;
}
};
window.addEventListener('scroll', toggleVisibility);
return () => window.removeEventListener('scroll', toggleVisibility);
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth',
});
};
return (
<>
{/* Progress bar */}
<motion.div
className="fixed top-0 left-0 right-0 h-1 bg-gradient-to-r from-purple-500 to-cyan-500 transform origin-left z-50"
style={{ scaleX }}
{/* Scroll progress bar */}
<div
ref={barRef}
className="fixed top-0 left-0 right-0 h-px bg-primary z-50 origin-left"
style={{ transform: 'scaleX(0)', transition: 'transform 0.1s linear' }}
/>
{/* Back to top button */}
{isVisible && (
<motion.button
onClick={scrollToTop}
className="fixed bottom-8 right-8 p-4 rounded-full glass hover:bg-accent/50 text-purple-400 hover:text-purple-300 transition-colors shadow-lg z-40 group"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
<button
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
className="fixed bottom-6 right-6 w-8 h-8 glass rounded-lg flex items-center justify-center text-muted-foreground/40 hover:text-primary hover:border-primary/40 transition-all duration-200 z-40"
aria-label="Back to top"
style={{ animation: 'fadeIn 0.2s ease-out both' }}
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 10l7-7m0 0l7 7m-7-7v18"
/>
</svg>
{/* Tooltip */}
<span className="absolute bottom-full right-0 mb-2 px-3 py-1 text-xs text-white bg-gray-900 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap">
Back to top
</span>
</motion.button>
<ChevronUp className="w-3.5 h-3.5" />
</button>
)}
</>
);

View File

@@ -1,47 +1,34 @@
'use client';
import { motion } from 'framer-motion';
import { GitFork, Heart } from 'lucide-react';
export default function Footer() {
const currentYear = new Date().getFullYear();
return (
<footer className="relative py-12 px-4">
<div className="max-w-6xl mx-auto border-t border-border pt-12">
<motion.div
className="flex flex-col md:flex-row items-center justify-between gap-6"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
{/* Copyright */}
<p className="text-sm text-muted-foreground flex items-center gap-1">
© {currentYear} Kit.
<Heart className="h-4 w-4 text-primary shrink-0 animate-pulse" fill="currentColor" />
<footer className="relative py-10 px-6">
<div className="max-w-5xl mx-auto border-t border-border/20 pt-8">
<div className="flex items-center justify-between">
<p className="flex items-center gap-1 text-[9px] text-muted-foreground/40 font-mono">
© {currentYear} Kit
<Heart className="w-2 h-2 text-primary/70 shrink-0 animate-pulse" fill="currentColor" />
<a
href="https://pivoine.art"
target="_blank"
rel="noopener noreferrer"
title="Pivoine.Art"
className="font-medium underline underline-offset-4 decoration-primary/0 hover:decoration-primary transition-all duration-300"
className="hover:text-foreground/70 transition-colors"
>
Valknar
</a>
</p>
{/* Source link */}
<a
href="https://dev.pivoine.art/valknar/kit-ui"
target="_blank"
rel="noopener noreferrer"
title="View source"
className="text-muted-foreground hover:text-primary transition-colors duration-300"
className="text-muted-foreground/30 hover:text-primary transition-colors"
>
<GitFork className="h-5 w-5" />
<GitFork className="w-3.5 h-3.5" />
</a>
</motion.div>
</div>
</div>
</footer>
);

View File

@@ -1,108 +1,80 @@
'use client';
import { motion } from 'framer-motion';
import { Toolbox } from 'lucide-react';
import Logo from './Logo';
export default function Hero() {
/**
* Smoothly scrolls the window to the tools section without modifying the URL hash.
*/
const scrollToTools = () => {
const toolsSection = document.getElementById('tools');
if (toolsSection) {
toolsSection.scrollIntoView({ behavior: 'smooth' });
}
document.getElementById('tools')?.scrollIntoView({ behavior: 'smooth' });
};
return (
<section className="relative min-h-screen flex flex-col items-center justify-center px-4 py-20">
<div className="max-w-6xl mx-auto text-center">
{/* Logo */}
<motion.div
className="mb-8 flex justify-center"
initial={{ opacity: 0, y: -50 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
>
<Logo size={130} />
</motion.div>
<section className="relative min-h-screen flex flex-col items-center justify-center px-6 py-24">
<div className="flex flex-col items-center text-center max-w-2xl mx-auto">
{/* Main heading */}
<motion.h1
className="text-6xl md:text-8xl font-bold mb-6 text-primary"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.2 }}
{/* Logo */}
<div style={{ animation: 'fadeIn 0.6s ease-out both' }}>
<Logo size={64} />
</div>
{/* Badge */}
<div
className="mt-8 flex items-center gap-2 px-3 py-1 glass rounded-full"
style={{ animation: 'slideUp 0.5s ease-out 0.2s both' }}
>
<span className="w-1.5 h-1.5 rounded-full bg-primary animate-pulse" />
<span className="text-[10px] font-mono text-muted-foreground/60 tracking-widest uppercase">
Browser-first toolkit
</span>
</div>
{/* Title */}
<h1
className="mt-5 text-6xl md:text-8xl font-bold text-foreground tracking-tight leading-none"
style={{ animation: 'slideUp 0.5s ease-out 0.3s both' }}
>
Kit
</motion.h1>
{/* Subtitle */}
<motion.p
className="text-xl md:text-2xl text-muted-foreground mb-4 max-w-2xl mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.4 }}
>
Your Creative Toolkit
</motion.p>
</h1>
{/* Description */}
<motion.p
className="text-base md:text-lg text-muted-foreground/80 mb-12 max-w-xl mx-auto"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.6 }}
<p
className="mt-5 text-sm text-muted-foreground/60 max-w-sm leading-relaxed"
style={{ animation: 'slideUp 0.5s ease-out 0.4s both' }}
>
A curated collection of creative and utility tools for developers and creators.
Simple, powerful, and always at your fingertips.
</motion.p>
A curated collection of browser-based tools for developers and creators.
Everything runs locally no uploads, no tracking.
</p>
{/* CTA Buttons */}
<motion.div
className="flex flex-col sm:flex-row gap-4 justify-center items-center mb-16"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.8 }}
{/* CTA */}
<div
className="mt-8"
style={{ animation: 'slideUp 0.5s ease-out 0.5s both' }}
>
<motion.button
<button
onClick={scrollToTools}
className="group relative px-8 py-4 rounded-full bg-gradient-to-r from-purple-500 to-cyan-500 text-white font-semibold shadow-lg overflow-hidden"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="flex items-center gap-2 px-5 py-2.5 glass rounded-lg border border-primary/30 hover:border-primary/60 hover:bg-primary/10 text-sm font-medium text-foreground/70 hover:text-foreground transition-all duration-200"
>
<span className="relative z-10 inline-flex items-center gap-2">
<Toolbox className="h-5 w-5" />
<Toolbox className="w-3.5 h-3.5 text-primary" />
Explore Tools
</span>
<motion.div
className="absolute inset-0 bg-gradient-to-r from-purple-600 to-cyan-600"
initial={{ x: '100%' }}
whileHover={{ x: 0 }}
transition={{ duration: 0.3 }}
/>
</motion.button>
</motion.div>
</button>
</div>
{/* Scroll indicator */}
<motion.button
<button
onClick={scrollToTools}
className="mx-auto flex flex-col items-center gap-2 cursor-pointer group"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8, delay: 1 }}
className="mt-20 flex flex-col items-center gap-2 group"
style={{ animation: 'fadeIn 0.5s ease-out 0.9s both' }}
>
<span className="text-base text-gray-500 group-hover:text-gray-400 transition-colors">Scroll to explore</span>
<motion.div
className="w-6 h-10 border-2 border-gray-600 group-hover:border-purple-400 rounded-full p-1 transition-colors"
animate={{ y: [0, 10, 0] }}
transition={{ duration: 1.5, repeat: Infinity }}
>
<div className="w-1 h-2 bg-gradient-to-b from-purple-400 to-cyan-400 rounded-full mx-auto" />
</motion.div>
</motion.button>
<span className="text-[9px] font-mono text-muted-foreground/25 uppercase tracking-widest group-hover:text-muted-foreground/50 transition-colors">
Scroll
</span>
<div className="w-4 h-7 border border-muted-foreground/15 rounded-full flex items-start justify-center pt-1.5 group-hover:border-primary/30 transition-colors">
<div
className="w-0.5 h-1.5 bg-primary/50 rounded-full"
style={{ animation: 'float 1.5s ease-in-out infinite' }}
/>
</div>
</button>
</div>
</section>
);

View File

@@ -1,68 +1,35 @@
'use client';
import { tools } from '@/lib/tools';
import { motion } from 'framer-motion';
import { Box, Code2, Shield } from 'lucide-react';
const stats = [
{
number: tools.length,
label: 'Tools',
icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
),
},
{
number: '100%',
label: 'Open Source',
icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
</svg>
),
},
{
number: '∞',
label: 'Privacy First',
icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
),
},
{ value: tools.length, label: 'Tools', icon: Box },
{ value: '100%', label: 'Open Source', icon: Code2 },
{ value: '∞', label: 'Privacy First', icon: Shield },
];
export default function Stats() {
return (
<section className="relative py-16 px-4">
<div className="max-w-6xl mx-auto">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{stats.map((stat, index) => (
<motion.div
<section className="relative py-8 px-6">
<div className="max-w-xl mx-auto">
<div className="grid grid-cols-3 gap-3">
{stats.map((stat, i) => {
const Icon = stat.icon;
return (
<div
key={stat.label}
className="glass rounded-2xl p-8 text-center"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
whileHover={{ y: -5 }}
className="glass rounded-xl p-5 flex flex-col items-center text-center"
style={{ animation: `slideUp 0.5s ease-out ${0.1 + i * 0.1}s both` }}
>
<motion.div
className="inline-flex items-center justify-center w-12 h-12 mb-4 rounded-xl bg-primary/10 text-primary"
whileHover={{ scale: 1.1, rotate: 5 }}
transition={{ type: 'spring', stiffness: 300 }}
>
{stat.icon}
</motion.div>
<div className="text-4xl font-bold mb-2 bg-clip-text text-transparent bg-gradient-to-r from-purple-400 to-cyan-400">
{stat.number}
<div className="w-7 h-7 rounded-md bg-primary/10 flex items-center justify-center mb-3">
<Icon className="w-3.5 h-3.5 text-primary" />
</div>
<div className="text-muted-foreground text-base font-medium">
<span className="text-2xl font-bold tabular-nums text-foreground">{stat.value}</span>
<span className="text-[10px] font-mono text-muted-foreground/40 uppercase tracking-widest mt-1">
{stat.label}
</span>
</div>
</motion.div>
))}
);
})}
</div>
</div>
</section>

View File

@@ -1,91 +1,56 @@
'use client';
import { motion } from 'framer-motion';
import { ReactNode } from 'react';
import Link from 'next/link';
const MotionLink = motion.create(Link);
import { ArrowRight } from 'lucide-react';
import { ElementType } from 'react';
interface ToolCardProps {
title: string;
description: string;
icon: ReactNode;
icon: ElementType;
url: string;
index: number;
badges?: string[];
}
export default function ToolCard({ title, description, icon, url, index, badges }: ToolCardProps) {
export default function ToolCard({ title, description, icon: Icon, url, index, badges }: ToolCardProps) {
return (
<MotionLink
<Link
href={url}
className="group relative block h-full"
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
whileHover={{ y: -10 }}
className="group glass rounded-xl p-4 flex flex-col h-full transition-all duration-200 hover:border-primary/30 hover:bg-primary/3"
style={{ animation: `slideUp 0.5s ease-out ${0.05 * index}s both` }}
>
<div className="glass relative overflow-hidden rounded-2xl p-8 h-full transition-all duration-300 group-hover:shadow-2xl group-hover:bg-card/80">
{/* Subtle hover overlay */}
<div className="absolute inset-0 opacity-0 group-hover:opacity-10 transition-opacity duration-300 bg-primary" />
{/* Icon */}
<motion.div
className="mb-6 flex justify-center"
whileHover={{ scale: 1.1, rotate: 5 }}
transition={{ type: 'spring', stiffness: 300 }}
>
<div className="p-4 rounded-xl bg-primary/10 text-primary shadow-lg shadow-black/5">
{icon}
<div className="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center mb-3 shrink-0 group-hover:bg-primary/15 transition-colors">
<Icon className="w-4 h-4 text-primary" />
</div>
</motion.div>
{/* Title */}
<h3 className="text-2xl font-bold mb-3 text-foreground transition-all duration-300 group-hover:text-primary">
<h3 className="text-sm font-semibold text-foreground/80 group-hover:text-foreground transition-colors mb-1.5">
{title}
</h3>
{/* Badges */}
{badges && badges.length > 0 && (
<div className="flex flex-wrap gap-2 mb-3">
{badges.map((badge) => (
{/* Description */}
<p className="text-[11px] text-muted-foreground/50 leading-relaxed flex-1 mb-3">
{description}
</p>
{/* Footer: badges + arrow */}
<div className="flex items-end justify-between gap-2">
{badges && badges.length > 0 ? (
<div className="flex flex-wrap gap-1">
{badges.slice(0, 2).map((badge) => (
<span
key={badge}
className="text-xs px-2 py-1 rounded-full bg-primary/5 border border-primary/10 text-muted-foreground font-medium"
className="text-[9px] font-mono px-1.5 py-0.5 rounded border border-border/30 text-muted-foreground/35"
>
{badge}
</span>
))}
</div>
) : (
<span />
)}
{/* Description */}
<p className="text-muted-foreground group-hover:text-foreground/80 transition-colors duration-300">
{description}
</p>
{/* Arrow icon */}
<motion.div
className="absolute bottom-8 right-8 text-muted-foreground group-hover:text-primary transition-colors duration-300"
initial={{ x: 0 }}
whileHover={{ x: 5 }}
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 7l5 5m0 0l-5 5m5-5H6"
/>
</svg>
</motion.div>
<ArrowRight className="w-3 h-3 text-muted-foreground/25 group-hover:text-primary group-hover:translate-x-0.5 transition-all duration-200 shrink-0" />
</div>
</MotionLink>
</Link>
);
}

View File

@@ -1,45 +1,37 @@
'use client';
import { motion } from 'framer-motion';
import ToolCard from './ToolCard';
import { tools } from '@/lib/tools';
export default function ToolsGrid() {
return (
<section id="tools" className="relative py-20 px-4">
<div className="max-w-6xl mx-auto">
<section id="tools" className="relative py-16 px-6">
<div className="max-w-5xl mx-auto">
{/* Section heading */}
<motion.div
className="text-center mb-16"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
<div
className="mb-8"
style={{ animation: 'fadeIn 0.5s ease-out both' }}
>
<h2 className="text-4xl md:text-5xl font-bold mb-4 bg-clip-text text-transparent bg-gradient-to-r from-purple-400 to-cyan-400">
<span className="text-[10px] font-semibold text-muted-foreground uppercase tracking-widest block mb-1">
Available Tools
</h2>
<p className="text-muted-foreground text-lg max-w-2xl mx-auto">
Explore our collection of carefully crafted tools designed to boost your productivity and creativity
</span>
<p className="text-xs text-muted-foreground/40">
Carefully crafted tools for your workflow
</p>
</motion.div>
</div>
{/* Tools grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{tools.map((tool, index) => {
const Icon = tool.icon;
return (
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-3">
{tools.map((tool, index) => (
<ToolCard
key={tool.href}
title={tool.title}
title={tool.shortTitle}
description={tool.summary}
icon={<Icon className="w-12 h-12" />}
icon={tool.icon}
url={tool.href}
badges={tool.badges}
index={index}
/>
);
})}
))}
</div>
</div>
</section>