chore: Enhance app structure by removing routing and social icons components. Implement full-page scrolling with section navigation. Update package dependencies and add new project details to content options.

This commit is contained in:
Harivansh Rathi 2025-03-17 23:02:48 -04:00
parent c73262065d
commit 4ffb2ffc07
20 changed files with 19963 additions and 1775 deletions

16889
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -3,16 +3,26 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@studio-freight/lenis": "^1.0.42",
"@types/node": "^22.13.10",
"@types/react": "^19.0.11",
"bootstrap": "^5.2.3",
"clsx": "^2.1.1",
"emailjs-com": "^3.2.0",
"framer-motion": "^12.5.0",
"react": "^18.2.0",
"react-bootstrap": "^2.7.0",
"react-dom": "^18.2.0",
"react-helmet-async": "^1.0.7",
"react-icons": "^4.1.0",
"react-icons": "^4.12.0",
"react-intersection-observer": "^9.16.0",
"react-router-dom": "^6.6.2",
"react-scripts": "^5.0.1",
"react-transition-group": "^4.4.2",
"remixicon": "^4.6.0",
"remixicon-react": "^1.0.0",
"tailwind-merge": "^3.0.2",
"typescript": "^5.8.2",
"typewriter-effect": "^2.18.2",
"web-vitals": "^3.1.1"
},
@ -41,5 +51,10 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"autoprefixer": "^10.4.21",
"postcss": "^8.5.3",
"tailwindcss": "^4.0.14"
}
}

BIN
public/images/project6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

View file

