mirror of
https://github.com/harivansh-afk/React-Portfolio.git
synced 2026-04-15 08:03:45 +00:00
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:
parent
c73262065d
commit
4ffb2ffc07
20 changed files with 19963 additions and 1775 deletions
16889
package-lock.json
generated
Normal file
16889
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
17
package.json
17
package.json
|
|
@ -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
BIN
public/images/project6.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 174 KiB |
448
smooth-scroll-implementation-plan.md
Normal file
448
smooth-scroll-implementation-plan.md
Normal 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
|
||||
157
src/app/App.css
157
src/app/App.css
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
67
src/app/layout.css
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
155
src/components/FullPage.js
Normal 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;
|
||||
74
src/components/ScrollProgress.js
Normal file
74
src/components/ScrollProgress.js
Normal 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
30
src/components/Section.js
Normal 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;
|
||||
40
src/components/SectionWrapper.js
Normal file
40
src/components/SectionWrapper.js
Normal 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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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/",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
47
src/hooks/useSmoothScroll.js
Normal file
47
src/hooks/useSmoothScroll.js
Normal 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);
|
||||
};
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue