initial commit

This commit is contained in:
Harivansh Rathi 2025-01-03 15:55:01 +05:30
commit fb5b992b17
35 changed files with 11573 additions and 0 deletions

2
.env Normal file
View file

@ -0,0 +1,2 @@
GENERATE_SOURCEMAP=false
DISABLE_ESLINT_PLUGIN=true

23
.gitignore vendored Normal file
View file

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
*.eslintcache
npm-debug.log*
yarn-debug.log*
yarn-error.log*

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Ubai Mutl
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

38
README.md Normal file
View file

@ -0,0 +1,38 @@
### Description
A simple portfolio template for developer/designers built with React.
### [live preview](https://ubaimutl.github.io/react-portfolio/)
[![react portfoiio](src/assets/images/react%20portfolio%20gif.gif)](https://ubaimutl.github.io/react-portfolio/)
### Features
- Fully Responsive
- Multi-Page Layout
- Contact Form With EmailJs
- React-Bootstrap
- Edit Content From One Place
### Setup
Get the code
<pre>git clone https://github.com/ubaimutl/react-portfolio.git</pre>
Install required dependencies
<pre>yarn install</pre>
Start the server
<pre>yarn start</pre>
### More
Modify pages content in `src/content_option.js`.
### Thanks
If you like this portfolio template don't forget give it a ⭐

45
package.json Normal file
View file

@ -0,0 +1,45 @@
{
"name": "react-portfolio",
"version": "0.1.0",
"private": true,
"dependencies": {
"bootstrap": "^5.2.3",
"emailjs-com": "^3.2.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-router-dom": "^6.6.2",
"react-scripts": "^5.0.1",
"react-transition-group": "^4.4.2",
"typewriter-effect": "^2.18.2",
"web-vitals": "^3.1.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"predeploy": "yarn build && cp build/index.html build/404.html",
"deploy": "gh-pages -d build"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

22
public/index.html Normal file
View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="author" content="Harivansh Rathi">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Marcellus&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,400;0,500;1,400;1,500&display=swap" rel="stylesheet">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

25
public/manifest.json Normal file
View file

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
public/robots.txt Normal file
View file

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

42
src/app/App.css Normal file
View file

@ -0,0 +1,42 @@
@media only screen and (max-width: 991px) {
.s_c {
padding-top: 40px;
}
}
.page-enter {
transform: translateY(100%);
}
.page-enter-active {
transform: translateY(0%);
transition: all 400ms ease-out;
}
.page-exit {
transform: translateY(0%);
position: absolute;
left: 0;
right: 0;
top: 0;
}
.page-exit-active {
position: absolute;
left: 0;
right: 0;
top: 0;
transform: translateY(-130%);
transition: all 400ms ease-out;
}
@media (min-width: 1400px) {
.container,
.container-lg,
.container-md,
.container-sm,
.container-xl,
.container-xxl {
max-width: 1140px;
}
}

41
src/app/App.js Normal file
View file

@ -0,0 +1,41 @@
import React, { useEffect } 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 Headermain from "../header";
import AnimatedCursor from "../hooks/AnimatedCursor";
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="cursor__dot">
<AnimatedCursor
innerSize={15}
outerSize={15}
color="255, 255 ,255"
outerAlpha={0.4}
innerScale={0.7}
outerScale={5}
/>
</div>
<ScrollToTop>
<Headermain />
<AppRoutes />
</ScrollToTop>
</Router>
);
}

42
src/app/routes.js Normal file
View file

@ -0,0 +1,42 @@
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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

View file

@ -0,0 +1,37 @@
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="2em"
height="2em"
x="0"
y="0"
viewBox="0 0 512 512"
>
<g>
<g xmlns="http://www.w3.org/2000/svg">
<g>
<circle
cx="256"
cy="256"
fill="#ffffff"
r="256"
data-original="#ff3333"
/>
</g>
<g>
<path
d="m374.02 119.205v155.57c0 65.08-52.94 118.02-118.02 118.02s-118.02-52.94-118.02-118.02v-155.57h58v155.57c0 33.09 26.93 60.02 60.02 60.02s60.02-26.93 60.02-60.02v-155.57z"
fill="#000000"
data-original="#f8fffb"
/>
</g>
<g>
<path
d="m374.02 119.2v155.58c0 65.08-52.94 118.02-118.02 118.02v-58c33.09 0 60.02-26.94 60.02-60.02v-155.58z"
fill="#000000"
data-original="#d8d8d8"
/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,49 @@
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

@ -0,0 +1,86 @@
.stick_follow_icon {
top: 50%;
left: 30px;
width: 20px;
height: 200px;
position: fixed;
margin-top: -100px;
}
.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 .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: unset;
height: unset;
position: static;
margin-top: unset;
display: flex;
flex-direction: row-reverse;
justify-content: center;
padding: 40px 0;
align-items: center;
}
.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

@ -0,0 +1,21 @@
import React, { useEffect, useState } from "react";
import { WiMoonAltWaningCrescent4 } from "react-icons/wi";
const Themetoggle = () => {
const [theme, settheme] = useState(localStorage.getItem("theme"));
const themetoggle = () => {
settheme(theme === "dark" ? "light" : "dark");
};
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme );
}, [theme]);
return (
<div className="nav_ac" onClick={themetoggle}>
<WiMoonAltWaningCrescent4 />
</div>
);
};
export default Themetoggle;

View file