@ -0,0 +1,448 @@
# Revised Smooth Scrolling Implementation Plan for React Portfolio
## Overview
Based on the issues encountered with the previous implementation, this revised plan takes a simpler but more reliable approach to create a smooth scrolling portfolio with perfectly contained full-page sections. We'll implement a fullpage.js-inspired solution with proper section sizing, positioning, and transitions.
## Technologies
- **React Fullpage**: A simplified custom implementation inspired by fullpage.js
- **React Intersection Observer**: To detect when sections enter/exit the viewport
- **Framer Motion**: For smooth animations and transitions
## Implementation Steps
### 1. Setup Dependencies
```bash
npm install framer-motion react-intersection-observer
```
### 2. Create FullPage Component
#### Create `src/components/FullPage.js`
```javascript
import React, { useState, useEffect, useRef } from 'react';
import { motion } from 'framer-motion';
const FullPage = ({ children }) => {
const [currentPage, setCurrentPage] = useState(0);
const [isScrolling, setIsScrolling] = useState(false);
const containerRef = useRef(null);
const pagesCount = React.Children.count(children);
// Handle page change
const goToPage = (pageIndex) => {
if (pageIndex >= 0 && pageIndex < pagesCount && !isScrolling) {
setIsScrolling(true);
setCurrentPage(pageIndex);
// Get the corresponding section ID to update URL
const sectionId = React.Children.toArray(children)[pageIndex].props.id;
window.history.replaceState(null, null, `#${sectionId}`);
setTimeout(() => {
setIsScrolling(false);
}, 1000); // Match this with your transition duration
}
};
// Handle wheel events
const handleWheel = (e) => {
if (isScrolling) return;
if (e.deltaY > 0) {
// Scrolling down
goToPage(currentPage + 1);
} else {
// Scrolling up
goToPage(currentPage - 1);
}
};
// Handle key events
const handleKeyDown = (e) => {
if (isScrolling) return;
if (e.key === 'ArrowDown' || e.key === 'PageDown') {
goToPage(currentPage + 1);
} else if (e.key === 'ArrowUp' || e.key === 'PageUp') {
goToPage(currentPage - 1);
}
};
// Initialize event listeners
useEffect(() => {
const container = containerRef.current;
if (container) {
container.addEventListener('wheel', handleWheel, { passive: false });
window.addEventListener('keydown', handleKeyDown);
return () => {
container.removeEventListener('wheel', handleWheel);
window.removeEventListener('keydown', handleKeyDown);
};
}
}, [currentPage, isScrolling]);
// Check for hash in URL on initial load
useEffect(() => {
const hash = window.location.hash.substring(1);
if (hash) {
const childrenArray = React.Children.toArray(children);
const initialPageIndex = childrenArray.findIndex(child => child.props.id === hash);
if (initialPageIndex !== -1) {
setCurrentPage(initialPageIndex);
}
}
}, []);
return (
<div
ref={containerRef}
className="fullpage-container"
style={{ overflow: 'hidden', height: '100vh', position: 'relative' }}
>
<div
className="fullpage-sections"
style={{
height: '100%',
width: '100%',
transition: 'transform 1s cubic-bezier(0.645, 0.045, 0.355, 1.000)',
transform: `translateY(-${currentPage * 100}vh)`
}}
>
{React.Children.map(children, (child, index) => (
<div
key={index}
className="fullpage-section"
style={{ height: '100vh', width: '100%' }}
>
{child}
</div>
))}
</div>
{/* Pagination Dots */}
<div className="fullpage-pagination">
{Array.from({ length: pagesCount }).map((_, index) => (
<motion.div
key={index}
className={`pagination-dot ${currentPage === index ? 'active' : ''}`}
initial={{ opacity: 0.5, scale: 0.8 }}
animate={{
opacity: currentPage === index ? 1 : 0.5,
scale: currentPage === index ? 1 : 0.8
}}
onClick={() => goToPage(index)}
/>
))}
</div>
</div>
);
};
export default FullPage;
```
### 3. Create Section Component
#### Create `src/components/Section.js`
```javascript
import React from 'react';
import { motion } from 'framer-motion';
const Section = ({ children, id, className = '' }) => {
return (
<motion.section
id={id}
className={`fullpage-section-content ${className}`}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
style={{
height: '100%',
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
position: 'relative'
}}
>
{children}
</motion.section>
);
};
export default Section;
```
### 4. Update Navigation Component
#### Enhance Header Component
Update the header to work with our new FullPage component by handling section navigation.
```javascript
// In src/header/index.js
import React, { useState, useContext } from 'react';
import "./style.css";
import { RiMenuLine, RiCloseLine } from "react-icons/ri";
import { logotext } from "../content_option";
import Themetoggle from "../components/themetoggle";
import { Socialicons } from "../components/socialicons";
const Headermain = () => {
const [isActive, setActive] = useState("false");
const handleToggle = () => {
setActive(!isActive);
document.body.classList.toggle("ovhidden");
};
const handleNavClick = (e, targetId) => {
e.preventDefault();
handleToggle();
// Find all section elements
const sections = document.querySelectorAll('.fullpage-section');
const targetSection = Array.from(sections).findIndex(
section => section.querySelector(`#${targetId}`) !== null
);
// Trigger a custom event that FullPage component will listen for
if (targetSection !== -1) {
const customEvent = new CustomEvent('navigateTo', {
detail: { index: targetSection }
});
document.dispatchEvent(customEvent);
}
};
// The rest of the component remains the same
}
```
### 5. Update App.js
Rewrite App.js to use our new FullPage and Section components.
```javascript
import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import AnimatedCursor from "../hooks/AnimatedCursor";
import Headermain from "../header";
import { Home } from "../pages/home";
import { About } from "../pages/about";
import { Portfolio } from "../pages/portfolio";
import { ContactUs } from "../pages/contact";
import { Socialicons } from "../components/socialicons";
import FullPage from "../components/FullPage";
import Section from "../components/Section";
import "./App.css";
export default function App() {
return (
<div className="app-container">
<div className="cursor__dot">
<AnimatedCursor
innerSize={15}
outerSize={15}
color="255, 255 ,255"
outerAlpha={0.4}
innerScale={0.7}
outerScale={5}
/>
</div>
<Headermain />
<FullPage>
<Section id="home">
<Home />
</Section>
<Section id="about">
<About />
</Section>
<Section id="portfolio">
<Portfolio />
</Section>
<Section id="contact">
<ContactUs />
</Section>
</FullPage>
<Socialicons />
</div>
);
}
```
### 6. Add Necessary CSS Styles
#### Update `src/app/App.css`
```css
/* Reset styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
overflow: hidden;
position: fixed;
height: 100%;
width: 100%;
}
/* Full page styles */
.app-container {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.fullpage-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
overflow: hidden;
}
.fullpage-section {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.fullpage-section-content {
padding-top: 60px; /* Account for header */
width: 100%;
height: 100%;
}
/* Navigation dots */
.fullpage-pagination {
position: fixed;
right: 20px;
top: 50%;
transform: translateY(-50%);
z-index: 100;
display: flex;
flex-direction: column;
gap: 15px;
}
.pagination-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: var(--text-color, #fff);
cursor: pointer;
transition: all 0.3s ease;
}
.pagination-dot.active {
transform: scale(1.2);
}
/* Make sure header is above fullpage */
.site__header {
z-index: 1000;
}
/* Social icons positioning */
.stick_follow_icon {
position: fixed;
bottom: 0;
left: 0;
z-index: 1000;
}
/* Responsive styles */
@media (max-width: 768px) {
.fullpage-pagination {
right: 10px;
gap: 10px;
}
.pagination-dot {
width: 8px;
height: 8px;
}
.fullpage-section-content {
padding-top: 50px;
}
}
```
### 7. Modify FullPage Component to Listen for Navigation Events
Update the FullPage component to listen for custom navigation events from the header.
```javascript
// Add this useEffect in the FullPage component
useEffect(() => {
const handleNavigation = (e) => {
const { index } = e.detail;
goToPage(index);
};
document.addEventListener('navigateTo', handleNavigation);
return () => {
document.removeEventListener('navigateTo', handleNavigation);
};
}, []);
```
## Implementation Timeline
1. **Setup (30 minutes)**:
- Install dependencies
- Create necessary files
2. **Core Components (2 hours)**:
- Implement FullPage component
- Implement Section component
- Setup basic navigation
3. **Styling and Refinement (1.5 hours)**:
- Implement CSS styles
- Add pagination dots
- Ensure responsive behavior
4. **Navigation Integration (1 hour)**:
- Connect header navigation with fullpage scrolling
- Implement URL hash updates
5. **Testing and Debugging (1 hour)**:
- Test on different browsers
- Ensure smooth scrolling on different devices
## Expected Results
- Clean, full-viewport sections that are perfectly contained
- Smooth transitions between sections
- Navigation dots for quick section access
- Header links that correctly navigate to sections
- URL hash updates for direct section access
- Keyboard navigation support
- Responsive design that works on all devices
## Future Enhancements
- Add touch swipe support for mobile devices
- Implement section-specific animations
- Add scroll progress indicator
- Create horizontal sliding sections for portfolio items
- Implement background parallax effects

View file

@ -1,42 +1,139 @@
@media only screen and (max-width: 991px) {
.s_c {
padding-top: 40px;
}
/* Variables */
:root {
--bg-color-rgb: 33, 37, 41;
--primary-color: #ff014f;
}
.page-enter {
transform: translateY(100%);
[data-theme="light"] {
--bg-color-rgb: 255, 255, 255;
}
.page-enter-active {
transform: translateY(0%);
transition: all 400ms ease-out;
/* Reset styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.page-exit {
transform: translateY(0%);
position: absolute;
left: 0;
right: 0;
top: 0;
html, body {
overflow: hidden;
position: fixed;
height: 100%;
width: 100%;
}
.page-exit-active {
position: absolute;
left: 0;
right: 0;
top: 0;
transform: translateY(-130%);
transition: all 400ms ease-out;
/* Full page styles */
.app-container {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.fullpage-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100vh;
overflow: hidden;
}
.fullpage-section {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.fullpage-section-content {
padding-top: 80px; /* Increased to account for header with margin */
width: 100%;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
-ms-overflow-style: none; /* Hide scrollbar IE and Edge */
scrollbar-width: none; /* Hide scrollbar Firefox */
}
.fullpage-section-content::-webkit-scrollbar {
display: none; /* Hide scrollbar Chrome, Safari, Opera */
}
.section-scrollable-content {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding-bottom: 40px; /* Add bottom padding for content */
}
/* Navigation dots */
.fullpage-pagination {
position: fixed;
right: 20px;
top: 50%;
transform: translateY(-50%);
z-index: 100;
display: flex;
flex-direction: column;
gap: 15px;
}
.pagination-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: var(--text-color, #fff);
cursor: pointer;
transition: all 0.3s ease;
}
.pagination-dot.active {
transform: scale(1.2);
}
/* Make sure header is above fullpage */
.site__header {
z-index: 1000;
}
/* Social icons positioning */
.stick_follow_icon {
position: fixed;
bottom: 0;
left: 0;
z-index: 1000;
}
/* Only allow scrolling on the active section */
.fullpage-section:not(.active-section) .fullpage-section-content {
overflow: hidden;
}
/* Responsive styles */
@media (max-width: 768px) {
.fullpage-pagination {
right: 10px;
gap: 10px;
}
.pagination-dot {
width: 8px;
height: 8px;
}
.fullpage-section-content {
padding-top: 50px;
}
}
@media (min-width: 1400px) {
.container,
.container-lg,
.container-md,
.container-sm,
.container-xl,
.container-xxl {
max-width: 1140px;
}
.container,
.container-lg,
.container-md,
.container-sm,
.container-xl,
.container-xxl {
max-width: 1140px;
}
}

View file

@ -1,27 +1,18 @@
import React, { useEffect } from "react";
import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import {
BrowserRouter as Router,
useLocation,
} from "react-router-dom";
import withRouter from "../hooks/withRouter";
import AppRoutes from "./routes";
import AnimatedCursor from "../hooks/AnimatedCursor";
import Headermain from "../header";
import AnimatedCursor from "../hooks/AnimatedCursor";
import { Home } from "../pages/home";
import { About } from "../pages/about";
import { Portfolio } from "../pages/portfolio";
import { ContactUs } from "../pages/contact";
import FullPage from "../components/FullPage";
import Section from "../components/Section";
import "./App.css";
function _ScrollToTop(props) {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return props.children;
}
const ScrollToTop = withRouter(_ScrollToTop);
export default function App() {
return (
<Router basename={process.env.PUBLIC_URL}>
<div className="app-container">
<div className="cursor__dot">
<AnimatedCursor
innerSize={15}
@ -32,10 +23,21 @@ export default function App() {
outerScale={5}
/>
</div>
<ScrollToTop>
<Headermain />
<AppRoutes />
</ScrollToTop>
</Router>
<Headermain />
<FullPage>
<Section id="home">
<Home />
</Section>
<Section id="about">
<About />
</Section>
<Section id="portfolio">
<Portfolio />
</Section>
<Section id="contact">
<ContactUs />
</Section>
</FullPage>
</div>
);
}

67
src/app/layout.css Normal file
View file

@ -0,0 +1,67 @@
html, body {
margin: 0;
padding: 0;
overflow-x: hidden;
height: 100%;
}
#root {
height: 100%;
}
#root > div {
min-height: 100%;
display: flex;
flex-direction: column;
}
.main-content {
flex: 1;
display: flex;
flex-direction: column;
width: 100%;
padding-top: 60px; /* Account for fixed header */
}
.sections-container {
flex: 1;
display: flex;
flex-direction: column;
}
.section-fullscreen {
min-height: calc(100vh - 60px);
width: 100%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
padding: 0;
}
#home {
height: calc(100vh - 60px);
display: flex;
align-items: center;
justify-content: center;
}
#home > div {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
/* Responsive adjustments */
@media (max-width: 991px) {
.main-content {
padding-top: 40px;
}
#home {
height: auto;
min-height: calc(100vh - 40px);
}
}

View file

@ -1,42 +0,0 @@
import React from "react";
import { Route, Routes} from "react-router-dom";
import withRouter from "../hooks/withRouter"
import { Home } from "../pages/home";
import { Portfolio } from "../pages/portfolio";
import { ContactUs } from "../pages/contact";
import { About } from "../pages/about";
import { Socialicons } from "../components/socialicons";
import { CSSTransition, TransitionGroup } from "react-transition-group";
const AnimatedRoutes = withRouter(({ location }) => (
<TransitionGroup>
<CSSTransition
key={location.key}
timeout={{
enter: 400,
exit: 400,
}}
classNames="page"
unmountOnExit
>
<Routes location={location}>
<Route exact path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/portfolio" element={<Portfolio />} />
<Route path="/contact" element={<ContactUs />} />
<Route path="*" element={<Home />} />
</Routes>
</CSSTransition>
</TransitionGroup>
));
function AppRoutes() {
return (
<div className="s_c">
<AnimatedRoutes />
<Socialicons />
</div>
);
}
export default AppRoutes;

155
src/components/FullPage.js Normal file
View file

@ -0,0 +1,155 @@
import React, { useState, useEffect, useRef } from 'react';
import { motion } from 'framer-motion';
const FullPage = ({ children }) => {
const [currentPage, setCurrentPage] = useState(0);
const [isScrolling, setIsScrolling] = useState(false);
const containerRef = useRef(null);
const pagesCount = React.Children.count(children);
// Handle page change
const goToPage = (pageIndex) => {
if (pageIndex >= 0 && pageIndex < pagesCount && !isScrolling) {
setIsScrolling(true);
setCurrentPage(pageIndex);
// Get the corresponding section ID to update URL
const sectionId = React.Children.toArray(children)[pageIndex].props.id;
window.history.replaceState(null, null, `#${sectionId}`);
setTimeout(() => {
setIsScrolling(false);
}, 1000); // Match this with your transition duration
}
};
// Handle wheel events with scrolling detection for active section
const handleWheel = (e) => {
if (isScrolling) return;
// Get the active section content element
const activeSection = document.querySelector('.active-section .fullpage-section-content');
if (activeSection) {
const { scrollTop, scrollHeight, clientHeight } = activeSection;
// Check if we're at the top or bottom of the section's content
const isAtTop = scrollTop === 0;
const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 5; // Allow small margin of error
// Only prevent default and change pages if we're at the top or bottom of content
if ((e.deltaY > 0 && isAtBottom) || (e.deltaY < 0 && isAtTop)) {
e.preventDefault();
if (e.deltaY > 0) {
// Scrolling down at bottom of content
goToPage(currentPage + 1);
} else {
// Scrolling up at top of content
goToPage(currentPage - 1);
}
}
}
};
// Handle key events
const handleKeyDown = (e) => {
if (isScrolling) return;
if (e.key === 'ArrowDown' || e.key === 'PageDown') {
goToPage(currentPage + 1);
} else if (e.key === 'ArrowUp' || e.key === 'PageUp') {
goToPage(currentPage - 1);
}
};
// Initialize event listeners
useEffect(() => {
const container = containerRef.current;
if (container) {
container.addEventListener('wheel', handleWheel, { passive: false });
window.addEventListener('keydown', handleKeyDown);
return () => {
container.removeEventListener('wheel', handleWheel);
window.removeEventListener('keydown', handleKeyDown);
};
}
}, [currentPage, isScrolling]);
// Check for hash in URL on initial load
useEffect(() => {
const hash = window.location.hash.substring(1);
if (hash) {
const childrenArray = React.Children.toArray(children);
const initialPageIndex = childrenArray.findIndex(child => child.props.id === hash);
if (initialPageIndex !== -1) {
setCurrentPage(initialPageIndex);
}
}
}, []);
// Listen for navigation events from header
useEffect(() => {
const handleNavigation = (e) => {
const { index } = e.detail;
goToPage(index);
};
document.addEventListener('navigateTo', handleNavigation);
return () => {
document.removeEventListener('navigateTo', handleNavigation);
};
}, []);
return (
<div
ref={containerRef}
className="fullpage-container"
style={{ overflow: 'hidden', height: '100vh', position: 'relative' }}
>
<div
className="fullpage-sections"
style={{
height: '100%',
width: '100%',
transition: 'transform 1s cubic-bezier(0.645, 0.045, 0.355, 1.000)',
transform: `translateY(-${currentPage * 100}vh)`
}}
>
{React.Children.map(children, (child, index) => {
// Add a class to the current page to allow scrolling only on active page
const isCurrentPage = index === currentPage;
return (
<div
key={index}
className={`fullpage-section ${isCurrentPage ? 'active-section' : ''}`}
style={{ height: '100vh', width: '100%' }}
>
{child}
</div>
);
})}
</div>
{/* Pagination Dots */}
<div className="fullpage-pagination">
{Array.from({ length: pagesCount }).map((_, index) => (
<motion.div
key={index}
className={`pagination-dot ${currentPage === index ? 'active' : ''}`}
initial={{ opacity: 0.5, scale: 0.8 }}
animate={{
opacity: currentPage === index ? 1 : 0.5,
scale: currentPage === index ? 1 : 0.8
}}
onClick={() => goToPage(index)}
/>
))}
</div>
</div>
);
};
export default FullPage;

