diff --git a/project6.jpg b/project6.jpg new file mode 100644 index 0000000..f30cb15 Binary files /dev/null and b/project6.jpg differ diff --git a/public/images/project7.jpg b/public/images/project7.jpg new file mode 100644 index 0000000..3d96d3e Binary files /dev/null and b/public/images/project7.jpg differ diff --git a/src/app/App.css b/src/app/App.css index cc24160..c375c94 100644 --- a/src/app/App.css +++ b/src/app/App.css @@ -15,13 +15,49 @@ box-sizing: border-box; } -html, body { +html, +body { overflow: hidden; position: fixed; height: 100%; width: 100%; } +/* Mobile devices override for normal scrolling */ +@media (max-width: 768px) { + html, + body { + overflow: auto; + position: static; + height: auto; + } + + .fullpage-container { + position: static !important; + height: auto !important; + overflow: visible !important; + } + + .fullpage-sections { + transform: none !important; + transition: none !important; + } + + .fullpage-section { + height: auto !important; + min-height: 100vh; + } + + .fullpage-section-content { + overflow: visible !important; + height: auto !important; + } + + .fullpage-pagination { + display: none !important; + } +} + /* Full page styles */ .app-container { position: relative; @@ -52,8 +88,8 @@ html, body { height: 100%; overflow-y: auto; overflow-x: hidden; - -ms-overflow-style: none; /* Hide scrollbar IE and Edge */ - scrollbar-width: none; /* Hide scrollbar Firefox */ + -ms-overflow-style: none; /* Hide scrollbar IE and Edge */ + scrollbar-width: none; /* Hide scrollbar Firefox */ } .fullpage-section-content::-webkit-scrollbar { @@ -111,22 +147,6 @@ html, body { } /* 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, @@ -134,6 +154,6 @@ html, body { .container-sm, .container-xl, .container-xxl { - max-width: 1140px; + max-width: 1140px; } } diff --git a/src/app/App.js b/src/app/App.js index 6700326..73557ee 100644 --- a/src/app/App.js +++ b/src/app/App.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import "bootstrap/dist/css/bootstrap.min.css"; import AnimatedCursor from "../hooks/AnimatedCursor"; import Headermain from "../header"; @@ -11,6 +11,59 @@ import Section from "../components/Section"; import "./App.css"; export default function App() { + const [isMobile, setIsMobile] = useState(false); + + // Check if device is mobile + useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth <= 768); + }; + + // Initial check + checkMobile(); + + // Listen for resize events + window.addEventListener('resize', checkMobile); + + return () => { + window.removeEventListener('resize', checkMobile); + }; + }, []); + + // Traditional multi-page style for mobile + if (isMobile) { + return ( +
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ); + } + + // Desktop version with smooth scrolling return (
@@ -23,7 +76,7 @@ export default function App() { outerScale={5} />
- +
diff --git a/src/components/FullPage.js b/src/components/FullPage.js index e99524a..2514cd1 100644 --- a/src/components/FullPage.js +++ b/src/components/FullPage.js @@ -4,43 +4,64 @@ import { motion } from 'framer-motion'; const FullPage = ({ children }) => { const [currentPage, setCurrentPage] = useState(0); const [isScrolling, setIsScrolling] = useState(false); + const [isMobile, setIsMobile] = useState(false); const containerRef = useRef(null); const pagesCount = React.Children.count(children); - + + // Check if device is mobile + useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth <= 768); + }; + + // Initial check + checkMobile(); + + // Listen for resize events + window.addEventListener('resize', checkMobile); + + return () => { + window.removeEventListener('resize', checkMobile); + }; + }, []); + // Handle page change const goToPage = (pageIndex) => { + if (isMobile) return; // Disable on mobile + 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 (isMobile) return; // Disable on mobile 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); @@ -51,34 +72,39 @@ const FullPage = ({ children }) => { } } }; - + // Handle key events const handleKeyDown = (e) => { + if (isMobile) return; // Disable on mobile 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(() => { + if (isMobile) return; // Disable on mobile + 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]); + }, [currentPage, isScrolling, isMobile]); // Check for hash in URL on initial load useEffect(() => { + if (isMobile) return; // Disable on mobile + const hash = window.location.hash.substring(1); if (hash) { const childrenArray = React.Children.toArray(children); @@ -87,22 +113,44 @@ const FullPage = ({ children }) => { setCurrentPage(initialPageIndex); } } - }, []); - + }, [isMobile]); + // Listen for navigation events from header useEffect(() => { + if (isMobile) return; // Disable on mobile + const handleNavigation = (e) => { const { index } = e.detail; goToPage(index); }; - + document.addEventListener('navigateTo', handleNavigation); - + return () => { document.removeEventListener('navigateTo', handleNavigation); }; - }, []); - + }, [isMobile]); + + // For mobile, render a simple wrapper without scroll snap + if (isMobile) { + return ( +
+
+ {React.Children.map(children, (child, index) => ( +
+ {child} +
+ ))} +
+
+ ); + } + + // Desktop version with custom scrolling return (
{ // Add a class to the current page to allow scrolling only on active page const isCurrentPage = index === currentPage; return ( -
@@ -132,7 +180,7 @@ const FullPage = ({ children }) => { ); })}
- + {/* Pagination Dots */}
{Array.from({ length: pagesCount }).map((_, index) => ( @@ -152,4 +200,4 @@ const FullPage = ({ children }) => { ); }; -export default FullPage; \ No newline at end of file +export default FullPage; diff --git a/src/components/Section.js b/src/components/Section.js index 484e350..334f798 100644 --- a/src/components/Section.js +++ b/src/components/Section.js @@ -1,7 +1,35 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { motion } from 'framer-motion'; const Section = ({ children, id, className = '' }) => { + const [isMobile, setIsMobile] = useState(false); + + // Check if device is mobile + useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth <= 768); + }; + + // Initial check + checkMobile(); + + // Listen for resize events + window.addEventListener('resize', checkMobile); + + return () => { + window.removeEventListener('resize', checkMobile); + }; + }, []); + + // Mobile-specific styles + const mobileStyles = isMobile ? { + height: 'auto', + minHeight: '100vh', + paddingTop: '60px', + paddingBottom: '30px', + overflowY: 'visible', + } : {}; + return ( { alignItems: 'flex-start', justifyContent: 'center', position: 'relative', - padding: '0 20px' + padding: '0 20px', + ...mobileStyles }} >
@@ -27,4 +56,4 @@ const Section = ({ children, id, className = '' }) => { ); }; -export default Section; \ No newline at end of file +export default Section; diff --git a/src/components/themetoggle/index.js b/src/components/themetoggle/index.js index 53718c2..d2ca8b1 100644 --- a/src/components/themetoggle/index.js +++ b/src/components/themetoggle/index.js @@ -1,18 +1,29 @@ import React, { useEffect, useState } from "react"; import { RiSunLine, RiMoonClearLine } from "react-icons/ri"; +import "./style.css"; const Themetoggle = () => { const [theme, settheme] = useState(localStorage.getItem("theme") || "dark"); + const [isHovering, setIsHovering] = useState(false); + const themetoggle = () => { settheme(theme === "dark" ? "light" : "dark"); }; + useEffect(() => { document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('theme', theme); }, [theme]); + return ( -
- {theme === "dark" ? : } +
setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + > +
+ {theme === "dark" ? : } +
); }; diff --git a/src/components/themetoggle/style.css b/src/components/themetoggle/style.css index 54da820..16d9836 100644 --- a/src/components/themetoggle/style.css +++ b/src/components/themetoggle/style.css @@ -20,3 +20,66 @@ .theme_toggler:hover svg { color: var(--text-color-3); } + +.theme__button { + color: var(--text-color); + padding: 10px; + background: transparent; + border: none; + position: relative; + overflow: visible; + cursor: pointer; + transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); + z-index: 1001; +} + +.theme__button:hover { + color: var(--text-color-3); + transform: scale(1.05); +} + +.theme-icon-wrapper { + position: relative; + width: 2em; + height: 2em; + transition: transform 0.4s cubic-bezier(0.68, -0.6, 0.32, 1.6); +} + +.theme-icon-wrapper svg { + width: 2em; + height: 2em; + fill: var(--text-color-2); + color: var(--text-color-2); + transition: all 0.4s cubic-bezier(0.68, -0.6, 0.32, 1.6); + filter: drop-shadow(0 0 0 transparent); +} + +.theme-icon-wrapper.hover svg { + transform: rotate(90deg) scale(1.1); + fill: var(--text-color-3); + color: var(--text-color-3); + filter: drop-shadow(0 0 5px var(--text-color-3)); +} + +/* Create a glowing effect behind the icon */ +.theme__button::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + background: radial-gradient(circle, var(--text-color-3) 0%, transparent 70%); + border-radius: 50%; + opacity: 0; + transform: translate(-50%, -50%); + transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); + z-index: -1; + pointer-events: none; +} + +.theme__button:hover::before { + width: 3em; + height: 3em; + opacity: 0.15; +} diff --git a/src/content_option.js b/src/content_option.js index 1b82090..2d330a7 100644 --- a/src/content_option.js +++ b/src/content_option.js @@ -126,6 +126,11 @@ const services = [{ ]; const dataportfolio = [{ + img: "/images/project7.jpg", + description: "AI-Powered Photo Editor, A modern SPA using Next.js, TypeScript, and Google Gemini 2.0 for real-time AI image transformations with an intuitive interface", + link: "https://ai-image-editor-three.vercel.app/", + }, + { 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", diff --git a/src/header/index.js b/src/header/index.js index 2620afe..881906b 100644 --- a/src/header/index.js +++ b/src/header/index.js @@ -1,15 +1,24 @@ -import React, { useState } from "react"; +import React, { useState, useRef, useEffect } from "react"; import "./style.css"; import { RiMenuLine, RiCloseLine } from "react-icons/ri"; +import { BsGrid3X3Gap } from "react-icons/bs"; import { logotext } from "../content_option"; import Themetoggle from "../components/themetoggle"; const Headermain = () => { const [isActive, setActive] = useState("false"); + const [isAnimating, setIsAnimating] = useState(false); + const [isHovering, setIsHovering] = useState(false); const handleToggle = () => { + setIsAnimating(true); setActive(!isActive); document.body.classList.toggle("ovhidden"); + + // Reset animation state after animation completes + setTimeout(() => { + setIsAnimating(false); + }, 600); }; const handleNavClick = (e, targetId) => { @@ -44,8 +53,23 @@ const Headermain = () => {
-
diff --git a/src/header/style.css b/src/header/style.css index 8ae84e4..ac75bc5 100644 --- a/src/header/style.css +++ b/src/header/style.css @@ -9,6 +9,11 @@ padding: 10px; background: transparent; border: none; + position: relative; + overflow: visible; + cursor: pointer; + transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); + z-index: 1001; } .menu__button:focus, @@ -16,6 +21,7 @@ color: var(--text-color-3); box-shadow: unset; background: transparent; + transform: scale(1.05); } .menu__button svg { @@ -23,12 +29,158 @@ height: 2em; fill: var(--text-color-2); color: var(--text-color-2); - transition: color 0.3s ease; + transition: all 0.4s cubic-bezier(0.68, -0.6, 0.32, 1.6); + filter: drop-shadow(0 0 0 transparent); } -.menu__button:hover svg { +/* Default hover for svg elements (for the close button) */ +.menu__button:hover > svg { color: var(--text-color-3); fill: var(--text-color-3); + transform: rotate(90deg) scale(1.1); + filter: drop-shadow(0 0 5px var(--text-color-3)); +} + +/* Icon container for transition effect */ +.icon-container { + position: relative; + width: 2em; + height: 2em; +} + +.icon-wrapper { + position: relative; + width: 100%; + height: 100%; + transition: transform 0.4s cubic-bezier(0.68, -0.6, 0.32, 1.6); +} + +.menu-icon, +.grid-icon { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + transition: opacity 0.3s ease, + transform 0.4s cubic-bezier(0.68, -0.6, 0.32, 1.6); +} + +.menu-icon { + opacity: 1; + transform: scale(1); +} + +.grid-icon { + opacity: 0; + transform: scale(0.8); +} + +/* Hover state for icon transition */ +.icon-wrapper.hover .menu-icon { + opacity: 0; + transform: scale(0.8) rotate(90deg); +} + +.icon-wrapper.hover .grid-icon { + opacity: 1; + transform: scale(1) rotate(0deg); + filter: drop-shadow(0 0 5px var(--text-color-3)); +} + +/* Create a glowing effect behind the icon */ +.menu__button::before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + background: radial-gradient(circle, var(--text-color-3) 0%, transparent 70%); + border-radius: 50%; + opacity: 0; + transform: translate(-50%, -50%); + transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); + z-index: -1; + pointer-events: none; +} + +.menu__button:hover::before { + width: 3em; + height: 3em; + opacity: 0.15; +} + +/* Animation when burger is clicked */ +.menu__button.animating svg, +.menu__button.animating .icon-wrapper { + animation: pulse 0.6s cubic-bezier(0.68, -0.6, 0.32, 1.6) forwards; +} + +@keyframes pulse { + 0% { + transform: scale(1); + filter: blur(0); + } + 50% { + transform: scale(1.2); + filter: blur(2px); + } + 100% { + transform: scale(1); + filter: blur(0); + } +} + +/* Different effects for open and closed states */ +.menu__button svg[data-state="closed"] { + transform-origin: center; +} + +.menu__button svg[data-state="open"] { + transform-origin: center; +} + +/* Hover effects for closed state (hamburger) */ +.menu__button:hover svg[data-state="closed"] { + transform: rotate(90deg) scale(1.1); + filter: drop-shadow(0 0 5px var(--text-color-3)); +} + +/* Hover effects for open state (X) */ +.menu__button:hover svg[data-state="open"] { + transform: rotate(-90deg) scale(1.1); + filter: drop-shadow(0 0 5px var(--text-color-3)); +} + +/* After-click effect */ +.menu__button.animating::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 100%; + height: 100%; + border-radius: 50%; + background: transparent; + border: 2px solid var(--text-color-3); + transform: translate(-50%, -50%) scale(0); + opacity: 1; + animation: ripple 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; + pointer-events: none; +} + +@keyframes ripple { + 0% { + transform: translate(-50%, -50%) scale(0); + opacity: 1; + border-width: 2px; + } + 100% { + transform: translate(-50%, -50%) scale(2); + opacity: 0; + border-width: 0; + } } .nav_ac { @@ -262,3 +414,7 @@ margin-right: 10px; text-decoration: none; } + +.d-flex.align-items-center { + gap: 5px; +} diff --git a/src/pages/portfolio/index.js b/src/pages/portfolio/index.js index 5102c72..41b0366 100644 --- a/src/pages/portfolio/index.js +++ b/src/pages/portfolio/index.js @@ -6,6 +6,9 @@ import { dataportfolio, meta } from "../../content_option"; import { FaExternalLinkAlt } from "react-icons/fa"; export const Portfolio = () => { + // Check if the device is mobile + const isMobile = window.innerWidth <= 768; + return ( @@ -22,17 +25,26 @@ export const Portfolio = () => {
{dataportfolio.map((data, i) => { - const title = data.description.split(",")[0]; + // Extract title and description without modifying for desktop + const fullTitle = data.description.split(",")[0]; const description = data.description.split(",").slice(1).join(",").trim(); + // Create shortened version only for mobile display + const words = fullTitle.split(" "); + const shortTitle = words.length > 3 + ? words.slice(0, 3).join(" ") + "..." + : fullTitle; + return (
- {title} + {fullTitle}
-

{title}

+

{fullTitle}

{description}

- - View Project + + View Project + {shortTitle} +
diff --git a/src/pages/portfolio/style.css b/src/pages/portfolio/style.css index d8ca605..8f8bf38 100644 --- a/src/pages/portfolio/style.css +++ b/src/pages/portfolio/style.css @@ -92,6 +92,7 @@ -moz-osx-font-smoothing: grayscale; } +/* Desktop styles for links */ .project_item .content a { color: var(--text-color); text-decoration: none; @@ -109,6 +110,11 @@ border: 1px solid var(--card-border); } +/* Hide mobile text by default on desktop */ +.mobile-text { + display: none; +} + .project_item .content a:hover { background: var(--secondary-color); color: var(--primary-color); @@ -116,7 +122,7 @@ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2); } -/* Mobile Styles */ +/* Mobile Styles - Only apply below 768px */ @media (max-width: 768px) { .project_items_ho { gap: 1.5rem; @@ -134,52 +140,42 @@ .project_item .content { opacity: 1; padding: 1.25rem; - background: linear-gradient( - to top, - rgba(var(--primary-rgb), 0.95) 0%, - rgba(var(--primary-rgb), 0.7) 50%, - rgba(var(--primary-rgb), 0.3) 100% - ); + background: transparent; justify-content: flex-end; gap: 0.75rem; - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + backdrop-filter: none; + -webkit-backdrop-filter: none; border-top: 1px solid var(--card-border); } .project_item .content h3 { - font-size: 1.1rem; - margin-bottom: 0.5rem; - padding-bottom: 0.25rem; - width: 100%; - max-width: 100%; - overflow-wrap: break-word; - word-wrap: break-word; - -webkit-hyphens: auto; - hyphens: auto; + display: none; /* Hide title on mobile */ } .project_item .content p { - font-size: 0.9rem; - margin-bottom: 1rem; - line-height: 1.4; - display: -webkit-box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; + display: none; /* Hide description text on mobile */ + } + + /* Show mobile text and hide desktop text on mobile */ + .mobile-text { + display: inline; + white-space: nowrap; overflow: hidden; - width: 100%; - max-width: 100%; - overflow-wrap: break-word; - word-wrap: break-word; - -webkit-hyphens: auto; - hyphens: auto; + text-overflow: ellipsis; + max-width: calc(100% - 20px); /* Allow space for the icon */ + } + + .desktop-text { + display: none; } .project_item .content a { padding: 0.75rem 1.25rem; font-size: 0.9rem; border-radius: 6px; - background: var(--primary-color); + background: rgba(var(--primary-rgb), 0.7); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); width: 100%; text-align: center; justify-content: center; @@ -189,6 +185,10 @@ appearance: none; position: relative; z-index: 1; + margin-top: 0; /* Remove margin since title is hidden */ + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + height: 45px; /* Fix height to prevent expansion */ + overflow: hidden; } .project_item:active {