@ -0,0 +1,15 @@
.theme_toggler {
background: var(--primary-color);
z-index: 999999999;
left: 10px;
background: var(--primary-color);
display: flex;
height: 50px;
align-items: center;
padding-left: 10px;
}
.theme_toggler svg {
width: 2em;
height: 2em;
}

170
src/content_option.js Normal file
View file

@ -0,0 +1,170 @@
const logotext = "HARIVANSH";
const meta = {
title: "Harivansh Rathi",
description: "Im Harivansh Rathi, a Computer Science student at the University of Virginia.",
};
const introdata = {
title: "Hi, I'm Harivansh Rathi",
animated: {
first: "Full Stack Developer",
second: "Software Engineer",
third: "Tech Enthusiast",
},
description: "I'm passionate about building exceptional digital experiences that make a difference. With expertise in both frontend and backend development, I create scalable and efficient solutions that solve real-world problems.",
your_img_url: "https://i.imgur.com/0y00000.png",
};
const dataabout = {
title: "About Me",
aboutme: "I am a Computer Science student at the University of Virginia, with a passion for software development and AI. I enjoy building scalable applications and exploring the latest advancements in technology. I am always eager to learn and contribute to innovative projects.",
};
const worktimeline = [{
jobtitle: "Front End Development Intern",
where: "UNIKOVE TECHNOLOGIES",
date: "June 2024 - August 2024",
},
{
jobtitle: "Software Development Intern",
where: "MOGLIX",
date: "June 2023 - August 2023",
},
{
jobtitle: "Software Engineering Intern",
where: "SAN AUTO",
date: "June 2022 - August 2022",
},
];
const skills = [{
name: "Python",
value: 90,
},
{
name: "Java",
value: 85,
},
{
name: "Typescript",
value: 80,
},
{
name: "HTML",
value: 90,
},
{
name: "CSS/Tailwind",
value: 85,
},
{
name: "C/C++",
value: 70,
},
{
name: "React.js",
value: 80,
},
{
name: "Node.js",
value: 75,
},
{
name: "Express.js",
value: 75,
},
{
name: "Vite",
value: 80,
},
{
name: "Next.js 14",
value: 70,
},
{
name: "Git/Github",
value: 90,
},
{
name: "PostgreSQL",
value: 80,
},
{
name: "MySQL",
value: 80,
},
{
name: "Supabase",
value: 75,
},
{
name: "Vercel",
value: 80,
},
{
name: "OAuth 2.0",
value: 70,
},
];
const services = [{
title: "Front End Development",
description: "Utilizing JavaScript and React.js to refine UX design elements, ensuring seamless functionality and integration.",
},
{
title: "Back End Development",
description: "Developing robust and scalable backend solutions using Node.js, Express.js, and SQL databases.",
},
{
title: "AI Application Development",
description: "Building AI-powered applications with a focus on document retrieval and data analysis.",
},
];
const dataportfolio = [{
img: "https://picsum.photos/400/300/?grayscale",
description: "Built a React/TypeScript RAG AI chat application with n8n, achieving 95% query response accuracy",
link: "https://github.com/harivansh-afk/RAG-ui",
},
{
img: "https://picsum.photos/400/300/?grayscale",
description: "Co-authored a research paper on cryptocurrency market predictability with a PHD student at CMU",
link: "https://github.com/harivansh-afk/CryptoCurrencyPredictionLSTM",
},
{
img: "https://picsum.photos/400/300/?grayscale",
description: "Built a full-stack habit tracker web app with React, TypeScript, and Supabase, boosting scalability by 25%",
link: "https://github.com/harivansh-afk/Habit-Tracker",
},
{
img: "https://picsum.photos/400/300/?grayscale",
description: "Built a React/TypeScript app with Shadcn/UI and Tailwind, implementing lazy loading for 40% faster loads",
link: "https://github.com/harivansh-afk/ENGL-Final-Project",
},
];
const contactConfig = {
YOUR_EMAIL: "zng2gc@virginia.edu",
YOUR_FONE: "+1 434-310-1227",
description: "I am currently based in Charlottesville, VA. Feel free to reach out to me for any opportunities or collaborations.",
YOUR_SERVICE_ID: "service_id",
YOUR_TEMPLATE_ID: "template_id",
YOUR_USER_ID: "user_id",
};
const socialprofils = {
github: "https://github.com/harivansh-afk",
linkedin: "https://linkedin.com/in/harivansh-rathi",
resume: "https://docs.google.com/document/d/1TKbtqYinjRNFZiZNbxEwRTz5QDiZ5mOk/edit?usp=sharing&ouid=104313709347999918268&rtpof=true&sd=true"
};
export {
meta,
dataabout,
dataportfolio,
worktimeline,
skills,
services,
introdata,
contactConfig,
socialprofils,
logotext,
};

72
src/header/index.js Normal file
View file

