- 
                Notifications
    
You must be signed in to change notification settings  - Fork 400
 
Add a animated heading #2640
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?
Add a animated heading #2640
Conversation
| 
           @khushboo-katariaa11 is attempting to deploy a commit to the Vivek Prajapati's projects Team on Vercel. A member of the Team first needs to authorize it.  | 
    
          
WalkthroughAdds a typing animation to the Home page hero text with blinking cursor styling. Updates Home.jsx markup to use typed text and revised CategoryCard props. Introduces blink keyframes and a typing-cursor class in index.css. Minor comment text change in App.jsx; no logic or routing behavior altered. Changes
 Sequence Diagram(s)sequenceDiagram
  autonumber
  participant U as User
  participant H as Home.jsx
  participant R as React Runtime
  participant CSS as CSS (index.css)
  U->>H: Navigate to Home
  activate H
  H->>R: mount() -> useEffect(start typing interval)
  loop every 100ms until fullText consumed
    R->>H: setState(typedText += next char)
    H-->>U: re-render with updated text + cursor
  end
  R->>H: setState(isTypingDone = true)
  H-->>U: render full gradient text + blinking cursor
  CSS-->>U: Apply .typing-cursor (blink keyframes)
  deactivate H
    Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
 Poem
 Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
 🧪 Generate unit tests
 Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit: 
 SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type  Other keywords and placeholders
 CodeRabbit Configuration File (
 | 
    
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.
Actionable comments posted: 5
🧹 Nitpick comments (3)
src/App.jsx (1)
172-172: Fix typo in commentChange “Product detailsF route” → “Product details route”.
- {/* Product detailsF route */} + {/* Product details route */}src/User/pages/Home/Home.jsx (2)
188-193: Prevent setState on unmounted component: store and clear the subscribe timeoutKeep the timeout id in a ref and clear it on unmount.
Apply this diff within the handler:
- setTimeout(() => { - setIsSubscribed(false); - setEmail(""); - }, 3000); + subscribeTimeoutRef.current = window.setTimeout(() => { + setIsSubscribed(false); + setEmail(""); + }, 3000);Add the supporting code outside this hunk:
// near other hooks const subscribeTimeoutRef = useRef(null); useEffect(() => { return () => { if (subscribeTimeoutRef.current) { clearTimeout(subscribeTimeoutRef.current); } }; }, []);
224-236: LGTM: gradient only when typing completesNice touch to gate the gradient on
isTypingDone. Consider using Tailwind utilities instead of inline transition style.- style={{ transition: "all 0.3s" }} + className={`${isTypingDone ? "bg-gradient-to-r from-gray-900 via-emerald-800 to-gray-900 bg-clip-text text-transparent" : ""} transition-all duration-300`}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
 - Jira integration is disabled by default for public repositories
 - Linear integration is disabled by default for public repositories
 
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
src/App.jsx(1 hunks)src/User/pages/Home/Home.jsx(9 hunks)src/index.css(1 hunks)
🔇 Additional comments (8)
src/User/pages/Home/Home.jsx (8)
1-1: LGTM: imports updated correctly
useEffectaddition is appropriate for the typing effect.
130-130: LGTM: constant for heading textKeeping
fullTextas a constant is fine for now.
141-144: LGTM: local state for typing animationState names are clear and scoped properly.
197-199: LGTM: wrapper semanticsUsing a
wrapper improves semantics; mobile search segregation is fine.
307-307: LGTM: section marker commentNo issues.
338-343: LGTM: anchor target for “Shop Now”Using
ref={sectionRef}with smooth scroll is correct.
377-386: LGTM: newsletter section background and overlaysLayering and blur look fine; no functional concerns.
437-437: LGTM: export remains defaultNo API changes.
| @keyframes blink { | ||
| 50% { | ||
| opacity: 0; | ||
| } | ||
| } | ||
| 
               | 
          ||
| .typing-cursor { | ||
| animation: blink 2s step-end infinite; | ||
| } | 
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.
💡 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 srcLength 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.
| 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); | ||
| }, []); | 
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
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.
| 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.
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.
Look at this code and try to impliment it
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.
@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:
- Removed mutable object hacks: Replaced 
idx = { current: 0 }anddisplayRef = { current: "" }with a simplelet idx = 0counter - Added accessibility support: Checks for 
prefers-reduced-motion: reduceand instantly shows the complete text for users with motion sensitivity - Used functional state updates: 
setTypedText((prev) => prev + fullText.charAt(idx))is more reliable than building a separate string - Explicit window methods: Used 
window.setIntervalandwindow.clearIntervalfor clarity 
This implementation is cleaner, more accessible, and follows React best practices while maintaining the same typing animation effect.
| className="typing-cursor text-emerald-600" | ||
| style={{ | ||
| animation: "blink 1s steps(2, start) infinite", | ||
| marginLeft: "2px", | ||
| }} | ||
| > | ||
| | | ||
| </span> | 
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
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.
| 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.
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.
look at this
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.
@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:
- Duplicate keyframes: The 
@keyframes blinkis defined twice in the component (lines ~228 and ~248) - Conflicting inline styles: The span uses both inline 
animationstyle and expects a CSS class - Inline margin: 
marginLeft: "2px"could be replaced with Tailwind'sml-0.5 
The suggested refactor would clean this up by:
- Using only the CSS class for animation (remove inline 
animationproperty) - 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> | 
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
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.
| <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.
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.
Look at this
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.
@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:
- One inside the 
<h1>element (lines ~245-258) - 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> | ||
| 
               | 
          
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
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.
| {/* 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).
| 
           The latest updates on your projects. Learn more about Vercel for GitHub. 
  | 
    
| 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); | ||
| }, []); | 
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.
Look at this code and try to impliment it
| className="typing-cursor text-emerald-600" | ||
| style={{ | ||
| animation: "blink 1s steps(2, start) infinite", | ||
| marginLeft: "2px", | ||
| }} | ||
| > | ||
| | | ||
| </span> | 
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.
look at this
| <style> | ||
| {` | ||
| @keyframes blink { | ||
| 0% { opacity: 1; } | ||
| 50% { opacity: 0; } | ||
| 100% { opacity: 1; } | ||
| } | ||
| .typing-cursor { | ||
| display: inline-block; | ||
| width: 1ch; | ||
| } | ||
| `} | ||
| </style> | ||
| </h1> | 
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.
Look at this
Fixes Issue
#2591
Changes proposed
Homecomponent.typedTextandisTypingDonestate variables to handle animation flow.useEffectto animate the heading text one character at a time.Screenshots
Summary by CodeRabbit
New Features
Style