Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export default function App() {
path="productDetails/:productId"
element={<ProductDetails />}
/>{" "}
{/* Product details route */}
{/* Product detailsF route */}
<Route path="wishlist" element={<Wishlist />} />
{/* Wishlist page route */}
<Route path="contact" element={<Contact />} />
Expand Down
128 changes: 86 additions & 42 deletions src/User/pages/Home/Home.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, { useRef, useState } from "react";
import React, { useRef, useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import CategoryCard from "../../components/HomPageCard/CategoryCard";
import LatestInMarketCard from "../../components/HomPageCard/LatestInMarketCard";
import background from "../../../assets/background.png";
import app from "../../../assets/app.png";
import { Link } from "react-router-dom";
import SearchBar from "../../pages/Home/SearchBar";

// Import category images
Expand All @@ -25,7 +24,6 @@ import BambooProductsImg from "../../../assets/Bamboo-Products.png";
import StorageBasketsImg from "../../../assets/Storage-Baskets.png";
import DownArrow from "../../components/DownArrow/downArrow";

// Redirecting links
const popularCategories = [
{
name: "Fashion & Accessories",
Expand Down Expand Up @@ -129,6 +127,8 @@ const latestProducts = [
},
];

const fullText = "Welcome to VigyBag!";

const Home = () => {
const sectionRef = useRef(null);
const [searchTerm, setSearchTerm] = useState("");
Expand All @@ -138,11 +138,32 @@ const Home = () => {
const [isSubscribed, setIsSubscribed] = useState(false);
const [email, setEmail] = useState("");

// Typing animation states (updated)
const [typedText, setTypedText] = useState('');
const [isTypingDone, setIsTypingDone] = useState(false);

useEffect(() => {
setTypedText('');
setIsTypingDone(false);
const idx = { current: 0 };
const displayRef = { current: "" };
const interval = setInterval(() => {
if (idx.current < fullText.length) {
displayRef.current += fullText.charAt(idx.current);
setTypedText(displayRef.current);
idx.current += 1;
} else {
clearInterval(interval);
setIsTypingDone(true);
}
}, 100);
return () => clearInterval(interval);
}, []);
Comment on lines +145 to +161
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Simplify effect, handle reduced-motion, and avoid mutable object hacks

Use a plain counter, append via functional setState, and short-circuit when users prefer reduced motion.

Apply this diff:

 useEffect(() => {
-    setTypedText('');
-    setIsTypingDone(false);
-    const idx = { current: 0 };
-    const displayRef = { current: "" };
-    const interval = setInterval(() => {
-      if (idx.current < fullText.length) {
-        displayRef.current += fullText.charAt(idx.current);
-        setTypedText(displayRef.current);
-        idx.current += 1;
-      } else {
-        clearInterval(interval);
-        setIsTypingDone(true);
-      }
-    }, 100);
-    return () => clearInterval(interval);
+    setTypedText('');
+    setIsTypingDone(false);
+    if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
+      setTypedText(fullText);
+      setIsTypingDone(true);
+      return;
+    }
+    let idx = 0;
+    const interval = window.setInterval(() => {
+      if (idx < fullText.length) {
+        setTypedText((prev) => prev + fullText.charAt(idx));
+        idx += 1;
+      } else {
+        window.clearInterval(interval);
+        setIsTypingDone(true);
+      }
+    }, 100);
+    return () => window.clearInterval(interval);
   }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
setTypedText('');
setIsTypingDone(false);
const idx = { current: 0 };
const displayRef = { current: "" };
const interval = setInterval(() => {
if (idx.current < fullText.length) {
displayRef.current += fullText.charAt(idx.current);
setTypedText(displayRef.current);
idx.current += 1;
} else {
clearInterval(interval);
setIsTypingDone(true);
}
}, 100);
return () => clearInterval(interval);
}, []);
useEffect(() => {
setTypedText('');
setIsTypingDone(false);
if (
window.matchMedia &&
window.matchMedia('(prefers-reduced-motion: reduce)').matches
) {
setTypedText(fullText);
setIsTypingDone(true);
return;
}
let idx = 0;
const interval = window.setInterval(() => {
if (idx < fullText.length) {
setTypedText((prev) => prev + fullText.charAt(idx));
idx += 1;
} else {
window.clearInterval(interval);
setIsTypingDone(true);
}
}, 100);
return () => window.clearInterval(interval);
}, []);
🤖 Prompt for AI Agents
In src/User/pages/Home/Home.jsx around lines 145 to 161, the effect uses mutable
object hacks for index and display, doesn’t respect reduced-motion, and can be
simplified: replace the idx/displayRef pattern with a simple numeric counter
variable closed over by the interval (or use a useRef counter) and update text
by calling setTypedText(prev => prev + nextChar) inside the interval; before
starting, short-circuit if window.matchMedia('(prefers-reduced-motion:
reduce)').matches and immediately set typed text to fullText and
setIsTypingDone(true); ensure you still clear the interval in the cleanup.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look at this code and try to impliment it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codervivek5, I'll implement the improved version of the typing animation effect based on the CodeRabbit suggestion. Here's the refactored code:

useEffect(() => {
  setTypedText('');
  setIsTypingDone(false);
  
  // Respect user's motion preferences for accessibility
  if (
    window.matchMedia &&
    window.matchMedia('(prefers-reduced-motion: reduce)').matches
  ) {
    setTypedText(fullText);
    setIsTypingDone(true);
    return;
  }
  
  let idx = 0;
  const interval = window.setInterval(() => {
    if (idx < fullText.length) {
      setTypedText((prev) => prev + fullText.charAt(idx));
      idx += 1;
    } else {
      window.clearInterval(interval);
      setIsTypingDone(true);
    }
  }, 100);
  
  return () => window.clearInterval(interval);
}, []);

Key improvements implemented:

  1. Removed mutable object hacks: Replaced idx = { current: 0 } and displayRef = { current: "" } with a simple let idx = 0 counter
  2. Added accessibility support: Checks for prefers-reduced-motion: reduce and instantly shows the complete text for users with motion sensitivity
  3. Used functional state updates: setTypedText((prev) => prev + fullText.charAt(idx)) is more reliable than building a separate string
  4. Explicit window methods: Used window.setInterval and window.clearInterval for clarity

This implementation is cleaner, more accessible, and follows React best practices while maintaining the same typing animation effect.


const handleSearch = (e) => {
const term = e.target.value;
setSearchTerm(term);

// Filter categories and products for suggestions
const filteredSuggestions = [
...popularCategories.filter((category) =>
category.name.toLowerCase().includes(term.toLowerCase())
Expand All @@ -155,7 +176,6 @@ const Home = () => {
};

const handleSuggestionClick = (suggestion) => {
// Navigate to the corresponding path
navigate(suggestion.path);
};

Expand All @@ -165,18 +185,17 @@ const Home = () => {

const handleSubscribe = (e) => {
e.preventDefault();
setIsSubscribed(true); // Show the success message
setIsSubscribed(true);
setTimeout(() => {
setIsSubscribed(false); // Hide the message
setEmail(""); // Clear the email input
}, 3000); // 3 seconds
setIsSubscribed(false);
setEmail("");
}, 3000);
};


return (
<div className="bg-gradient-to-b from-orange-50 to-orange-100 min-h-screen">
<main className="">
{/* ENHANCED: Mobile search with glassmorphism */}
<main>
{/* Mobile search */}
<div className="sm:block md:hidden bg-gradient-to-r from-emerald-50 to-teal-50 pt-5 backdrop-blur-sm">
<SearchBar
searchTerm={searchTerm}
Expand All @@ -202,15 +221,55 @@ const Home = () => {

<div className="container mx-auto px-4 flex flex-col md:flex-row items-center relative z-10">
<div className="w-full md:w-2/3 lg:w-1/2 text-center md:text-left">
{/* Heading without extra margin-top */}
<h1 className="block text-[33px] sm:text-4xl md:text-[53px] font-extrabold mb-4 leading-snug pb-2 bg-gradient-to-r from-gray-900 via-emerald-800 to-gray-900 bg-clip-text text-transparent animate-fade-in">
Welcome to{" "}
<span className="text-transparent bg-gradient-to-r from-emerald-600 to-teal-700 bg-clip-text">
VigyBag!
{/* Typing Animation Heading (updated) */}
<h1 className="block text-[33px] sm:text-4xl md:text-[53px] font-extrabold mb-4 leading-snug pb-2">
<span
className={
isTypingDone
? "bg-gradient-to-r from-gray-900 via-emerald-800 to-gray-900 bg-clip-text text-transparent"
: ""
}
style={{ transition: "all 0.3s" }}
>
{typedText}
</span>
<span
className="typing-cursor text-emerald-600"
style={{
animation: "blink 1s steps(2, start) infinite",
marginLeft: "2px",
}}
>
|
</span>
Comment on lines +237 to 244
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove inline cursor animation; rely on CSS class

Avoid conflicting animation definitions and inline styles. Use Tailwind for margin.

-                <span
-                  className="typing-cursor text-emerald-600"
-                  style={{
-                    animation: "blink 1s steps(2, start) infinite",
-                    marginLeft: "2px",
-                  }}
-                >
+                <span className="typing-cursor text-emerald-600 ml-0.5">
                   |
                 </span>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
className="typing-cursor text-emerald-600"
style={{
animation: "blink 1s steps(2, start) infinite",
marginLeft: "2px",
}}
>
|
</span>
<span className="typing-cursor text-emerald-600 ml-0.5">
|
</span>
🤖 Prompt for AI Agents
In src/User/pages/Home/Home.jsx around lines 237 to 244, the span currently uses
inline styles for the blink animation and margin which conflicts with
CSS/Tailwind; remove the inline style object entirely and remove the hardcoded
animation and marginLeft, replace them by adding/using a CSS class (e.g., a
utility or custom class like "blink" for keyframes) plus Tailwind margin utility
(e.g., "ml-0.5" or appropriate ml-*), so the span only declares className values
and the blink keyframes live in your stylesheet (or Tailwind plugin) rather than
inline.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

look at this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codervivek5, I can see the refactor suggestion from CodeRabbit about cleaning up the typing cursor animation. The suggestion is indeed valid - there are several issues with the current implementation:

  1. Duplicate keyframes: The @keyframes blink is defined twice in the component (lines ~228 and ~248)
  2. Conflicting inline styles: The span uses both inline animation style and expects a CSS class
  3. Inline margin: marginLeft: "2px" could be replaced with Tailwind's ml-0.5

The suggested refactor would clean this up by:

  • Using only the CSS class for animation (remove inline animation property)
  • Using Tailwind margin utility (ml-0.5) instead of inline margin
  • Consolidating the duplicate keyframes definitions

This would make the code more maintainable and follow better React/CSS practices. Would you like me to show you the complete cleaned-up version?

<style>
{`
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0; }
100% { opacity: 1; }
}
.typing-cursor {
display: inline-block;
width: 1ch;
}
`}
</style>
</h1>
Comment on lines +245 to 258
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Delete inline <style> block defining blink

This duplicates global CSS and risks keyframe collisions; remove it.

-                <style>
-                  {`
-                      @keyframes blink {
-                        0% { opacity: 1; }
-                        50% { opacity: 0; }
-                        100% { opacity: 1; }
-                      }
-                      .typing-cursor {
-                        display: inline-block;
-                        width: 1ch;
-                      }
-                  `}
-                </style>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<style>
{`
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0; }
100% { opacity: 1; }
}
.typing-cursor {
display: inline-block;
width: 1ch;
}
`}
</style>
</h1>
</h1>
🤖 Prompt for AI Agents
src/User/pages/Home/Home.jsx around lines 245 to 258, remove the inline <style>
block that defines the @keyframes blink and .typing-cursor rules because they
duplicate global CSS and can cause keyframe/name collisions; delete the entire
<style>{`...`}</style> fragment (ensuring the surrounding JSX remains valid and
the closing </h1> stays in place), rely on the existing global stylesheet for
those definitions, and if necessary ensure the element still has the correct
className so the global styles apply.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look at this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codervivek5, I see what you're pointing out! Looking at the code more carefully, there are actually two identical <style> blocks with the same @keyframes blink and .typing-cursor definitions:

  1. One inside the <h1> element (lines ~245-258)
  2. Another right after the </h1> closing tag (lines ~264-276)

Both blocks contain exactly the same CSS rules, which creates unnecessary duplication and potential conflicts. Since the global index.css file already defines these styles:

@keyframes blink {
  50% { opacity: 0; }
}

.typing-cursor {
  animation: blink 2s step-end infinite;
}

Both inline <style> blocks should be removed entirely. The component can simply use the typing-cursor class name and rely on the global stylesheet for the animation definitions.

This would clean up the code significantly and eliminate the redundancy you've identified.



{/* Blinking animation keyframes */}
<style>
{`
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0; }
100% { opacity: 1; }
}
.typing-cursor {
display: inline-block;
width: 1ch;
}
`}
</style>

Comment on lines +259 to 273
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove duplicated inline <style> block (second copy)

This is a second identical definition; delete to avoid overriding global styles unpredictably.

-              <style>
-                {`
-                  @keyframes blink {
-                    0% { opacity: 1; }
-                    50% { opacity: 0; }
-                    100% { opacity: 1; }
-                  }
-                  .typing-cursor {
-                    display: inline-block;
-                    width: 1ch;
-                  }
-                `}
-              </style>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{/* Blinking animation keyframes */}
<style>
{`
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0; }
100% { opacity: 1; }
}
.typing-cursor {
display: inline-block;
width: 1ch;
}
`}
</style>
{/* Blinking animation keyframes */}
🤖 Prompt for AI Agents
In src/User/pages/Home/Home.jsx around lines 259 to 273, there's a duplicated
inline <style> block defining the blink keyframes and .typing-cursor; remove
this second copy to avoid overriding global styles unpredictably—delete the
entire <style>...</style> block at these lines and ensure only the original
single definition remains elsewhere in the file (or consolidate if needed).

<h2
className="text-[25px] sm:text-2xl md:text-[33px] font-bold mb-6 text-gray-800 drop-shadow-sm"
Expand Down Expand Up @@ -245,29 +304,24 @@ const Home = () => {
</div>
</section>


{/* ENHANCED: Popular Categories with glassmorphism and hover effects */}
{/* Popular Categories */}
<section className="py-8 sm:py-12 md:py-16 bg-gradient-to-b from-orange-100 to-orange-50 relative">
{/* ENHANCED: Decorative background elements */}
<div className="absolute inset-0 overflow-hidden">
<div className="absolute top-10 left-10 w-32 h-32 bg-emerald-200 rounded-full opacity-20 blur-3xl"></div>
<div className="absolute bottom-20 right-10 w-40 h-40 bg-orange-200 rounded-full opacity-20 blur-3xl"></div>
</div>

<div className="container mx-auto px-4 relative z-10">
{/* ENHANCED: Section title with gradient and better typography */}
<h2 className="text-xl sm:text-2xl md:text-3xl font-bold mb-6 md:mb-8 text-transparent bg-gradient-to-r from-gray-800 to-emerald-800 bg-clip-text text-center">
Popular Categories
</h2>

{/* ENHANCED: Grid with hover animations */}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4 sm:gap-6 md:gap-8">
{popularCategories.map((category, index) => (
<div
key={index}
className="group transform hover:scale-105 transition-all duration-300 hover:z-10"
>
{/* ENHANCED: Card wrapper with glassmorphism */}
<div className="backdrop-blur-sm bg-white/60 rounded-2xl p-2 shadow-lg hover:shadow-2xl border border-white/40 group-hover:bg-white/80 transition-all duration-300">
<CategoryCard
name={category.name}
Expand All @@ -281,13 +335,12 @@ const Home = () => {
</div>
</section>

{/* ENHANCED: Latest in the Market with improved styling */}
{/* Latest in the Market */}
<section
className="bg-gradient-to-b from-orange-50 to-amber-50 py-8 sm:py-12 md:py-16 relative overflow-hidden"
id="sect"
ref={sectionRef}>

{/* ENHANCED: Animated background pattern */}
ref={sectionRef}
>
<div className="absolute inset-0">
<div className="absolute top-0 left-0 w-full h-full opacity-5">
<div className="absolute top-20 left-20 w-24 h-24 bg-emerald-400 rounded-full animate-bounce"></div>
Expand All @@ -297,12 +350,10 @@ const Home = () => {
</div>

<div className="container mx-auto px-4 relative z-10">
{/* ENHANCED: Section title with animation */}
<h2 className="text-xl sm:text-2xl md:text-3xl font-bold mb-6 md:mb-8 text-transparent bg-gradient-to-r from-gray-800 to-teal-800 bg-clip-text text-center animate-fade-in">
Latest in the Market
</h2>

{/* ENHANCED: Product grid with staggered animations */}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4 md:gap-6">
{latestProducts.map((product, index) => (
<div
Expand All @@ -313,7 +364,6 @@ const Home = () => {
}}
>
<div className="w-full max-w-[220px] transform group-hover:scale-105 transition-all duration-300">
{/* ENHANCED: Product card wrapper with glassmorphism */}
<div className="backdrop-blur-sm bg-white/70 rounded-2xl p-3 shadow-lg hover:shadow-2xl border border-white/50 group-hover:bg-white/90 transition-all duration-300">
<LatestInMarketCard product={product} />
</div>
Expand All @@ -324,35 +374,30 @@ const Home = () => {
</div>
</section>

{/* ENHANCED: Newsletter Section with modern glassmorphism design */}
{/* Newsletter Section */}
<section
className="py-8 sm:py-12 md:py-16 relative mb-[-1px] overflow-hidden"
style={{
backgroundImage: `url(${app})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}>

{/* ENHANCED: Multi-layered background overlay */}
}}
>
<div className="absolute inset-0 bg-gradient-to-br from-gray-900/70 via-emerald-900/50 to-gray-900/70"></div>
<div className="absolute inset-0 backdrop-blur-sm"></div>

<div className="container mx-auto px-4 relative z-10">
{/* ENHANCED: Newsletter card with advanced glassmorphism */}
<div className="backdrop-blur-xl bg-white/10 rounded-3xl p-6 sm:p-8 md:p-12 max-w-3xl mx-auto border border-white/20 shadow-2xl">
<div className="text-center">
{/* ENHANCED: Title with gradient text */}
<h2 className="text-2xl sm:text-3xl md:text-4xl font-bold mb-4 text-transparent bg-gradient-to-r from-white to-emerald-200 bg-clip-text">
Stay Updated with Our Latest News
</h2>

{/* ENHANCED: Subtitle with better styling */}
<p className="text-base sm:text-lg md:text-xl mb-8 text-gray-200 leading-relaxed">
Subscribe to our newsletter to receive exclusive updates,
promotions, and tips for sustainable living.
</p>

{/* ENHANCED: Form with modern styling */}
<form onSubmit={handleSubscribe} className="space-y-4">
<div className="relative">
<input
Expand All @@ -373,7 +418,6 @@ const Home = () => {
</button>
</form>

{/* ENHANCED: Success message with animation */}
{isSubscribed && (
<div className="mt-6 p-4 bg-emerald-500/80 backdrop-blur-sm rounded-xl border border-emerald-400/50 animate-bounce">
<p className="text-white font-semibold">
Expand All @@ -390,4 +434,4 @@ const Home = () => {
);
};

export default Home;
export default Home;
9 changes: 9 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,12 @@
cursor: pointer;
}

@keyframes blink {
50% {
opacity: 0;
}
}

.typing-cursor {
animation: blink 2s step-end infinite;
}
Comment on lines +30 to +38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Unify cursor animation in CSS and avoid duplication with component inline styles

Define the cursor blink behavior here and drop conflicting inline styles in Home.jsx. Align timing with the component (1s, 2 steps), and include width/margin so no inline styles are needed.

Apply this diff:

 @keyframes blink {
-  50% {
-    opacity: 0;
-  }
+  50% { opacity: 0; }
 }
 
 .typing-cursor {
-  animation: blink 2s step-end infinite;
+  animation: blink 1s steps(2, start) infinite;
+  display: inline-block;
+  width: 1ch;
+  margin-left: 2px;
 }

Add reduced-motion support (outside the changed hunk) to respect user preferences:

@media (prefers-reduced-motion: reduce) {
  .typing-cursor { animation: none; }
}

Run to confirm there’s now a single blink keyframes and no conflicting inline animation:


🏁 Script executed:

#!/bin/bash
rg -nP "@keyframes\\s+blink|typing-cursor|steps\\(" -C2 src

Length of output: 2804


Unify cursor animation in a single CSS file
Define the blink keyframes and .typing-cursor once in src/index.css—using animation: blink 1s steps(2, start) infinite; display:inline-block; width:1ch; margin-left:2px;—and add a @media (prefers-reduced-motion: reduce) rule to disable the animation. Then remove the duplicate @keyframes blink and .typing-cursor rules from src/User/pages/Home/typewriter.css and the embedded <style> blocks and inline animation/marginLeft styles in Home.jsx.

🤖 Prompt for AI Agents
In src/index.css around lines 30 to 38, the blink keyframes and .typing-cursor
are defined differently across the project; consolidate by defining @keyframes
blink and .typing-cursor once in src/index.css using animation: blink 1s
steps(2, start) infinite; display: inline-block; width: 1ch; margin-left: 2px;
and add a @media (prefers-reduced-motion: reduce) rule that sets animation: none
for .typing-cursor to honor reduced-motion preferences; then remove the
duplicate @keyframes blink and .typing-cursor rules from
src/User/pages/Home/typewriter.css and delete any embedded <style> blocks and
inline animation/marginLeft styles in Home.jsx so the component relies solely on
the centralized CSS.