From 36742949bdc379f047993637d3754a566355da77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Kr=C3=BCger?= Date: Fri, 7 Nov 2025 12:39:14 +0100 Subject: [PATCH] polish: Priority 2 improvements - smooth scroll, badges, back to top MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🎯 Smooth Scroll Behavior: - Added smooth scroll CSS for seamless navigation - Scroll indicator now links to #tools section - Added hover states for scroll indicator - Accessibility: respects prefers-reduced-motion 🏷️ Tool Badges: - Added feature badges to each tool card - Vert: Privacy, Open Source, Free - Paint: Browser-Based, Free - Pastel: Open Source, WCAG, Free - Subtle glassmorphic badge design ⬆️ Back to Top Button: - Animated button appears after scrolling 300px - Smooth scroll to top on click - Hover tooltip with "Back to top" label - Scale animations on hover/tap - Progress bar at top showing scroll position - Gradient progress indicator ♿ Accessibility: - Added prefers-reduced-motion support - Proper ARIA labels - Keyboard accessible - Smooth animations respect user preferences 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/globals.css | 18 ++++++++++ app/page.tsx | 2 ++ components/BackToTop.tsx | 77 ++++++++++++++++++++++++++++++++++++++++ components/Hero.tsx | 11 +++--- components/ToolCard.tsx | 17 ++++++++- components/ToolsGrid.tsx | 6 +++- 6 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 components/BackToTop.tsx diff --git a/app/globals.css b/app/globals.css index a6269c1..fc611aa 100644 --- a/app/globals.css +++ b/app/globals.css @@ -42,6 +42,10 @@ } } +html { + scroll-behavior: smooth; +} + body { color: var(--color-foreground); background: var(--color-background); @@ -50,6 +54,20 @@ body { -moz-osx-font-smoothing: grayscale; } +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } + + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} + @utility text-balance { text-wrap: balance; } diff --git a/app/page.tsx b/app/page.tsx index c393279..69b9927 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -3,11 +3,13 @@ import Hero from '@/components/Hero'; import Stats from '@/components/Stats'; import ToolsGrid from '@/components/ToolsGrid'; import Footer from '@/components/Footer'; +import BackToTop from '@/components/BackToTop'; export default function Home() { return (
+ diff --git a/components/BackToTop.tsx b/components/BackToTop.tsx new file mode 100644 index 0000000..4112bae --- /dev/null +++ b/components/BackToTop.tsx @@ -0,0 +1,77 @@ +'use client'; + +import { motion, useScroll, useSpring } from 'framer-motion'; +import { useState, useEffect } from 'react'; + +export default function BackToTop() { + const [isVisible, setIsVisible] = useState(false); + const { scrollYProgress } = useScroll(); + const scaleX = useSpring(scrollYProgress, { + stiffness: 100, + damping: 30, + restDelta: 0.001, + }); + + useEffect(() => { + const toggleVisibility = () => { + if (window.pageYOffset > 300) { + setIsVisible(true); + } else { + setIsVisible(false); + } + }; + + window.addEventListener('scroll', toggleVisibility); + return () => window.removeEventListener('scroll', toggleVisibility); + }, []); + + const scrollToTop = () => { + window.scrollTo({ + top: 0, + behavior: 'smooth', + }); + }; + + return ( + <> + {/* Progress bar */} + + + {/* Back to top button */} + {isVisible && ( + + + + + + {/* Tooltip */} + + Back to top + + + )} + + ); +} diff --git a/components/Hero.tsx b/components/Hero.tsx index c1991af..ce671da 100644 --- a/components/Hero.tsx +++ b/components/Hero.tsx @@ -49,21 +49,22 @@ export default function Hero() { {/* Scroll indicator */} - - Explore Tools + Explore Tools
- +
); diff --git a/components/ToolCard.tsx b/components/ToolCard.tsx index 2764214..4759688 100644 --- a/components/ToolCard.tsx +++ b/components/ToolCard.tsx @@ -10,9 +10,10 @@ interface ToolCardProps { url: string; gradient: string; index: number; + badges?: string[]; } -export default function ToolCard({ title, description, icon, url, gradient, index }: ToolCardProps) { +export default function ToolCard({ title, description, icon, url, gradient, index, badges }: ToolCardProps) { return ( + {/* Badges */} + {badges && badges.length > 0 && ( +
+ {badges.map((badge) => ( + + {badge} + + ))} +
+ )} + {/* Description */}

{description} diff --git a/components/ToolsGrid.tsx b/components/ToolsGrid.tsx index 2d6ebfc..c4fb232 100644 --- a/components/ToolsGrid.tsx +++ b/components/ToolsGrid.tsx @@ -9,6 +9,7 @@ const tools = [ description: 'Privacy-focused file converter that processes images, audio, and documents locally on your device. No file size limits, completely open source.', url: 'https://vert.kit.pivoine.art', gradient: 'gradient-green-teal', + badges: ['Privacy', 'Open Source', 'Free'], icon: ( @@ -20,6 +21,7 @@ const tools = [ description: 'An advanced image editor running in your browser. Edit photos, create graphics, and more.', url: 'https://paint.kit.pivoine.art', gradient: 'gradient-orange-pink', + badges: ['Browser-Based', 'Free'], icon: ( @@ -31,6 +33,7 @@ const tools = [ description: 'Modern color manipulation toolkit with palette generation, accessibility testing, and format conversion. Supports hex, RGB, HSL, Lab, and more.', url: 'https://pastel.kit.pivoine.art', gradient: 'gradient-indigo-purple', + badges: ['Open Source', 'WCAG', 'Free'], icon: ( @@ -44,7 +47,7 @@ const tools = [ export default function ToolsGrid() { return ( -
+

{/* Section heading */} ))}