@ -0,0 +1,72 @@
import React, { useState } from "react";
import "./style.css";
import { VscGrabber, VscClose } from "react-icons/vsc";
import { Link } from "react-router-dom";
import { logotext ,socialprofils } from "../content_option";
import Themetoggle from "../components/themetoggle";
const Headermain = () => {
const [isActive, setActive] = useState("false");
const handleToggle = () => {
setActive(!isActive);
document.body.classList.toggle("ovhidden");
};
return (
<>
<header className="fixed-top site__header">
<div className="d-flex align-items-center justify-content-between">
<Link className="navbar-brand nav_ac" to="/">
{logotext}
</Link>
<div className="d-flex align-items-center">
<Themetoggle />
<button className="menu__button nav_ac" onClick={handleToggle}>
{!isActive ? <VscClose /> : <VscGrabber />}
</button>
</div>
</div>
<div className={`site__navigation ${!isActive ? "menu__opend" : ""}`}>
<div className="bg__menu h-100">
<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>
<li className="menu_item">
<Link onClick={handleToggle} to="/portfolio" className="my-3"> Portfolio</Link>
</li>
<li className="menu_item">
<Link onClick={handleToggle} to="/about" className="my-3">About</Link>
</li>
<li className="menu_item">
<Link onClick={handleToggle} to="/contact" className="my-3"> Contact</Link>
</li>
</ul>
</div>
</div>
</div>
<div className="menu_footer d-flex flex-column flex-md-row justify-content-between align-items-md-center position-absolute w-100 p-3">
<div className="d-flex">
<a href={socialprofils.github}>Github</a>
<a href={socialprofils.linkedin}>LinkedIn</a>
<a href={socialprofils.portfolio}>Resume</a>
</div>
<p className="copyright m-0">copyright __ {logotext}</p>
</div>
</div>
</header>
<div className="br-top"></div>
<div className="br-bottom"></div>
<div className="br-left"></div>
<div className="br-right"></div>
</>
);
};
export default Headermain;

207
src/header/style.css Normal file
View file

@ -0,0 +1,207 @@
.site__header {
top: 10px;
padding-left: 10px;
padding-right: 10px;
}
.menu__button {
color: var(--text-color);
}
.menu__button:focus,
.menu__button:hover {
color: var(--text-color);
box-shadow: unset;
}
.menu__button svg {
width: 2em;
height: 2em;
fill: var(--text-color-2);
color: var(--text-color-2);
}
.nav_ac {
padding: 5px 15px;
margin: 0;
border: unset;
background: var(--primary-color);
font-size: 1.25rem;
font-family: Marcellus;
color: var(--text-color-2);
line-height: 2;
height: 50px;
font-weight: bold;
z-index: 1000;
}
.nav_ac:hover {
color: var(--text-color-2);
}
.br-top,
.br-bottom,
.br-right,
.br-left {
position: fixed;
z-index: 999999;
background: var(--primary-color);
}
.br-top {
top: 0;
height: 10px;
left: 0;
width: 100%;
}
.br-bottom {
bottom: 0;
left: 0;
height: 10px;
width: 100%;
}
.br-right {
width: 10px;
right: 0;
top: 0;
height: 100%;
}
.br-left {
width: 10px;
left: 0;
top: 0;
height: 100%;
}
.cortina__wrapper-menu {
position: relative;
width: 100%;
padding-top: 5em;
padding-bottom: 3em;
height: 100%;
overflow-y: auto;
}
.site__navigation {
height: 100%;
left: 0;
overflow: hidden;
position: fixed;
top: 0;
width: 100%;
visibility: hidden;
}
.menu__opend {
visibility: visible !important;
}
.main__menu_ul,
.menu_right {
opacity: 0;
position: relative;
transition: 0.5s;
transition-delay: 0s;
visibility: hidden;
z-index: 100;
}
.menu_right {
text-align: center;
}
.site__navigation.menu__opend .main__menu_ul,
.site__navigation.menu__opend .menu_right {
opacity: 1;
transition-delay: 0.6s;
visibility: visible;
}
.site__navigation .main__menu_ul li {
list-style: none;
margin: 10px 0;
}
.site__navigation .main__menu_ul li a {
color: var(--text-color);
display: block;
font-size: 2.5rem;
text-decoration: none;
}
.bg__menu {
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
background-color: var(--primary-color);
will-change: transform;
transform: translateY(-100%);
transition: .5s ease all;
}
.menu__opend .bg__menu {
transform: translateY(0);
}
.menu__wrapper {
position: relative;
width: 100%;
height: 100%;
overflow: hidden auto;
}
.the_menu {
padding-top: 20vh;
padding-bottom: 20vh;
padding-left: 0;
}
@media (min-width: 992px) {
.menu__container {
margin-left: 33.3333%;
}
.the_menu {
padding-top: 10vh;
padding-bottom: 10vh;
}
}
.the_menu .menu_item>a {
color: var(--text-color-2);
line-height: 1;
font-size: 2rem;
display: inline-block;
position: relative;
transition: color 250ms cubic-bezier(0, 0, 0.58, 1) 0s;
padding: 4px 0px;
text-decoration: none;
font-family: Marcellus;
}
.the_menu .menu_item>a:hover {
color: var(--text-color-3);
}
@media (min-width: 768px) {
.the_menu .menu_item>a {
font-size: 4.8vw;
}
}
.menu_footer {
bottom: 0;
font-family: Marcellus;
font-size: 14px;
background: var(--primary-color);
}
.menu_footer a {
color: var(--text-color-2);
margin-right: 10px;
text-decoration: none;
}

329
src/hooks/AnimatedCursor.js Normal file
View file

