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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// src/App.js

import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import GlobalStyles from './styles/GlobalStyles';
import Header from './Components/Header';
import AboutMe from './Components/AboutMe';
Expand All @@ -13,8 +13,31 @@ import Footer from './Components/Footer';
import GithubRepos from './Components/GithubRepos';
import ProjectsWithGithub from './Components/ProjectsWithGithub';
import ScrollReveal from './Components/ScrollReveal';
import DarkModeToggle from './Components/DarkModeToggle';
import BackToTop from './Components/BackToTop';

function App() {
const [isDark, setIsDark] = useState(() => {
try {
return localStorage.getItem('darkMode') === 'true';
} catch {
return false;
}
});

useEffect(() => {
if (isDark) {
document.body.classList.add('dark-mode');
} else {
document.body.classList.remove('dark-mode');
}
try {
localStorage.setItem('darkMode', isDark);
} catch {
// ignore storage errors
}
}, [isDark]);

useEffect(() => {
let ticking = false;

Expand Down Expand Up @@ -50,8 +73,12 @@ function App() {
return (
<>
<GlobalStyles />
<DarkModeToggle isDark={isDark} onToggle={() => setIsDark(d => !d)} />
<Header />
<ScrollReveal>
<AboutMe />
</ScrollReveal>
<ScrollReveal delay={0.05}>
<Skills />
</ScrollReveal>
<ScrollReveal delay={0.1}>
Expand All @@ -69,6 +96,7 @@ function App() {
<ScrollReveal delay={0.3}>
<Footer />
</ScrollReveal>
<BackToTop />
</>
);
}
Expand Down
5 changes: 2 additions & 3 deletions src/Components/AboutMe.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ import styled from 'styled-components';

const AboutSection = styled.section`
padding: 4rem 0;
background-color: #f9f9f9;
color: #333;
background-color: var(--section-bg, #f9f9f9);
color: var(--text-color, #333);
text-align: center;
margin-top: 80px; // Leave space for header
`;

const AboutContent = styled.div`
Expand Down
59 changes: 59 additions & 0 deletions src/Components/BackToTop.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { FaArrowUp } from 'react-icons/fa';

const BackToTopButton = styled.button`
position: fixed;
bottom: 2rem;
right: 1.5rem;
z-index: 999;
background: #ff6f61;
color: #fff;
border: none;
border-radius: 50%;
width: 48px;
height: 48px;
font-size: 1.2rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
opacity: ${({ $visible }) => ($visible ? 1 : 0)};
pointer-events: ${({ $visible }) => ($visible ? 'auto' : 'none')};
transform: translateY(${({ $visible }) => ($visible ? '0' : '20px')});
transition: opacity 0.3s, transform 0.3s, background 0.2s;

&:hover {
background: #e65a51;
}
`;

const BackToTop = () => {
const [visible, setVisible] = useState(false);

useEffect(() => {
const handleScroll = () => {
setVisible(window.scrollY > 300);
};

window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, []);

const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};

return (
<BackToTopButton
$visible={visible}
onClick={scrollToTop}
aria-label="Back to top"
>
<FaArrowUp />
</BackToTopButton>
);
};

export default BackToTop;
36 changes: 36 additions & 0 deletions src/Components/DarkModeToggle.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import styled from 'styled-components';
import { FaSun, FaMoon } from 'react-icons/fa';

const ToggleButton = styled.button`
position: fixed;
top: 1.2rem;
right: 1.5rem;
z-index: 1000;
background: var(--toggle-bg, #333);
color: var(--toggle-color, #fff);
border: none;
border-radius: 50px;
padding: 0.5rem 1rem;
font-size: 1.1rem;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.4rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
transition: background 0.3s, color 0.3s, box-shadow 0.3s;

&:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}
`;

const DarkModeToggle = ({ isDark, onToggle }) => {
return (
<ToggleButton onClick={onToggle} aria-label="Toggle dark mode">
{isDark ? <FaSun /> : <FaMoon />}
</ToggleButton>
);
};

export default DarkModeToggle;
69 changes: 60 additions & 9 deletions src/Components/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,50 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { FaGithub, FaLinkedin, FaEnvelope } from 'react-icons/fa';
import profilePic from '../images/profile-pic.png';

const TYPING_PHRASES = [
'AI Integration & Cloud Computing',
'Full-Stack Software Developer',
'Building Impactful Solutions',
];

const useTypewriter = (phrases, typingSpeed = 80, deletingSpeed = 40, pauseMs = 1800) => {
const [text, setText] = useState('');
const [phraseIndex, setPhraseIndex] = useState(0);
const [isDeleting, setIsDeleting] = useState(false);

useEffect(() => {
const current = phrases[phraseIndex % phrases.length];
let timeout;

if (!isDeleting && text === current) {
timeout = setTimeout(() => setIsDeleting(true), pauseMs);
} else if (isDeleting && text === '') {
setIsDeleting(false);
setPhraseIndex(i => i + 1);
} else {
timeout = setTimeout(() => {
setText(prev =>
isDeleting ? prev.slice(0, -1) : current.slice(0, prev.length + 1)
);
}, isDeleting ? deletingSpeed : typingSpeed);
}

return () => clearTimeout(timeout);
}, [text, isDeleting, phraseIndex, phrases, typingSpeed, deletingSpeed, pauseMs]);

return text;
};

const HeaderSection = styled.section`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 3rem;
background-color: #f8f8f8;
background-color: var(--bg-color, #f8f8f8);
min-height: 80vh;
position: relative;
overflow: hidden;
Expand Down Expand Up @@ -112,20 +146,36 @@ const ProfileImage = styled.img`
const Name = styled.h1`
font-size: 2.5rem;
font-weight: bold;
color: #333;
color: var(--text-color, #333);
margin-bottom: 1rem;
position: relative;
z-index: 1;
`;

const Description = styled.p`
const TypingText = styled.p`
font-size: 1.2rem;
color: #555;
color: var(--subtext-color, #555);
line-height: 1.6;
max-width: 600px;
margin-bottom: 2rem;
position: relative;
z-index: 1;
min-height: 2em;
`;

const Cursor = styled.span`
display: inline-block;
width: 2px;
height: 1.1em;
background: #ff6f61;
margin-left: 2px;
vertical-align: text-bottom;
animation: blink 0.8s step-end infinite;

@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
`;

const ButtonContainer = styled.div`
Expand Down Expand Up @@ -182,6 +232,8 @@ const ConnectSection = styled.div`
`;

const Header = () => {
const typedText = useTypewriter(TYPING_PHRASES);

return (
<HeaderSection>
<HeroScene>
Expand All @@ -193,10 +245,9 @@ const Header = () => {
</HeroScene>
<ProfileImage src={profilePic} alt="Yasar Nazzarian" />
<Name>Yasar Nazzarian</Name>
<Description>
I'm a passionate software Developer with experience in AI integration,
cloud computing, and software development. Let's create impactful solutions together.
</Description>
<TypingText>
{typedText}<Cursor />
</TypingText>
<ButtonContainer>
<a href="#projects">
<button>View Projects</button>
Expand Down
Loading