-
Notifications
You must be signed in to change notification settings - Fork 399
Fix/UI improvements #2653
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Fix/UI improvements #2653
Changes from all commits
709c020
f2af035
0850856
2c799c0
b0bf960
671cc4e
c430bf2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,121 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { motion } from 'framer-motion'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const AnimatedButton = ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| children, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variant = 'primary', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size = 'md', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| disabled = false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| icon, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className = '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...props | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const baseClasses = 'relative overflow-hidden font-semibold rounded-full transition-all duration-300 flex items-center justify-center gap-2'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const variants = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| primary: 'bg-gradient-to-r from-green-600 to-green-700 text-white hover:from-green-700 hover:to-green-800 shadow-lg hover:shadow-xl', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| secondary: 'border-2 border-green-600 text-green-700 hover:bg-green-50 hover:border-green-700', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| outline: 'border border-gray-300 text-gray-700 hover:border-gray-400 hover:bg-gray-50', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ghost: 'text-green-600 hover:bg-green-50', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| danger: 'bg-gradient-to-r from-red-500 to-red-600 text-white hover:from-red-600 hover:to-red-700 shadow-lg hover:shadow-xl' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const sizes = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sm: 'px-4 py-2 text-sm', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| md: 'px-6 py-3 text-base', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lg: 'px-8 py-4 text-lg', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| xl: 'px-10 py-5 text-xl' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const buttonVariants = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| initial: { scale: 1 }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| hover: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scale: 1.05, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transition: { type: 'spring', stiffness: 300, damping: 20 } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tap: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scale: 0.95, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transition: { type: 'spring', stiffness: 300, damping: 20 } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rippleVariants = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| initial: { scale: 0, opacity: 0.5 }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| animate: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scale: 4, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| opacity: 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transition: { duration: 0.6, ease: 'easeOut' } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleClick = (e) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (disabled) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create ripple effect | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const button = e.currentTarget; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rect = button.getBoundingClientRect(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const size = Math.max(rect.width, rect.height); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const x = e.clientX - rect.left - size / 2; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const y = e.clientY - rect.top - size / 2; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const ripple = document.createElement('span'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ripple.style.cssText = ` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| position: absolute; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| left: ${x}px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| top: ${y}px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width: ${size}px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| height: ${size}px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| background: rgba(255, 255, 255, 0.3); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border-radius: 50%; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transform: scale(0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| animation: ripple 0.6s ease-out; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pointer-events: none; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| button.appendChild(ripple); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTimeout(() => ripple.remove(), 600); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (onClick) onClick(e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <motion.button | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className={`${baseClasses} ${variants[variant]} ${sizes[size]} ${className} ${ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| variants={buttonVariants} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| initial="initial" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| whileHover={!disabled ? "hover" : "initial"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| whileTap={!disabled ? "tap" : "initial"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={handleClick} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| disabled={disabled} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {...props} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+82
to
+94
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π οΈ Refactor suggestion Default button type should be βbuttonβ to prevent accidental form submits. - <motion.button
+ <motion.button
+ type="button"
className={`${baseClasses} ${variants[variant]} ${sizes[size]} ${className} ${
disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'
}`}π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {icon && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <motion.span | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| initial={{ rotate: 0 }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| whileHover={{ rotate: 360 }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transition={{ duration: 0.3 }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {icon} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </motion.span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="relative z-10">{children}</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* Shine effect */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-white/20 to-transparent hover:translate-x-full transition-transform duration-1000 ease-out" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <style jsx>{` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @keyframes ripple { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| to { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transform: scale(4); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| opacity: 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `}</style> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </motion.button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default AnimatedButton; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| import React, { useState, useEffect } from 'react'; | ||
| import { motion } from 'framer-motion'; | ||
| import { FaLeaf, FaRecycle, FaHeart } from 'react-icons/fa'; | ||
|
Comment on lines
+1
to
+3
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π οΈ Refactor suggestion Text rotation uses exit without AnimatePresence; exit wonβt run. Wrap the heading with AnimatePresence. -import { motion } from 'framer-motion';
+import { motion, AnimatePresence } from 'framer-motion';- <motion.div
- variants={itemVariants}
- className="h-20 mb-8 flex items-center justify-center"
- >
- <motion.h2
- key={currentText}
- initial={{ opacity: 0, y: 20 }}
- animate={{ opacity: 1, y: 0 }}
- exit={{ opacity: 0, y: -20 }}
- className="text-2xl md:text-4xl lg:text-5xl font-semibold text-gray-800"
- >
- {heroTexts[currentText]}
- </motion.h2>
- </motion.div>
+ <motion.div
+ variants={itemVariants}
+ className="h-20 mb-8 flex items-center justify-center"
+ >
+ <AnimatePresence mode="wait">
+ <motion.h2
+ key={currentText}
+ initial={{ opacity: 0, y: 20 }}
+ animate={{ opacity: 1, y: 0 }}
+ exit={{ opacity: 0, y: -20 }}
+ transition={{ duration: 0.3 }}
+ className="text-2xl md:text-4xl lg:text-5xl font-semibold text-gray-800"
+ >
+ {heroTexts[currentText]}
+ </motion.h2>
+ </AnimatePresence>
+ </motion.div>Also applies to: 112-125 π€ Prompt for AI Agents |
||
| import background from '../../../assets/background.png'; | ||
|
|
||
| const EnhancedHero = ({ onShopNowClick }) => { | ||
| const [currentText, setCurrentText] = useState(0); | ||
|
|
||
| const heroTexts = [ | ||
| "Your Eco-Friendly Shopping Heaven", | ||
| "Sustainable Products for Better Tomorrow", | ||
| "Green Living Made Simple" | ||
| ]; | ||
|
|
||
| useEffect(() => { | ||
| const interval = setInterval(() => { | ||
| setCurrentText((prev) => (prev + 1) % heroTexts.length); | ||
| }, 3000); | ||
| return () => clearInterval(interval); | ||
| }, []); | ||
|
|
||
| const containerVariants = { | ||
| hidden: { opacity: 0 }, | ||
| visible: { | ||
| opacity: 1, | ||
| transition: { | ||
| duration: 0.8, | ||
| staggerChildren: 0.2 | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const itemVariants = { | ||
| hidden: { y: 50, opacity: 0 }, | ||
| visible: { | ||
| y: 0, | ||
| opacity: 1, | ||
| transition: { duration: 0.6, ease: "easeOut" } | ||
| } | ||
| }; | ||
|
|
||
| const floatingVariants = { | ||
| animate: { | ||
| y: [-10, 10, -10], | ||
| transition: { | ||
| duration: 3, | ||
| repeat: Infinity, | ||
| ease: "easeInOut" | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <section | ||
| className="relative min-h-screen flex items-center justify-center overflow-hidden" | ||
| style={{ | ||
| backgroundImage: `url(${background})`, | ||
| backgroundPosition: "center", | ||
| backgroundSize: "cover", | ||
| backgroundRepeat: "no-repeat", | ||
| }} | ||
| > | ||
| <div className="absolute inset-0 bg-gradient-to-br from-green-900/20 via-transparent to-amber-900/20" /> | ||
|
|
||
| <motion.div | ||
| variants={floatingVariants} | ||
| animate="animate" | ||
| className="absolute top-20 left-10 text-green-600 opacity-30" | ||
| > | ||
| <FaLeaf size={40} /> | ||
| </motion.div> | ||
|
|
||
| <motion.div | ||
| variants={floatingVariants} | ||
| animate="animate" | ||
| className="absolute top-32 right-20 text-green-500 opacity-40" | ||
| style={{ animationDelay: '1s' }} | ||
| > | ||
| <FaRecycle size={35} /> | ||
| </motion.div> | ||
|
|
||
| <motion.div | ||
| variants={floatingVariants} | ||
| animate="animate" | ||
| className="absolute bottom-32 left-20 text-amber-600 opacity-30" | ||
| style={{ animationDelay: '2s' }} | ||
| > | ||
| <FaHeart size={30} /> | ||
| </motion.div> | ||
|
Comment on lines
+73
to
+89
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π οΈ Refactor suggestion Floating icons animate in sync; style-based animationDelay doesnβt affect Framer Motion. Add per-element transition delays and mark icons decorative. - <motion.div
- variants={floatingVariants}
- animate="animate"
- className="absolute top-32 right-20 text-green-500 opacity-40"
- style={{ animationDelay: '1s' }}
- >
- <FaRecycle size={35} />
- </motion.div>
+ <motion.div
+ variants={floatingVariants}
+ animate="animate"
+ transition={{ delay: 1 }}
+ className="absolute top-32 right-20 text-green-500 opacity-40"
+ aria-hidden="true"
+ >
+ <FaRecycle size={35} aria-hidden="true" focusable="false" />
+ </motion.div>
- <motion.div
- variants={floatingVariants}
- animate="animate"
- className="absolute bottom-32 left-20 text-amber-600 opacity-30"
- style={{ animationDelay: '2s' }}
- >
- <FaHeart size={30} />
- </motion.div>
+ <motion.div
+ variants={floatingVariants}
+ animate="animate"
+ transition={{ delay: 2 }}
+ className="absolute bottom-32 left-20 text-amber-600 opacity-30"
+ aria-hidden="true"
+ >
+ <FaHeart size={30} aria-hidden="true" focusable="false" />
+ </motion.div>Also add aria-hidden to the first icon: - <FaLeaf size={40} />
+ <FaLeaf size={40} aria-hidden="true" focusable="false" />
π€ Prompt for AI Agents |
||
|
|
||
| <div className="container mx-auto px-4 relative z-10"> | ||
| <motion.div | ||
| variants={containerVariants} | ||
| initial="hidden" | ||
| animate="visible" | ||
| className="max-w-4xl mx-auto text-center" | ||
| > | ||
| <motion.h1 | ||
| variants={itemVariants} | ||
| className="text-4xl md:text-6xl lg:text-7xl font-bold mb-6" | ||
| > | ||
| Welcome to{' '} | ||
| <motion.span | ||
| className="text-green-700 inline-block" | ||
| whileHover={{ scale: 1.05 }} | ||
| transition={{ type: "spring", stiffness: 300 }} | ||
| > | ||
| VigyBag! | ||
| </motion.span> | ||
| </motion.h1> | ||
|
|
||
| <motion.div | ||
| variants={itemVariants} | ||
| className="h-20 mb-8 flex items-center justify-center" | ||
| > | ||
| <motion.h2 | ||
| key={currentText} | ||
| initial={{ opacity: 0, y: 20 }} | ||
| animate={{ opacity: 1, y: 0 }} | ||
| exit={{ opacity: 0, y: -20 }} | ||
| className="text-2xl md:text-4xl lg:text-5xl font-semibold text-gray-800" | ||
| > | ||
| {heroTexts[currentText]} | ||
| </motion.h2> | ||
| </motion.div> | ||
|
|
||
| <motion.p | ||
| variants={itemVariants} | ||
| className="text-lg md:text-xl lg:text-2xl text-gray-700 mb-10 max-w-3xl mx-auto leading-relaxed" | ||
| > | ||
| At VigyBag, we curate the finest earth-friendly essentials to help you reduce your environmental footprint without compromising on quality or style. | ||
| </motion.p> | ||
|
|
||
| <motion.div | ||
| variants={itemVariants} | ||
| className="flex flex-col sm:flex-row gap-4 justify-center items-center" | ||
| > | ||
| <motion.button | ||
| onClick={onShopNowClick} | ||
| className="bg-gradient-to-r from-green-600 to-green-700 text-white px-8 py-4 rounded-full text-lg font-semibold shadow-lg" | ||
| whileHover={{ | ||
| scale: 1.05, | ||
| boxShadow: "0 10px 25px rgba(0,0,0,0.2)" | ||
| }} | ||
| whileTap={{ scale: 0.95 }} | ||
| transition={{ type: "spring", stiffness: 300 }} | ||
| > | ||
| Shop Now | ||
| </motion.button> | ||
|
|
||
| <motion.button | ||
| className="border-2 border-green-600 text-green-700 px-8 py-4 rounded-full text-lg font-semibold hover:bg-green-50" | ||
| whileHover={{ scale: 1.05 }} | ||
| whileTap={{ scale: 0.95 }} | ||
| transition={{ type: "spring", stiffness: 300 }} | ||
| > | ||
| Learn More | ||
| </motion.button> | ||
| </motion.div> | ||
|
|
||
| <motion.div | ||
| variants={itemVariants} | ||
| className="grid grid-cols-3 gap-8 mt-16 max-w-2xl mx-auto" | ||
| > | ||
| <div className="text-center"> | ||
| <motion.div | ||
| className="text-3xl font-bold text-green-700" | ||
| initial={{ scale: 0 }} | ||
| animate={{ scale: 1 }} | ||
| transition={{ delay: 1, type: "spring" }} | ||
| > | ||
| 1000+ | ||
| </motion.div> | ||
| <div className="text-gray-600">Eco Products</div> | ||
| </div> | ||
| <div className="text-center"> | ||
| <motion.div | ||
| className="text-3xl font-bold text-green-700" | ||
| initial={{ scale: 0 }} | ||
| animate={{ scale: 1 }} | ||
| transition={{ delay: 1.2, type: "spring" }} | ||
| > | ||
| 50K+ | ||
| </motion.div> | ||
| <div className="text-gray-600">Happy Customers</div> | ||
| </div> | ||
| <div className="text-center"> | ||
| <motion.div | ||
| className="text-3xl font-bold text-green-700" | ||
| initial={{ scale: 0 }} | ||
| animate={{ scale: 1 }} | ||
| transition={{ delay: 1.4, type: "spring" }} | ||
| > | ||
| 100% | ||
| </motion.div> | ||
| <div className="text-gray-600">Sustainable</div> | ||
| </div> | ||
| </motion.div> | ||
| </motion.div> | ||
| </div> | ||
| </section> | ||
| ); | ||
| }; | ||
|
|
||
| export default EnhancedHero; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π οΈ Refactor suggestion
Center ripple for keyboard-initiated βclicksβ; current math uses clientX/Y which are 0.
const handleClick = (e) => { if (disabled) return; // Create ripple effect const button = e.currentTarget; const rect = button.getBoundingClientRect(); const size = Math.max(rect.width, rect.height); - const x = e.clientX - rect.left - size / 2; - const y = e.clientY - rect.top - size / 2; + const isKeyboard = e.clientX === 0 && e.clientY === 0; + const x = isKeyboard ? rect.width / 2 - size / 2 : e.clientX - rect.left - size / 2; + const y = isKeyboard ? rect.height / 2 - size / 2 : e.clientY - rect.top - size / 2;π Committable suggestion
π€ Prompt for AI Agents