@ -0,0 +1,329 @@
import React, {useEffect,useRef,useState,useCallback} from "react"
const IsDevice = (() => {
if (typeof navigator == 'undefined') return
let ua = navigator.userAgent
return {
info: ua,
Android() {
return ua.match(/Android/i)
},
BlackBerry() {
return ua.match(/BlackBerry/i)
},
IEMobile() {
return ua.match(/IEMobile/i)
},
iOS() {
return ua.match(/iPhone|iPad|iPod/i)
},
iPad() {
return (
ua.match(/Mac/) &&
navigator.maxTouchPoints &&
navigator.maxTouchPoints > 2
)
},
OperaMini() {
return ua.match(/Opera Mini/i)
},
/**
* Any Device
*/
any() {
return (
IsDevice.Android() ||
IsDevice.BlackBerry() ||
IsDevice.iOS() ||
IsDevice.iPad() ||
IsDevice.OperaMini() ||
IsDevice.IEMobile()
)
}
}
})()
function useEventListener(eventName, handler, element = document) {
const savedHandler = useRef()
useEffect(() => {
savedHandler.current = handler
}, [handler])
useEffect(() => {
const isSupported = element && element.addEventListener
if (!isSupported) return
const eventListener = (event) => savedHandler.current(event)
element.addEventListener(eventName, eventListener)
return () => {
element.removeEventListener(eventName, eventListener)
}
}, [eventName, element])
}
/**
* Cursor Core
* Replaces the native cursor with a custom animated cursor, consisting
* of an inner and outer dot that scale inversely based on hover or click.
*
* @author Stephen Scaff (github.com/stephenscaff)
*
* @param {string} color - rgb color value
* @param {number} outerAlpha - level of alpha transparency for color
* @param {number} innerSize - inner cursor size in px
* @param {number} innerScale - inner cursor scale amount
* @param {number} outerSize - outer cursor size in px
* @param {number} outerScale - outer cursor scale amount
* @param {object} outerStyle - style object for outer cursor
* @param {object} innerStyle - style object for inner cursor
* @param {array} clickables - array of clickable selectors
*
*/
function CursorCore({
outerStyle,
innerStyle,
color = '220, 90, 90',
outerAlpha = 0.3,
innerSize = 8,
outerSize = 8,
outerScale = 6,
innerScale = 0.6,
trailingSpeed = 8,
clickables = [
'a',
'input[type="text"]',
'input[type="email"]',
'input[type="number"]',
'input[type="submit"]',
'input[type="image"]',
'label[for]',
'select',
'textarea',
'button',
'.link'
]
}) {
const cursorOuterRef = useRef()
const cursorInnerRef = useRef()
const requestRef = useRef()
const previousTimeRef = useRef()
const [coords, setCoords] = useState({ x: 0, y: 0 })
const [isVisible, setIsVisible] = useState(false)
const [isActive, setIsActive] = useState(false)
const [isActiveClickable, setIsActiveClickable] = useState(false)
let endX = useRef(0)
let endY = useRef(0)
/**
* Primary Mouse move event
* @param {number} clientX - MouseEvent.clientx
* @param {number} clientY - MouseEvent.clienty
*/
const onMouseMove = useCallback(({ clientX, clientY }) => {
setCoords({ x: clientX, y: clientY })
cursorInnerRef.current.style.top = `${clientY}px`
cursorInnerRef.current.style.left = `${clientX}px`
endX.current = clientX
endY.current = clientY
}, [])
// Outer Cursor Animation Delay
const animateOuterCursor = useCallback(
(time) => {
if (previousTimeRef.current !== undefined) {
coords.x += (endX.current - coords.x) / trailingSpeed
coords.y += (endY.current - coords.y) / trailingSpeed
cursorOuterRef.current.style.top = `${coords.y}px`
cursorOuterRef.current.style.left = `${coords.x}px`
}
previousTimeRef.current = time
requestRef.current = requestAnimationFrame(animateOuterCursor)
},
[requestRef] // eslint-disable-line
)
// RAF for animateOuterCursor
useEffect(() => {
requestRef.current = requestAnimationFrame(animateOuterCursor)
return () => cancelAnimationFrame(requestRef.current)
}, [animateOuterCursor])
// Mouse Events State updates
const onMouseDown = useCallback(() => setIsActive(true), [])
const onMouseUp = useCallback(() => setIsActive(false), [])
const onMouseEnterViewport = useCallback(() => setIsVisible(true), [])
const onMouseLeaveViewport = useCallback(() => setIsVisible(false), [])
useEventListener('mousemove', onMouseMove)
useEventListener('mousedown', onMouseDown)
useEventListener('mouseup', onMouseUp)
useEventListener('mouseover', onMouseEnterViewport)
useEventListener('mouseout', onMouseLeaveViewport)
// Cursors Hover/Active State
useEffect(() => {
if (isActive) {
cursorInnerRef.current.style.transform = `translate(-50%, -50%) scale(${innerScale})`
cursorOuterRef.current.style.transform = `translate(-50%, -50%) scale(${outerScale})`
} else {
cursorInnerRef.current.style.transform = 'translate(-50%, -50%) scale(1)'
cursorOuterRef.current.style.transform = 'translate(-50%, -50%) scale(1)'
}
}, [innerScale, outerScale, isActive])
// Cursors Click States
useEffect(() => {
if (isActiveClickable) {
cursorInnerRef.current.style.transform = `translate(-50%, -50%) scale(${
innerScale * 1.2
})`
cursorOuterRef.current.style.transform = `translate(-50%, -50%) scale(${
outerScale * 1.4
})`
}
}, [innerScale, outerScale, isActiveClickable])
// Cursor Visibility State
useEffect(() => {
if (isVisible) {
cursorInnerRef.current.style.opacity = 1
cursorOuterRef.current.style.opacity = 1
} else {
cursorInnerRef.current.style.opacity = 0
cursorOuterRef.current.style.opacity = 0
}
}, [isVisible])
useEffect(() => {
const clickableEls = document.querySelectorAll(clickables.join(','))
clickableEls.forEach((el) => {
el.style.cursor = 'none'
el.addEventListener('mouseover', () => {
setIsActive(true)
})
el.addEventListener('click', () => {
setIsActive(true)
setIsActiveClickable(false)
})
el.addEventListener('mousedown', () => {
setIsActiveClickable(true)
})
el.addEventListener('mouseup', () => {
setIsActive(true)
})
el.addEventListener('mouseout', () => {
setIsActive(false)
setIsActiveClickable(false)
})
})
return () => {
clickableEls.forEach((el) => {
el.removeEventListener('mouseover', () => {
setIsActive(true)
})
el.removeEventListener('click', () => {
setIsActive(true)
setIsActiveClickable(false)
})
el.removeEventListener('mousedown', () => {
setIsActiveClickable(true)
})
el.removeEventListener('mouseup', () => {
setIsActive(true)
})
el.removeEventListener('mouseout', () => {
setIsActive(false)
setIsActiveClickable(false)
})
})
}
}, [isActive, clickables])
// Cursor Styles
const styles = {
cursorInner: {
zIndex: 999,
display: 'block',
position: 'fixed',
borderRadius: '50%',
width: innerSize,
height: innerSize,
pointerEvents: 'none',
backgroundColor: `rgba(${color}, 1)`,
...(innerStyle && innerStyle),
transition: 'opacity 0.15s ease-in-out, transform 0.25s ease-in-out'
},
cursorOuter: {
zIndex: 999,
display: 'block',
position: 'fixed',
borderRadius: '50%',
pointerEvents: 'none',
width: outerSize,
height: outerSize,
backgroundColor: `rgba(${color}, ${outerAlpha})`,
transition: 'opacity 0.15s ease-in-out, transform 0.15s ease-in-out',
willChange: 'transform',
...(outerStyle && outerStyle)
}
}
// Hide / Show global cursor
document.body.style.cursor = 'none'
return (
<React.Fragment>
<div ref={cursorOuterRef} style={styles.cursorOuter} />
<div ref={cursorInnerRef} style={styles.cursorInner} />
</React.Fragment>
)
}
/**
* AnimatedCursor
* Calls and passes props to CursorCore if not a touch/mobile device.
*/
function AnimatedCursor({
outerStyle,
innerStyle,
color,
outerAlpha,
innerSize,
innerScale,
outerSize,
outerScale,
trailingSpeed,
clickables
}) {
if (typeof navigator !== 'undefined' && IsDevice.any()) {
return <React.Fragment></React.Fragment>
}
return (
<CursorCore
outerStyle={outerStyle}
innerStyle={innerStyle}
color={color}
outerAlpha={outerAlpha}
innerSize={innerSize}
innerScale={innerScale}
outerSize={outerSize}
outerScale={outerScale}
trailingSpeed={trailingSpeed}
clickables={clickables}
/>
)
}
export default AnimatedCursor

