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 Link from 'next/link';
import { motion } from 'framer-motion';
import AnimatedBackground from '@/components/AnimatedBackground'; import AnimatedBackground from '@/components/AnimatedBackground';
import Logo from '@/components/Logo'; import Logo from '@/components/Logo';
import { Button } from '@/components/ui/button'; import { ArrowLeft } from 'lucide-react';
import { Home } from 'lucide-react';
export default function NotFound() { export default function NotFound() {
return ( return (
<main className="relative min-h-screen dark text-foreground flex flex-col"> <main className="relative min-h-screen dark text-foreground flex flex-col">
<AnimatedBackground /> <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">
{/* 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>
{/* 404 heading */} <div className="flex-1 flex flex-col items-center justify-center px-6 py-20 relative z-10 text-center">
<motion.h1
className="text-7xl md:text-9xl font-bold mb-6 text-primary" {/* Logo */}
initial={{ opacity: 0, y: 20 }} <div style={{ animation: 'fadeIn 0.5s ease-out both' }}>
animate={{ opacity: 1, y: 0 }} <Logo size={52} />
transition={{ duration: 0.8, delay: 0.2 }} </div>
>
{/* 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 404
</motion.h1> </span>
</div>
{/* Subtitle */} {/* Message */}
<motion.p <div
className="text-xl md:text-3xl font-medium mb-4" className="mt-4 space-y-1"
initial={{ opacity: 0, y: 20 }} style={{ animation: 'slideUp 0.5s ease-out 0.3s both' }}
animate={{ opacity: 1, y: 0 }} >
transition={{ duration: 0.8, delay: 0.4 }} <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">
Page Not Found The tool or page you&apos;re looking for doesn&apos;t exist or has been moved.
</motion.p> </p>
</div>
{/* Description */} {/* CTA */}
<motion.p <div
className="text-base md:text-lg text-muted-foreground/80 mb-12 max-w-md mx-auto" className="mt-8"
initial={{ opacity: 0, y: 20 }} style={{ animation: 'slideUp 0.5s ease-out 0.45s both' }}
animate={{ opacity: 1, y: 0 }} >
transition={{ duration: 0.8, delay: 0.6 }} <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"
> >
The tool or page you are looking for doesn&apos;t exist or has been moved. <ArrowLeft className="w-3.5 h-3.5 text-primary" />
</motion.p> Back to Home
</Link>
{/* CTA Button */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.8 }}
>
<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" />
Back to Home
</Button>
</Link>
</motion.div>
</div> </div>
</div> </div>
</main> </main>

View File

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

View File

@@ -1,76 +1,44 @@
'use client'; 'use client';
import { motion, useScroll, useSpring } from 'framer-motion'; import { useState, useEffect, useRef } from 'react';
import { useState, useEffect } from 'react'; import { ChevronUp } from 'lucide-react';
export default function BackToTop() { export default function BackToTop() {
const [isVisible, setIsVisible] = useState(false); const [isVisible, setIsVisible] = useState(false);
const { scrollYProgress } = useScroll(); const barRef = useRef<HTMLDivElement>(null);
const scaleX = useSpring(scrollYProgress, {
stiffness: 100,
damping: 30,
restDelta: 0.001,
});
useEffect(() => { useEffect(() => {
const toggleVisibility = () => { const onScroll = () => {
if (window.pageYOffset > 300) { setIsVisible(window.scrollY > 300);
setIsVisible(true); if (barRef.current) {
} else { const el = document.documentElement;
setIsVisible(false); const scrolled = el.scrollTop / (el.scrollHeight - el.clientHeight);
barRef.current.style.transform = `scaleX(${scrolled})`;
} }
}; };
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('scroll', toggleVisibility); return () => window.removeEventListener('scroll', onScroll);
return () => window.removeEventListener('scroll', toggleVisibility);
}, []); }, []);
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth',
});
};
return ( return (
<> <>
{/* Progress bar */} {/* Scroll progress bar */}
<motion.div <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" ref={barRef}
style={{ scaleX }} 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 */} {/* Back to top button */}
{isVisible && ( {isVisible && (
<motion.button <button
onClick={scrollToTop} onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
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" 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"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
aria-label="Back to top" aria-label="Back to top"
style={{ animation: 'fadeIn 0.2s ease-out both' }}
> >
<svg <ChevronUp className="w-3.5 h-3.5" />
className="w-6 h-6" </button>
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>
)} )}
</> </>
); );

View File

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

View File

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

View File

@@ -1,68 +1,35 @@
'use client';
import { tools } from '@/lib/tools'; import { tools } from '@/lib/tools';
import { motion } from 'framer-motion'; import { Box, Code2, Shield } from 'lucide-react';
const stats = [ const stats = [
{ { value: tools.length, label: 'Tools', icon: Box },
number: tools.length, { value: '100%', label: 'Open Source', icon: Code2 },
label: 'Tools', { value: '∞', label: 'Privacy First', icon: Shield },
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>
),
},
]; ];
export default function Stats() { export default function Stats() {
return ( return (
<section className="relative py-16 px-4"> <section className="relative py-8 px-6">
<div className="max-w-6xl mx-auto"> <div className="max-w-xl mx-auto">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8"> <div className="grid grid-cols-3 gap-3">
{stats.map((stat, index) => ( {stats.map((stat, i) => {
<motion.div const Icon = stat.icon;
key={stat.label} return (
className="glass rounded-2xl p-8 text-center" <div
initial={{ opacity: 0, y: 20 }} key={stat.label}
whileInView={{ opacity: 1, y: 0 }} className="glass rounded-xl p-5 flex flex-col items-center text-center"
viewport={{ once: true }} style={{ animation: `slideUp 0.5s ease-out ${0.1 + i * 0.1}s both` }}
transition={{ duration: 0.5, delay: index * 0.1 }}
whileHover={{ y: -5 }}
>
<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} <div className="w-7 h-7 rounded-md bg-primary/10 flex items-center justify-center mb-3">
</motion.div> <Icon className="w-3.5 h-3.5 text-primary" />
<div className="text-4xl font-bold mb-2 bg-clip-text text-transparent bg-gradient-to-r from-purple-400 to-cyan-400"> </div>
{stat.number} <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> </div>
<div className="text-muted-foreground text-base font-medium"> );
{stat.label} })}
</div>
</motion.div>
))}
</div> </div>
</div> </div>
</section> </section>

View File

@@ -1,91 +1,56 @@
'use client';
import { motion } from 'framer-motion';
import { ReactNode } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { ArrowRight } from 'lucide-react';
const MotionLink = motion.create(Link); import { ElementType } from 'react';
interface ToolCardProps { interface ToolCardProps {
title: string; title: string;
description: string; description: string;
icon: ReactNode; icon: ElementType;
url: string; url: string;
index: number; index: number;
badges?: string[]; 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 ( return (
<MotionLink <Link
href={url} href={url}
className="group relative block h-full" className="group glass rounded-xl p-4 flex flex-col h-full transition-all duration-200 hover:border-primary/30 hover:bg-primary/3"
initial={{ opacity: 0, y: 50 }} style={{ animation: `slideUp 0.5s ease-out ${0.05 * index}s both` }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.1 }}
whileHover={{ y: -10 }}
> >
<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"> {/* Icon */}
{/* Subtle hover overlay */} <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">
<div className="absolute inset-0 opacity-0 group-hover:opacity-10 transition-opacity duration-300 bg-primary" /> <Icon className="w-4 h-4 text-primary" />
</div>
{/* Icon */} {/* Title */}
<motion.div <h3 className="text-sm font-semibold text-foreground/80 group-hover:text-foreground transition-colors mb-1.5">
className="mb-6 flex justify-center" {title}
whileHover={{ scale: 1.1, rotate: 5 }} </h3>
transition={{ type: 'spring', stiffness: 300 }}
>
<div className="p-4 rounded-xl bg-primary/10 text-primary shadow-lg shadow-black/5">
{icon}
</div>
</motion.div>
{/* Title */} {/* Description */}
<h3 className="text-2xl font-bold mb-3 text-foreground transition-all duration-300 group-hover:text-primary"> <p className="text-[11px] text-muted-foreground/50 leading-relaxed flex-1 mb-3">
{title} {description}
</h3> </p>
{/* Badges */} {/* Footer: badges + arrow */}
{badges && badges.length > 0 && ( <div className="flex items-end justify-between gap-2">
<div className="flex flex-wrap gap-2 mb-3"> {badges && badges.length > 0 ? (
{badges.map((badge) => ( <div className="flex flex-wrap gap-1">
{badges.slice(0, 2).map((badge) => (
<span <span
key={badge} 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} {badge}
</span> </span>
))} ))}
</div> </div>
) : (
<span />
)} )}
<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" />
{/* 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>
</div> </div>
</MotionLink> </Link>
); );
} }

View File

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