View file

@ -0,0 +1,74 @@
import React, { useEffect, useState, useMemo } from 'react';
import { useSmoothScroll } from '../hooks/useSmoothScroll';
import { motion } from 'framer-motion';
const ScrollProgress = () => {
const [activeSection, setActiveSection] = useState('home');
const [scrollProgress, setScrollProgress] = useState(0);
const { lenis } = useSmoothScroll();
const sections = useMemo(() => ['home', 'about', 'portfolio', 'contact'], []);
useEffect(() => {
if (!lenis) return;
const handleScroll = ({ scroll, limit }) => {
// Calculate overall scroll progress
const progress = scroll / limit;
setScrollProgress(progress);
// Determine active section
const viewportHeight = window.innerHeight;
const currentPosition = scroll + viewportHeight / 2;
sections.forEach(sectionId => {
const section = document.getElementById(sectionId);
if (!section) return;
const sectionTop = section.offsetTop;
const sectionHeight = section.offsetHeight;
if (
currentPosition >= sectionTop &&
currentPosition <= sectionTop + sectionHeight
) {
setActiveSection(sectionId);
}
});
};
lenis.on('scroll', handleScroll);
return () => {
lenis.off('scroll', handleScroll);
};
}, [lenis, sections]);
return (
<div className="scroll-progress-container">
<div className="scroll-indicators">
{sections.map((section) => (
<motion.div
key={section}
className={`scroll-indicator ${activeSection === section ? 'active' : ''}`}
animate={{
scale: activeSection === section ? 1.2 : 1,
opacity: activeSection === section ? 1 : 0.5
}}
transition={{ duration: 0.3 }}
onClick={() => {
lenis.scrollTo(`#${section}`, {
duration: 1.2,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t))
});
}}
/>
))}
</div>
<motion.div
className="progress-bar"
style={{ scaleX: scrollProgress }}
/>
</div>
);
};
export default ScrollProgress;