21
src/hooks/withRouter.js Normal file
View file

@ -0,0 +1,21 @@
import { useLocation, useNavigate, useParams } from 'react-router-dom';
function withRouter(Component) {
function ComponentWithRouterProp(props) {
let location = useLocation();
let navigate = useNavigate();
let params = useParams();
return (
<Component
{...props}
location={location}
params={params}
navigate={navigate}
/>
);
}
return ComponentWithRouterProp;
}
export default withRouter;

96
src/index.css Normal file
View file

@ -0,0 +1,96 @@
:root {
--bg-color: #0c0c0c;
--primary-color: #0d0d0d;
--secondary-color: #fff;
--text-color: #fff;
--text-color-2: #fff;
--text-color-3: rgb(204, 0, 0);
--overlay-color: rgb(12 12 12 / 63%);
}
[data-theme="light"] {
--bg-color: #ffffff;
--primary-color: #ffffff;
--secondary-color: #000;
--text-color: #000;
--text-color-2: #000;
--text-color-3: rgb(204, 0, 0);
--overlay-color: rgb(255 255 255 / 70%);
}
html,
body {
height: 100%;
}
body {
margin: 0;
height: 100%;
overflow-x: hidden;
overflow-y: visible;
background-color: var(--bg-color);
color: var(--text-color);
font-family: 'Raleway', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding-top: 60px;
border-left: 10px solid var(--primary-color);
border-right: 10px solid var(--primary-color);
}
ul {
list-style: none;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: Marcellus;
}
a,
a:hover {
color: var(--text-color);
}
p {
word-break: break-word;
hyphens: auto;
}
.ovhidden {
overflow: hidden;
}
.text_2,
.text_2:hover {
color: var(--text-color-2);
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
}
.cursor__dot div {
z-index: 999999 !important;
}
.cursor__dot div:last-child {
background-color: var(--text-color-3) !important;
}
.cursor__dot div:first-child {
filter: invert(1);
background-color: var(--overlay-color) !important;
}
.color_pr {
color: var(--primary-color) !important;
}
.color_sec {
color: var(--secondary-color);
}

9
src/index.js Normal file
View file

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from "react-dom/client";
import App from './app/App';
import './index.css';
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<App />
);

107
src/pages/about/index.js Normal file
View file

