-
Notifications
You must be signed in to change notification settings - Fork 0
Add 3D hero orb and scroll-driven, scroll-aware backdrop #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,6 @@ | ||||||
| // src/App.js | ||||||
|
|
||||||
| import React from 'react'; | ||||||
| import React, { useEffect } from 'react'; | ||||||
| import GlobalStyles from './styles/GlobalStyles'; | ||||||
| import Header from './Components/Header'; | ||||||
| import AboutMe from './Components/AboutMe'; | ||||||
|
|
@@ -12,18 +12,63 @@ import Contact from './Components/Contact'; | |||||
| import Footer from './Components/Footer'; | ||||||
| import GithubRepos from './Components/GithubRepos'; | ||||||
| import ProjectsWithGithub from './Components/ProjectsWithGithub'; | ||||||
| import ScrollReveal from './Components/ScrollReveal'; | ||||||
|
|
||||||
| function App() { | ||||||
| useEffect(() => { | ||||||
| let ticking = false; | ||||||
|
|
||||||
| const updateScrollProgress = () => { | ||||||
| const maxScroll = | ||||||
| document.documentElement.scrollHeight - window.innerHeight; | ||||||
| const progress = maxScroll > 0 ? window.scrollY / maxScroll : 0; | ||||||
| document.documentElement.style.setProperty( | ||||||
| '--scroll-progress', | ||||||
| progress.toFixed(4) | ||||||
| ); | ||||||
| }; | ||||||
|
|
||||||
| const handleScroll = () => { | ||||||
| if (ticking) return; | ||||||
| ticking = true; | ||||||
| window.requestAnimationFrame(() => { | ||||||
| updateScrollProgress(); | ||||||
| ticking = false; | ||||||
| }); | ||||||
| }; | ||||||
|
|
||||||
| updateScrollProgress(); | ||||||
| window.addEventListener('scroll', handleScroll, { passive: true }); | ||||||
| window.addEventListener('resize', handleScroll); | ||||||
|
||||||
| window.addEventListener('resize', handleScroll); | |
| window.addEventListener('resize', handleScroll, { passive: true }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,90 @@ const HeaderSection = styled.section` | |
| padding: 3rem; | ||
| background-color: #f8f8f8; | ||
| min-height: 80vh; | ||
| position: relative; | ||
| overflow: hidden; | ||
| `; | ||
|
|
||
| const HeroScene = styled.div` | ||
| position: absolute; | ||
| inset: 0; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| pointer-events: none; | ||
| perspective: 1200px; | ||
| z-index: 0; | ||
| transform: translateY(calc(var(--scroll-progress) * 30px)); | ||
| `; | ||
|
|
||
| const Orb = styled.div` | ||
| position: relative; | ||
| width: 220px; | ||
| height: 220px; | ||
| transform-style: preserve-3d; | ||
| animation: orbSpin 14s linear infinite; | ||
| opacity: 0.65; | ||
|
|
||
| &::before { | ||
| content: ''; | ||
| position: absolute; | ||
| inset: 25px; | ||
| border-radius: 50%; | ||
| background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.9), rgba(255, 111, 97, 0.25)); | ||
| box-shadow: 0 0 40px rgba(255, 111, 97, 0.2); | ||
| transform: translateZ(20px); | ||
| } | ||
|
|
||
| &::after { | ||
| content: ''; | ||
| position: absolute; | ||
| inset: 50px; | ||
| border-radius: 50%; | ||
| border: 1px solid rgba(255, 111, 97, 0.35); | ||
| transform: translateZ(-20px); | ||
| } | ||
|
|
||
| @keyframes orbSpin { | ||
| from { | ||
| transform: rotateX(18deg) rotateY(0deg); | ||
| } | ||
| to { | ||
| transform: rotateX(18deg) rotateY(360deg); | ||
| } | ||
| } | ||
|
Comment on lines
+58
to
+65
|
||
| `; | ||
|
|
||
| const Ring = styled.span` | ||
| position: absolute; | ||
| inset: 25px; | ||
| border-radius: 50%; | ||
| border: 1px solid rgba(51, 51, 51, 0.15); | ||
| transform-style: preserve-3d; | ||
| animation: ringFloat 7s ease-in-out infinite; | ||
|
|
||
| &:nth-child(1) { | ||
| transform: rotateX(65deg) rotateZ(20deg) translateZ(25px); | ||
| } | ||
|
|
||
| &:nth-child(2) { | ||
| transform: rotateY(75deg) rotateZ(-15deg) translateZ(-10px); | ||
| animation-delay: 1.5s; | ||
| } | ||
|
|
||
| &:nth-child(3) { | ||
| transform: rotateX(80deg) rotateZ(80deg) translateZ(10px); | ||
| animation-delay: 3s; | ||
| } | ||
|
Comment on lines
+76
to
+88
|
||
|
|
||
| @keyframes ringFloat { | ||
| 0%, | ||
| 100% { | ||
| filter: drop-shadow(0 0 8px rgba(255, 111, 97, 0.2)); | ||
| } | ||
| 50% { | ||
| filter: drop-shadow(0 0 18px rgba(73, 135, 255, 0.3)); | ||
| } | ||
| } | ||
|
Comment on lines
+90
to
+98
|
||
| `; | ||
|
|
||
| const ProfileImage = styled.img` | ||
|
|
@@ -21,13 +105,17 @@ const ProfileImage = styled.img` | |
| object-fit: cover; | ||
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); | ||
| margin-bottom: 1.5rem; | ||
| position: relative; | ||
| z-index: 1; | ||
| `; | ||
|
|
||
| const Name = styled.h1` | ||
| font-size: 2.5rem; | ||
| font-weight: bold; | ||
| color: #333; | ||
| margin-bottom: 1rem; | ||
| position: relative; | ||
| z-index: 1; | ||
| `; | ||
|
|
||
| const Description = styled.p` | ||
|
|
@@ -36,12 +124,16 @@ const Description = styled.p` | |
| line-height: 1.6; | ||
| max-width: 600px; | ||
| margin-bottom: 2rem; | ||
| position: relative; | ||
| z-index: 1; | ||
| `; | ||
|
|
||
| const ButtonContainer = styled.div` | ||
| display: flex; | ||
| gap: 1rem; | ||
| margin-bottom: 1.5rem; | ||
| position: relative; | ||
| z-index: 1; | ||
|
|
||
| a { | ||
| text-decoration: none; | ||
|
|
@@ -75,6 +167,8 @@ const ConnectSection = styled.div` | |
| display: flex; | ||
| gap: 1rem; | ||
| justify-content: center; | ||
| position: relative; | ||
| z-index: 1; | ||
|
|
||
| a { | ||
| color: #ff6f61; | ||
|
|
@@ -90,6 +184,13 @@ const ConnectSection = styled.div` | |
| const Header = () => { | ||
| return ( | ||
| <HeaderSection> | ||
| <HeroScene> | ||
| <Orb> | ||
| <Ring /> | ||
| <Ring /> | ||
| <Ring /> | ||
| </Orb> | ||
| </HeroScene> | ||
|
Comment on lines
+187
to
+193
|
||
| <ProfileImage src={profilePic} alt="Yasar Nazzarian" /> | ||
| <Name>Yasar Nazzarian</Name> | ||
| <Description> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import React from 'react'; | ||
| import { motion } from 'framer-motion'; | ||
|
|
||
| const ScrollReveal = ({ children, delay = 0 }) => { | ||
| return ( | ||
| <motion.div | ||
| initial={{ opacity: 0, y: 24, scale: 0.98 }} | ||
| whileInView={{ opacity: 1, y: 0, scale: 1 }} | ||
| viewport={{ once: true, amount: 0.25 }} | ||
| transition={{ | ||
| duration: 0.6, | ||
| ease: [0.22, 1, 0.36, 1], | ||
| delay, | ||
| }} | ||
| > | ||
| {children} | ||
| </motion.div> | ||
| ); | ||
| }; | ||
|
|
||
| export default ScrollReveal; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,11 +3,63 @@ | |||||||||||||||||||
| import { createGlobalStyle } from 'styled-components'; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const GlobalStyles = createGlobalStyle` | ||||||||||||||||||||
| :root { | ||||||||||||||||||||
| --scroll-progress: 0; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| body { | ||||||||||||||||||||
| margin: 0; | ||||||||||||||||||||
| padding: 0; | ||||||||||||||||||||
| box-sizing: border-box; | ||||||||||||||||||||
| font-family: 'Arial', sans-serif; | ||||||||||||||||||||
| background-color: #f8f8f8; | ||||||||||||||||||||
| color: #222; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| body::before, | ||||||||||||||||||||
| body::after { | ||||||||||||||||||||
| content: ''; | ||||||||||||||||||||
| position: fixed; | ||||||||||||||||||||
| inset: -20%; | ||||||||||||||||||||
| pointer-events: none; | ||||||||||||||||||||
| z-index: -1; | ||||||||||||||||||||
| transition: transform 0.25s ease-out; | ||||||||||||||||||||
|
||||||||||||||||||||
| transition: transform 0.25s ease-out; | |
| will-change: transform; |
Copilot
AI
Dec 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The blur filter is set to 0px which has no effect. Either remove this line if no blur is intended, or set it to a non-zero value if a blur effect is desired.
| filter: blur(0px); |
Copilot
AI
Dec 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Users with vestibular motion disorders may experience discomfort from the scroll-driven parallax effects on the backdrop pseudo-elements. Consider adding a prefers-reduced-motion media query to disable the transform animations for users who have requested reduced motion in their system settings.
| @media (prefers-reduced-motion: reduce) { | |
| body::before, | |
| body::after { | |
| transition: none; | |
| transform: none; | |
| } | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file comment says "src/App.js" but the file is named "App.jsx". Update the comment to match the actual filename.