30
src/components/Section.js Normal file
View file

@ -0,0 +1,30 @@
import React from 'react';
import { motion } from 'framer-motion';
const Section = ({ children, id, className = '' }) => {
return (
<motion.section
id={id}
className={`fullpage-section-content ${className}`}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
style={{
height: '100%',
width: '100%',
display: 'flex',
alignItems: 'flex-start',
justifyContent: 'center',
position: 'relative',
padding: '0 20px'
}}
>
<div className="section-scrollable-content">
{children}
</div>
</motion.section>
);
};
export default Section;

View file

@ -0,0 +1,40 @@
import React, { useEffect } from 'react';
import { motion } from 'framer-motion';
import { useInView } from 'react-intersection-observer';
import { useSmoothScroll } from '../hooks/useSmoothScroll';
const SectionWrapper = ({ children, id, className = '' }) => {
const { lenis } = useSmoothScroll();
const { ref, inView } = useInView({
threshold: 0.4,
triggerOnce: false
});
useEffect(() => {
if (inView && lenis) {
// Update URL hash without scrolling
window.history.replaceState(null, null, `#${id}`);
}
}, [inView, id, lenis]);
return (
<motion.section
id={id}
ref={ref}
className={`section-fullscreen ${className} ${inView ? 'active' : ''}`}
initial={{ opacity: 0.5 }}
animate={{
opacity: inView ? 1 : 0.5,
scale: inView ? 1 : 0.98
}}
transition={{
duration: 0.8,
ease: [0.33, 1, 0.68, 1] // Cubic bezier for Apple-like easing
}}
>
{children}
</motion.section>
);
};
export default SectionWrapper;

View file

@ -1,49 +0,0 @@
import React from "react";
import "./style.css";
import {
FaGithub,
FaTwitter,
FaFacebookF,
FaLinkedin,
FaYoutube,
FaTwitch,
FaInstagram,
FaSnapchatGhost,
FaTiktok,
FaFileAlt
} from "react-icons/fa";
import { socialprofils } from "../../content_option";
const ICON_MAPPING = {
default: FaFileAlt,
facebook: FaFacebookF,
github: FaGithub,
instagram: FaInstagram,
linkedin: FaLinkedin,
snapchat: FaSnapchatGhost,
tiktok: FaTiktok,
twitter: FaTwitter,
twitch: FaTwitch,
youtube: FaYoutube,
resume: FaFileAlt
};
export const Socialicons = (params) => {
return (
<div className="stick_follow_icon">
<ul>
{Object.entries(socialprofils).map(([platform, url]) => {
const IconComponent = ICON_MAPPING[platform] || ICON_MAPPING.default;
return (
<li key={platform}>
<a href={url}>
<IconComponent />
</a>
</li>
);
})}
</ul>
<p>Follow Me</p>
</div>
);
};

View file

@ -1,101 +0,0 @@
.stick_follow_icon {
top: 50%;
left: 30px;
width: 20px;
height: 200px;
position: fixed;
margin-top: -100px;
z-index: 10;
}
@media (max-width: 1200px) {
.stick_follow_icon {
left: 15px;
}
}
@media (max-width: 1100px) {
.stick_follow_icon {
left: 10px;
}
}
.stick_follow_icon ul {
list-style: none;
padding: 0;
margin: 0;
}
.stick_follow_icon svg {
width: 1.3em;
height: 1.3em;
fill: var(--text-color);
}
.stick_follow_icon p {
top: 70px;
left: -24px;
width: 68px;
height: 20px;
color: var(--text-color);
font-size: 12px;
font-weight: 600;
line-height: 1.2;
white-space: nowrap;
position: relative;
transform: rotate(-90deg);
}
.stick_follow_icon ul li {
display: block;
font-size: 12px;
text-align: center;
margin-bottom: 10px;
transition: all 0.3s;
}
.stick_follow_icon p:after {
top: 9px;
right: -48px;
width: 40px;
height: 1px;
content: "";
display: block;
position: absolute;
background-color: var(--text-color);
}
@media only screen and (max-width: 991px) {
.stick_follow_icon {
width: 100%;
height: auto;
position: static;
margin-top: 40px;
display: flex;
flex-direction: row-reverse;
justify-content: center;
padding: 20px 0;
align-items: center;
background: var(--background-color);
z-index: 1;
}
.stick_follow_icon p {
top: unset;
left: unset;
width: unset;
height: unset;
white-space: nowrap;
position: relative;
transform: unset;
font-size: 17px;
margin-right: 65px;
}
.stick_follow_icon ul {
margin-bottom: 20px;
}
.stick_follow_icon ul li {
display: inline;
margin-bottom: 29px;
margin-right: 10px;
}
}