@ -0,0 +1,107 @@
import React from "react";
import "./style.css";
import { Helmet, HelmetProvider } from "react-helmet-async";
import { Container, Row, Col } from "react-bootstrap";
import {
dataabout,
meta,
worktimeline,
skills,
} from "../../content_option";
export const About = () => {
return (
<HelmetProvider>
<Container className="About-header">
<Helmet>
<meta charSet="utf-8" />
<title> About | {meta.title}</title>
<meta name="description" content={meta.description} />
</Helmet>
<Row className="mb-5 mt-3 pt-md-3">
<Col lg="8">
<h1 className="display-4 mb-4">About me</h1>
<hr className="t_border my-4 ml-0 text-left" />
</Col>
</Row>
<Row className="sec_sp">
<Col lg="5">
<h3 className="color_sec py-4">{dataabout.title}</h3>
</Col>
<Col lg="7" className="d-flex align-items-center">
<div>
<p>{dataabout.aboutme}</p>
</div>
</Col>
</Row>
<Row className=" sec_sp">
<Col lg="5">
<h3 className="color_sec py-4">Work Timline</h3>
</Col>
<Col lg="7">
<table className="table caption-top">
<tbody>
{worktimeline.map((data, i) => {
return (
<tr key={i}>
<th scope="row">{data.jobtitle}</th>
<td>{data.where}</td>
<td>{data.date}</td>
</tr>
);
})}
</tbody>
</table>
</Col>
</Row>
<Row className="sec_sp">
<Col lg="5">
<h3 className="color_sec py-4">Skills</h3>
</Col>
<Col lg="7">
{skills.map((data, i) => {
return (
<div key={i}>
<h3 className="progress-title">{data.name}</h3>
<div className="progress">
<div
className="progress-bar"
style={{
width: `${data.value}%`,
}}
>
<div className="progress-value">{data.value}%</div>
</div>
</div>
</div>
);
})}
</Col>
</Row>
<Row className="sec_sp">
<Col lg="5">
<h3 className="color_sec py-4">Education</h3>
</Col>
<Col lg="7">
<div>
<p>
<strong>University of Virginia, Charlottesville, VA</strong>
<br />
Bachelor of Arts, Computer Science
<br />
Expected May 2026
<br />
Cumulative GPA: 3.55/4.0
<br />
Relevant Coursework:
<br />
Data structures and algo, Computer systems and organization,
Software dev essentials
</p>
</div>
</Col>
</Row>
</Container>
</HelmetProvider>
);
};

100
src/pages/about/style.css Normal file
View file

@ -0,0 +1,100 @@
.sec_sp {
margin-bottom: calc(3rem + 5.128vw)
}
.table td,
.table th {
color: var(--text-color);
}
.t_border {
border-color: var(--text-color) !important;
}
.progress-title {
font-size: 16px;
font-weight: 700;
margin: 15px 0 20px;
font-family: 'Raleway';
}
.progress {
height: 5px;
background: var(--secondary);
border-radius: 0;
box-shadow: none;
margin-bottom: 30px;
overflow: visible;
}
.progress .progress-bar {
position: relative;
background: var(--text-color);
animation: animate-positive 2s;
overflow: visible;
opacity: 0.9;
}
.progress .progress-value {
position: absolute;
top: -30px;
right: 8px;
font-size: 17px;
font-weight: bold;
font-style: italic;
color: var(--text-color);
}
@-webkit-keyframes animate-positive {
0% {
width: 0%;
}
}
@keyframes animate-positive {
0% {
width: 0%;
}
}
.section-title {
font-size: 45px;
}
.service__title {
padding: 8px 0;
border-bottom: solid 2px var(--secondary-color);
}
.service-section .service-category-title {
padding-bottom: 4px;
}
/*! CSS Used keyframes */
@-webkit-keyframes fadeInUp {
0% {
opacity: 0;
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}
to {
opacity: 1;
-webkit-transform: translateZ(0);
transform: translateZ(0);
}
}
@keyframes fadeInUp {
0% {
opacity: 0;
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}
to {
opacity: 1;
-webkit-transform: translateZ(0);
transform: translateZ(0);
}
}

167
src/pages/contact/index.js Normal file
View file

@ -0,0 +1,167 @@
import React, { useState } from "react";
import * as emailjs from "emailjs-com";
import "./style.css";
import { Helmet, HelmetProvider } from "react-helmet-async";
import { meta } from "../../content_option";
import { Container, Row, Col, Alert } from "react-bootstrap";
import { contactConfig } from "../../content_option";
export const ContactUs = () => {
const [formData, setFormdata] = useState({
email: "",
name: "",
message: "",
loading: false,
show: false,
alertmessage: "",
variant: "",
});
const handleSubmit = (e) => {
e.preventDefault();
setFormdata({ loading: true });
const templateParams = {
from_name: formData.email,
user_name: formData.name,
to_name: contactConfig.YOUR_EMAIL,
message: formData.message,
};
emailjs
.send(
contactConfig.YOUR_SERVICE_ID,
contactConfig.YOUR_TEMPLATE_ID,
templateParams,
contactConfig.YOUR_USER_ID
)
.then(
(result) => {
console.log(result.text);
setFormdata({
loading: false,
alertmessage: "SUCCESS! ,Thankyou for your messege",
variant: "success",
show: true,
});
},
(error) => {
console.log(error.text);
setFormdata({
alertmessage: `Faild to send!,${error.text}`,
variant: "danger",
show: true,
});
document.getElementsByClassName("co_alert")[0].scrollIntoView();
}
);
};
const handleChange = (e) => {
setFormdata({
...formData,
[e.target.name]: e.target.value,
});
};
return (
<HelmetProvider>
<Container>
<Helmet>
<meta charSet="utf-8" />
<title>{meta.title} | Contact</title>
<meta name="description" content={meta.description} />
</Helmet>
<Row className="mb-5 mt-3 pt-md-3">
<Col lg="8">
<h1 className="display-4 mb-4">Contact Me</h1>
<hr className="t_border my-4 ml-0 text-left" />
</Col>
</Row>
<Row className="sec_sp">
<Col lg="12">
<Alert
//show={formData.show}
variant={formData.variant}
className={`rounded-0 co_alert ${
formData.show ? "d-block" : "d-none"
}`}
onClose={() => setFormdata({ show: false })}
dismissible
>
<p className="my-0">{formData.alertmessage}</p>
</Alert>
</Col>
<Col lg="5" className="mb-5">
<h3 className="color_sec py-4">Get in touch</h3>
<address>
<strong>Email:</strong>{" "}
<a href={`mailto:${contactConfig.YOUR_EMAIL}`}>
{contactConfig.YOUR_EMAIL}
</a>
<br />
<br />
{contactConfig.hasOwnProperty("YOUR_FONE") ? (
<p>
<strong>Phone:</strong> {contactConfig.YOUR_FONE}
</p>
) : (
""
)}
</address>
<p>{contactConfig.description}</p>
</Col>
<Col lg="7" className="d-flex align-items-center">
<form onSubmit={handleSubmit} className="contact__form w-100">
<Row>
<Col lg="6" className="form-group">
<input
className="form-control"
id="name"
name="name"
placeholder="Name"
value={formData.name || ""}
type="text"
required
onChange={handleChange}
/>
</Col>
<Col lg="6" className="form-group">
<input
className="form-control rounded-0"
id="email"
name="email"
placeholder="Email"
type="email"
value={formData.email || ""}
required
onChange={handleChange}
/>
</Col>
</Row>
<textarea
className="form-control rounded-0"
id="message"
name="message"
placeholder="Message"
rows="5"
value={formData.message}
onChange={handleChange}
required
></textarea>
<br />
<Row>
<Col lg="12" className="form-group">
<button className="btn ac_btn" type="submit">
{formData.loading ? "Sending..." : "Send"}
</button>
</Col>
</Row>
</form>
</Col>
</Row>
</Container>
<div className={formData.loading ? "loading-bar" : "d-none"}></div>
</HelmetProvider>
);
};

View file

@ -0,0 +1,45 @@
.contact__form .form-control {
padding: 1.375rem .75rem;
line-height: 1.5;
color: var(--text-color);
background-color: var(--bg-color);
border-radius: 0 !important;
border: 1px solid var(--secondary-color);
}
.contact__form input.form-control {
margin-bottom: 2em;
height: calc(2.5em + .75rem + 2px);
}
button.btn.ac_btn:hover {
color: var(--secondary-color);
}
.loading-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 10px;
z-index: 999999999;
background: var(--text-color);
transform: translateX(100%);
animation: shift-rightwards 1s ease-in-out infinite;
animation-delay: .3s;
}
@keyframes shift-rightwards {
0% {
transform: translateX(-100%);
}
40% {
transform: translateX(0%);
}
60% {
transform: translateX(0%);
}
100% {
transform: translateX(100%);
}
}

66
src/pages/home/index.js Normal file
View file

@ -0,0 +1,66 @@
import React from "react";
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";
export const Home = () => {
return (
<HelmetProvider>
<section id="home" className="home">
<Helmet>
<meta charSet="utf-8" />
<title> {meta.title}</title>
<meta name="description" content={meta.description} />
</Helmet>
<div className="intro_sec d-block d-lg-flex align-items-center ">
<div
className="h_bg-image order-1 order-lg-2 h-100 "
style={{ backgroundImage: `url(/assets/images/Image%20with%20Background%20Removed.png)` }}
></div>
<div className="text order-2 order-lg-1 h-100 d-lg-flex justify-content-center">
<div className="align-self-center ">
<div className="intro mx-auto">
<h2 className="mb-1x">{introdata.title}</h2>
<h1 className="fluidz-48 mb-1x">
<Typewriter
options={{
strings: [
introdata.animated.first,
introdata.animated.second,
introdata.animated.third,
],
autoStart: true,
loop: true,
deleteSpeed: 10,
}}
/>
</h1>
<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>
</div>
</div>
</div>
</div>
</section>
</HelmetProvider>
);
};

208
src/pages/home/style.css Normal file
View file