View file

@ -126,6 +126,11 @@ const services = [{
];
const dataportfolio = [{
img: "/images/project6.jpg",
description: "AI-Powered Real Estate Investment Analysis. Make data-driven property investment decisions with comprehensive analytics, risk assessment, and market insights powered by artificial intelligence.",
link: "https://estate-ai-eight.vercel.app/landing",
},
{
img: "/images/project5.jpg",
description: "Founded SolveX, a gamified algorithm practice platform featuring real-time battles, AI-powered problem generation, and competitive matchmaking",
link: "https://solvex-pi.vercel.app/",

View file

@ -1,10 +1,8 @@
import React, { useState } from "react";
import "./style.css";
import { RiMenuLine, RiCloseLine } from "react-icons/ri";
import { Link } from "react-router-dom";
import { logotext ,socialprofils } from "../content_option";
import { logotext } from "../content_option";
import Themetoggle from "../components/themetoggle";
import { Socialicons } from "../components/socialicons";
const Headermain = () => {
const [isActive, setActive] = useState("false");
@ -14,18 +12,41 @@ const Headermain = () => {
document.body.classList.toggle("ovhidden");
};
const handleNavClick = (e, targetId) => {
e.preventDefault();
handleToggle();
// Find all section elements
const sections = document.querySelectorAll('.fullpage-section');
const targetSection = Array.from(sections).findIndex(
section => section.querySelector(`#${targetId}`) !== null
);
// Trigger a custom event that FullPage component will listen for
if (targetSection !== -1) {
const customEvent = new CustomEvent('navigateTo', {
detail: { index: targetSection }
});
document.dispatchEvent(customEvent);
}
};
return (
<>
<header className="fixed-top site__header">
<div className="d-flex align-items-center justify-content-between">
<Link className="navbar-brand nav_ac" to="/">
<a
className="navbar-brand nav_ac"
href="#home"
onClick={(e) => handleNavClick(e, 'home')}
>
{logotext}
</Link>
</a>
<div className="d-flex align-items-center">
<Themetoggle />
<button className="menu__button nav_ac" onClick={handleToggle}>
{!isActive ? <RiCloseLine /> : <RiMenuLine />}
</button>
<Themetoggle />
<button className="menu__button nav_ac" onClick={handleToggle}>
{!isActive ? <RiCloseLine /> : <RiMenuLine />}
</button>
</div>
</div>
@ -34,34 +55,44 @@ const Headermain = () => {
<div className="menu__wrapper">
<div className="menu__container p-3">
<ul className="the_menu">
<li className="menu_item ">
<Link onClick={handleToggle} to="/" className="my-3">Home</Link>
<li className="menu_item">
<a
href="#home"
className="my-3"
onClick={(e) => handleNavClick(e, 'home')}
>Home</a>
</li>
<li className="menu_item">
<Link onClick={handleToggle} to="/portfolio" className="my-3"> Portfolio</Link>
<a
href="#portfolio"
className="my-3"
onClick={(e) => handleNavClick(e, 'portfolio')}
>Portfolio</a>
</li>
<li className="menu_item">
<Link onClick={handleToggle} to="/about" className="my-3">About</Link>
<a
href="#about"
className="my-3"
onClick={(e) => handleNavClick(e, 'about')}
>About</a>
</li>
<li className="menu_item">
<Link onClick={handleToggle} to="/contact" className="my-3"> Contact</Link>
<a
href="#contact"
className="my-3"
onClick={(e) => handleNavClick(e, 'contact')}
>Contact</a>
</li>
</ul>
</div>
</div>
</div>
<div className="menu_footer d-flex flex-column flex-md-row justify-content-center align-items-md-center position-absolute w-100 p-3">
<div className="d-flex">
<Socialicons />
</div>
</div>
</div>
</header>
<div className="br-top"></div>
<div className="br-bottom"></div>
<div className="br-left"></div>
<div className="br-right"></div>
</>
);
};

View file

@ -0,0 +1,47 @@
import { useEffect, useState, createContext, useContext } from 'react';
import Lenis from '@studio-freight/lenis';
const SmoothScrollContext = createContext({
lenis: null,
isReady: false
});
export const SmoothScrollProvider = ({ children }) => {
const [lenis, setLenis] = useState(null);
const [isReady, setIsReady] = useState(false);
useEffect(() => {
const lenisInstance = new Lenis({
duration: 1.2,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
direction: 'vertical',
gestureDirection: 'vertical',
smooth: true,
smoothTouch: false,
touchMultiplier: 2
});
function raf(time) {
lenisInstance.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
setLenis(lenisInstance);
setIsReady(true);
return () => {
lenisInstance.destroy();
};
}, []);
return (
<SmoothScrollContext.Provider value={{ lenis, isReady }}>
{children}
</SmoothScrollContext.Provider>
);
};
export const useSmoothScroll = () => {
return useContext(SmoothScrollContext);
};

View file

@ -3,7 +3,13 @@ import "./style.css";
import { Helmet, HelmetProvider } from "react-helmet-async";
import Typewriter from "typewriter-effect";
import { introdata, meta } from "../../content_option";
import { Link } from "react-router-dom";
import {
RiGithubFill,
RiLinkedinFill,
RiFilePdfFill,
RiTwitterXFill
} from "react-icons/ri";
import { socialprofils } from "../../content_option";
export const Home = () => {
const [needsScroll, setNeedsScroll] = useState(false);
@ -57,7 +63,7 @@ export const Home = () => {
style={{ backgroundImage: `url(/assets/images/new_headshot.png)` }}
></div>
<h2 className="mb-1x">{introdata.title}</h2>
<h1 className="fluidz-48 mb-1x">
<h1 className="fluidz-48 mb-3">
<Typewriter
options={{
strings: [
@ -71,24 +77,70 @@ export const Home = () => {
}}
/>
</h1>
<div className="social-icons mb-3">
<a
href={socialprofils.github}
target="_blank"
rel="noopener noreferrer"
className="icon-link"
aria-label="GitHub"
>
<RiGithubFill />
</a>
<a
href={socialprofils.linkedin}
target="_blank"
rel="noopener noreferrer"
className="icon-link"
aria-label="LinkedIn"
>
<RiLinkedinFill />
</a>
<a
href={socialprofils.resume}
target="_blank"
rel="noopener noreferrer"
className="icon-link"
aria-label="Resume"
>
<RiFilePdfFill />
</a>
<a
href="https://x.com/HarivanshRathi"
target="_blank"
rel="noopener noreferrer"
className="icon-link"
aria-label="X (Twitter)"
>
<RiTwitterXFill />
</a>
</div>
<p className="mb-1x">{introdata.description}</p>
<div className="intro_btn-action pb-5">
<Link to="/portfolio" className="text_2">
<div id="button_p" className="ac_btn btn ">
My Portfolio
<div className="ring one"></div>
<div className="ring two"></div>
<div className="ring three"></div>
</div>
</Link>
<Link to="/contact">
<div id="button_h" className="ac_btn btn">
Contact Me
<div className="ring one"></div>
<div className="ring two"></div>
<div className="ring three"></div>
</div>
</Link>
<div className="buttons-container">
<a href="#portfolio" className="text_2" onClick={(e) => {
e.preventDefault();
document.getElementById('portfolio').scrollIntoView({ behavior: 'smooth' });
}}>
<div id="button_p" className="ac_btn btn ">
My Portfolio
<div className="ring one"></div>
<div className="ring two"></div>
<div className="ring three"></div>
</div>
</a>
<a href="#contact" onClick={(e) => {
e.preventDefault();
document.getElementById('contact').scrollIntoView({ behavior: 'smooth' });
}}>
<div id="button_h" className="ac_btn btn">
Contact Me
<div className="ring one"></div>
<div className="ring two"></div>
<div className="ring three"></div>
</div>
</a>
</div>
</div>
</div>
</div>

View file

@ -90,6 +90,16 @@ section {
flex-direction: column;
align-items: center;
gap: 15px;
width: 100%;
padding: 0;
}
.intro_btn-action .buttons-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
width: 100%;
}
.ac_btn {
@ -103,6 +113,9 @@ section {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 100%;
padding: 0 20px;
}
.intro_sec .text p {
@ -110,6 +123,11 @@ section {
max-width: 90%;
margin: 0 auto 20px;
}
.social-icons {
justify-content: center;
margin-bottom: 20px;
}
}
/* Extra small devices (phones, 600px and down) */
@ -127,6 +145,11 @@ section {
font-size: 14px;
padding: 10px 20px;
}
.ac_btn {
width: 180px; /* Slightly smaller on very small screens */
padding: 10px 20px;
}
}
.intro_sec .text,
@ -330,4 +353,71 @@ section {
background-color: transparent;
border: 0.5px solid var(--secondary-color);
}
.intro_btn-action {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 15px;
width: 100%;
padding: 0;
}
.intro_btn-action .buttons-container {
display: flex;
flex-direction: column;
gap: 15px;
align-items: flex-start;
}
.ac_btn {
margin-right: 0; /* Remove default right margin */
}
}
.social-icons {
display: flex;
gap: 12px;
align-items: center;
justify-content: flex-start;
padding: 0;
margin: 0;
}
.social-icons .icon-link {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 50%;
background: rgba(var(--bg-color-rgb, 0, 0, 0), 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.social-icons .icon-link:hover {
background: rgba(var(--primary-color-rgb, 255, 1, 79), 0.1);
transform: translateY(-2px);
}
.social-icons .icon-link svg {
width: 18px;
height: 18px;
fill: var(--text-color);
opacity: 0.8;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.social-icons .icon-link:hover svg {
opacity: 1;
fill: var(--primary-color, #ff014f);
}
/* Mobile responsive adjustments */
@media (max-width: 991.98px) {
.social-icons {
justify-content: center;
}
}

3320
yarn.lock

File diff suppressed because it is too large Load diff