@ -0,0 +1,208 @@
section {
flex: 1 0 auto;
position: relative;
width: 100%;
-webkit-transition: all 0.5s ease-in;
-o-transition: all 0.5s ease-in;
transition: all 0.5s ease-in;
}
.who_am_I {
font-family: Cinzel;
}
.has-first-color {
color: var(--primary-color);
}
.btn-portfolio {
background: var(--primary-color);
border-radius: 0;
}
.btn-portfolio a {
color: #000;
text-decoration: none;
}
.btn-about a {
color: var(--text-color);
text-decoration: none;
}
.intro_sec {
height: calc(100vh - 60px);
min-height: 700px;
height: 100vh;
margin-top: -60px;
}
@media (max-width: 991.98px) {
.intro_sec {
display: block;
height: auto !important;
}
}
.intro_sec .text,
.intro_sec .h_bg-image {
width: 50%;
}
@media (max-width: 991.98px) {
.intro_sec .text,
.intro_sec .h_bg-image {
width: 100%;
}
}
.intro_sec .intro {
max-width: 450px;
margin: 0 auto;
}
@media (max-width: 991.98px) {
.intro_sec .intro {
max-width: 700px;
padding-left: 20px;
padding-right: 20px;
}
}
.intro_sec .intro .feature .wrap-icon {
background: 0 0 !important;
width: auto;
height: auto;
margin-bottom: 0;
}
.intro_sec .intro .feature .wrap-icon svg {
color: #5cccc9;
}
.intro_sec .text h1 {
font-size: 30px;
margin-bottom: 50px;
font-weight: 700;
}
.intro_sec .text h3 {
font-size: 16px;
font-weight: 700;
}
.intro_sec .h_bg-image {
background-size: cover;
background-position: center 30%;
min-height: 700px;
position: relative;
max-width: 600px;
max-height: 600px;
border-radius: 20px;
overflow: hidden;
margin: 0 auto;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.ac_btn {
padding: 4px 19px;
color: var(--secondary-color);
position: relative;
border: var(--secondary-color) 2px solid;
overflow: hidden;
transition: all 0.6s cubic-bezier(0.55, 0, 0.1, 1);
cursor: pointer;
border-radius: 0;
margin-right: 20px;
}
.ac_btn a {
text-decoration: none;
}
.ac_btn:hover {
box-shadow: 8px 8px 0px var(--text-color), -8px -8px 0px var(--text-color);
}
.ac_btn:hover .one {
opacity: 1;
transform: translate3d(0px, 0px, 0px);
}
.ac_btn:hover .two {
transform: translate3d(0px, 0px, 0px);
}
.ac_btn:hover .three {
transform: translate3d(0px, 0px, 0px);
}
.ac_btn:hover .four {
transform: translate3d(0px, 0px, 0px);
}
.ac_btn .ring {
width: 100%;
height: 100%;
position: absolute;
background: transparent;
top: 0;
left: 0;
transform: translate3d(0px, 90px, 0px);
}
.ac_btn .one {
background-color: #000;
transition: all 0.3s cubic-bezier(0.55, 0, 0.1, 1);
z-index: -3;
z-index: -4;
}
.ac_btn .two {
background-color: var(--primary-color);
transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
z-index: -3;
}
.ac_btn .three {
background-color: var(--secondary-color);
z-index: -2;
transition: all 0.7s cubic-bezier(0.55, 0, 0.1, 1);
z-index: -3;
}
#button_p {
background: var(--secondary-color);
color: var(--primary-color);
}
#button_h:hover {
color: var(--primary-color);
}
.intro_sec .h_bg-image .resume-icon {
position: absolute;
bottom: 20px;
left: 20px;
width: 50px;
height: 50px;
color: var(--text-color);
cursor: pointer;
transition: transform 0.2s ease;
}
.intro_sec .h_bg-image .resume-icon:hover {
transform: scale(1.1);
}
@media (max-width: 991.98px) {
.intro_sec .h_bg-image .resume-icon {
bottom: 10px;
left: 50%;
transform: translateX(-50%);
}
}
.intro_sec .h_bg-image {
filter: saturate(0.5);
}

View file

@ -0,0 +1,38 @@
import React from "react";
import "./style.css";
import { Helmet, HelmetProvider } from "react-helmet-async";
import { Container, Row, Col } from "react-bootstrap";
import { dataportfolio, meta } from "../../content_option";
export const Portfolio = () => {
return (
<HelmetProvider>
<Container className="About-header">
<Helmet>
<meta charSet="utf-8" />
<title> Projects | {meta.title} </title>
<meta name="description" content={meta.description} />
</Helmet>
<Row className="mb-5 mt-3 pt-md-3">
<Col lg="8">
<h1 className="display-4 mb-4"> Projects </h1>
<hr className="t_border my-4 ml-0 text-left" />
</Col>
</Row>
<div className="mb-5 project_items_ho">
{dataportfolio.map((data, i) => {
return (
<div key={i} className="project_item">
<img src={data.img} alt="" />
<div className="content">
<p>{data.description}</p>
<a href={data.link} target="_blank" rel="noopener noreferrer">View Project</a>
</div>
</div>
);
})}
</div>
</Container>
</HelmetProvider>
);
};

View file

@ -0,0 +1,82 @@
.project_items_ho {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 2rem;
}
.project_item {
width: calc(33% - 2rem);
text-align: center;
margin: 0.5rem;
position: relative;
background: var(--secondary-color);
padding: 6px;
border: 1px solid var(--secondary-color);
transition: 0.3s ease;
font-size: 0;
min-height: 300px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
@media (max-width: 768px) {
.project_item {
width: calc(50% - 2rem);
}
}
@media (max-width: 576px) {
.project_item {
width: calc(100% - 2rem);
}
}
.project_item img {
max-width: 100%;
max-height: 200px;
object-fit: cover;
}
.project_item .content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: var(--overlay-color);
opacity: 0;
transition: opacity 0.3s ease-in-out;
padding: 10px;
box-sizing: border-box;
}
.project_item:hover .content {
opacity: 1;
}
.project_item .content p {
color: var(--text-color);
font-size: 1rem;
margin-bottom: 10px;
}
.project_item .content a {
background: var(--bg-color);
border: solid 1px var(--text-color);
padding: 4px 8px;
text-align: center;
font-size: 1rem;
color: var(--text-color);
text-decoration: none;
}
.project_item .content a:hover {
text-decoration: none;
}

13
src/reportWebVitals.js Normal file
View file

@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

9331
yarn.lock Normal file

File diff suppressed because it is too large Load diff