diff --git a/bun.lock b/bun.lock index 8e3e4b3..2ac9715 100644 --- a/bun.lock +++ b/bun.lock @@ -13,6 +13,7 @@ "astro": "^5.10.0", "caniuse-lite": "^1.0.30001698", "clsx": "^2.1.1", + "hamburger-react": "^2.5.2", "moment": "^2.30.1", "moment-timezone": "^0.6.0", "motion": "^12.23.12", @@ -522,6 +523,8 @@ "h3": ["h3@1.15.4", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.2", "radix3": "^1.1.2", "ufo": "^1.6.1", "uncrypto": "^0.1.3" } }, "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ=="], + "hamburger-react": ["hamburger-react@2.5.2", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19" } }, "sha512-xsv/I7wQX1RDaulDnvMVCXMMRXnkwnUnkE8seD5eNVZJ4nIrjtK5LLA20l5x/pJ+mPWOzlFxW32VJ86Gbo2Njw=="], + "hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="], "hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="], diff --git a/package.json b/package.json index c3c4345..4f36a09 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "astro": "^5.10.0", "caniuse-lite": "^1.0.30001698", "clsx": "^2.1.1", + "hamburger-react": "^2.5.2", "moment": "^2.30.1", "moment-timezone": "^0.6.0", "motion": "^12.23.12", diff --git a/public/images/profile/contact_photo_small.webp b/public/images/profile/contact_photo_small.webp new file mode 100644 index 0000000..13bf3e6 Binary files /dev/null and b/public/images/profile/contact_photo_small.webp differ diff --git a/public/images/pfp_new.webp b/public/images/profile/pfp_new.webp similarity index 100% rename from public/images/pfp_new.webp rename to public/images/profile/pfp_new.webp diff --git a/src/components/contact/ContactComponent.astro b/src/components/contact/ContactComponent.astro index bb35e62..5ef84bd 100644 --- a/src/components/contact/ContactComponent.astro +++ b/src/components/contact/ContactComponent.astro @@ -5,22 +5,24 @@ import { AnimatedTooltip } from "@/components/ui/AnimatedToolTip"; ---
-
+
Ryan

- Ryan - -

+ diff --git a/src/components/home/Greeting.svelte b/src/components/home/Greeting.svelte index 6109140..579632c 100644 --- a/src/components/home/Greeting.svelte +++ b/src/components/home/Greeting.svelte @@ -1,68 +1,68 @@ - Good {greetingState}|| diff --git a/src/components/home/Intro.astro b/src/components/home/Intro.astro index a5d2a82..5799f53 100644 --- a/src/components/home/Intro.astro +++ b/src/components/home/Intro.astro @@ -11,7 +11,7 @@ import Greeting from "./Greeting.svelte";
Ryan diff --git a/src/components/ui/DesktopNav.tsx b/src/components/ui/DesktopNav.tsx new file mode 100644 index 0000000..7fa3a5e --- /dev/null +++ b/src/components/ui/DesktopNav.tsx @@ -0,0 +1,221 @@ +// retractable navigation +// do not retract if it's on contact page + +// mobile: display hamburger + available for work + +import { cn } from "@/lib/utils"; +import { + AnimatePresence, + motion, + useScroll, + useMotionValueEvent, +} from "motion/react"; +import { Cross as HamburgerCross } from "hamburger-react"; +import { useEffect, useState } from "react"; +import pfp from "@/components/images/contact_photo_small.webp"; + +function getWindowDimensions() { + const { innerWidth: width, innerHeight: height } = window; + return { + width, + height, + }; +} + +function useWindowDimensions() { + const hasWindow = typeof window !== "undefined"; + + function getWindowDimensions() { + const width = hasWindow ? window.innerWidth : 0; + const height = hasWindow ? window.innerHeight : 0; + return { + width, + height, + }; + } + + const [windowDimensions, setWindowDimensions] = useState( + getWindowDimensions(), + ); + + useEffect(() => { + if (hasWindow) { + function handleResize() { + setWindowDimensions(getWindowDimensions()); + } + + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + } + }, [hasWindow]); + + return windowDimensions; +} + +export const HoverNavigation = ({ + items, + className, +}: { + items: { + label: string; + link: string; + }[]; + className?: string; +}) => { + const [hoveredIndex, setHoveredIndex] = useState(null); + const { scrollYProgress } = useScroll(); + const [visible, setVisible] = useState(true); + const [navVisible, setNavVisible] = useState(false); + const [availableVisible, setAvailableVisible] = useState(false); + const [onContactPage, setOnContactPage] = useState(false); + const [hamburgerOpen, setHamburgerOpen] = useState(false); + const { width } = useWindowDimensions(); + + // if on contact page, do not do width 0% + useEffect(() => { + setOnContactPage(window.location.href.endsWith("/contact")); + }, []); + + useEffect(() => { + updateNavBarState(width); + }, [width]); + + const updateNavBarState = ( + width: number, + scrollProgress: number = 0, + scrollUp: boolean = false, + ) => { + if (width && width < 1024) { + setNavVisible(false); + setAvailableVisible(true); + } else if (scrollProgress < 0.05) { + setNavVisible(true); + setAvailableVisible(false); + } else if (!onContactPage) { + setNavVisible(scrollUp); + setAvailableVisible(!scrollUp); + } else { + setNavVisible(true); + setAvailableVisible(false); + } + }; + + useMotionValueEvent(scrollYProgress, "change", (current) => { + // Check if current is not undefined and is a number + if (typeof current === "number") { + let direction = current! - scrollYProgress.getPrevious()!; + let scrollUp = direction <= 0; + let scrollProgress = scrollYProgress.get(); + + updateNavBarState(width, scrollProgress, scrollUp); + } + }); + + return ( + <> + Ryan + + + Available for work + +
+
+
+
+
+ + {items.map((item, idx) => ( + setHoveredIndex(idx)} + onMouseLeave={() => setHoveredIndex(null)} + > + + {hoveredIndex === idx && ( + + )} + + {item.label} + + ))} + + +
+ +
+ + ); +}; + +export const NavBarLink = ({ + className, + children, +}: { + className?: string; + children: React.ReactNode; +}) => { + return ( +
+ {children} +
+ ); +}; diff --git a/src/components/ui/HoverNavigation.tsx b/src/components/ui/HoverNavigation.tsx deleted file mode 100644 index 70b5819..0000000 --- a/src/components/ui/HoverNavigation.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { cn } from "@/lib/utils"; -import { - AnimatePresence, - motion, - useScroll, - useMotionValueEvent, -} from "motion/react"; - -import { useState } from "react"; - -export const HoverNavigation = ({ - items, - className, -}: { - items: { - label: string; - link: string; - }[]; - className?: string; -}) => { - let [hoveredIndex, setHoveredIndex] = useState(null); - const { scrollYProgress } = useScroll(); - const [visible, setVisible] = useState(true); - - useMotionValueEvent(scrollYProgress, "change", (current) => { - // Check if current is not undefined and is a number - if (typeof current === "number") { - let direction = current! - scrollYProgress.getPrevious()!; - - if (scrollYProgress.get() < 0.05 && current !== 1) { - setVisible(true); - } else { - if (direction > 0) { - setVisible(false); - } else { - setVisible(true); - } - } - } - }); - - return ( - <> - - - Available for work - -
-
-
-
-
- - {items.map((item, idx) => ( - setHoveredIndex(idx)} - onMouseLeave={() => setHoveredIndex(null)} - > - - {hoveredIndex === idx && ( - - )} - - {item.label} - - ))} - - - ); -}; - -export const NavBarLink = ({ - className, - children, -}: { - className?: string; - children: React.ReactNode; -}) => { - return ( -
- {children} -
- ); -}; diff --git a/src/components/ui/MobileNav.tsx b/src/components/ui/MobileNav.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/ui/NavigationBar.tsx b/src/components/ui/NavigationBar.tsx new file mode 100644 index 0000000..36ca55c --- /dev/null +++ b/src/components/ui/NavigationBar.tsx @@ -0,0 +1,250 @@ +import { cn } from "@/lib/utils"; +import { + AnimatePresence, + motion, + useScroll, + useMotionValueEvent, +} from "motion/react"; +import { Cross as HamburgerCross } from "hamburger-react"; +import { useEffect, useState } from "react"; + +function useWindowDimensions() { + const hasWindow = typeof window !== "undefined"; + + function getWindowDimensions() { + const width = hasWindow ? window.innerWidth : 0; + const height = hasWindow ? window.innerHeight : 0; + return { + width, + height, + }; + } + + const [windowDimensions, setWindowDimensions] = useState( + getWindowDimensions(), + ); + + useEffect(() => { + if (hasWindow) { + function handleResize() { + setWindowDimensions(getWindowDimensions()); + } + + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + } + }, [hasWindow]); + + return windowDimensions; +} + +export const HoverNavigation = ({ + items, + className, +}: { + items: { + label: string; + link: string; + }[]; + className?: string; +}) => { + const [hoveredIndex, setHoveredIndex] = useState(null); + const { scrollYProgress } = useScroll(); + const [navVisible, setNavVisible] = useState(true); + const [availableVisible, setAvailableVisible] = useState(false); + + const [onContactPage, setOnContactPage] = useState(false); + const [hamburgerOpen, setHamburgerOpen] = useState(false); + const { width } = useWindowDimensions(); + + // if on contact page, do not do width 0% + useEffect(() => { + setOnContactPage(window.location.href.endsWith("/contact")); + }, []); + + useEffect(() => { + updateNavBarState(width); + }, [width]); + + const updateNavBarState = ( + width: number, + scrollProgress: number = 0, + scrollUp: boolean = false, + ) => { + if (width && width < 1024) { + setNavVisible(false); + setAvailableVisible(true); + } else if (scrollProgress < 0.05) { + setNavVisible(true); + setAvailableVisible(false); + } else if (!onContactPage) { + setNavVisible(scrollUp); + setAvailableVisible(!scrollUp); + } else { + setNavVisible(true); + setAvailableVisible(false); + } + }; + + useMotionValueEvent(scrollYProgress, "change", (current) => { + // Check if current is not undefined and is a number + if (typeof current === "number") { + let direction = current! - scrollYProgress.getPrevious()!; + let scrollUp = direction <= 0; + let scrollProgress = scrollYProgress.get(); + + updateNavBarState(width, scrollProgress, scrollUp); + } + }); + + return ( + + ); +}; + +export const NavBarLink = ({ + className, + children, +}: { + className?: string; + children: React.ReactNode; +}) => { + return ( +
+ {children} +
+ ); +}; diff --git a/src/layouts/PageLayout.astro b/src/layouts/PageLayout.astro new file mode 100644 index 0000000..986e90d --- /dev/null +++ b/src/layouts/PageLayout.astro @@ -0,0 +1,22 @@ +--- +interface Props { + title: string; + description: string; + thumbnail?: string; + metaURL?: string; +} + +const { title, description, thumbnail, metaURL } = Astro.props; +import Layout from "./Layout.astro"; +import NavBar from "@/components/global/NavBar.astro"; +--- + + + + + diff --git a/src/navStore.js b/src/navStore.js new file mode 100644 index 0000000..55a9c86 --- /dev/null +++ b/src/navStore.js @@ -0,0 +1,4 @@ +import { atom } from "nanostores"; + +export const isNavVisible = atom(true); +export const isAvVisible = atom(false); diff --git a/src/pages/experience.astro b/src/pages/experience.astro index b9d881c..e4ffb11 100644 --- a/src/pages/experience.astro +++ b/src/pages/experience.astro @@ -4,16 +4,16 @@ import Layout from "../layouts/Layout.astro"; import ExperienceComponent from "../components/experience/ExperienceComponent.astro"; import MoreExperience from "../components/experience/MoreExperience.astro"; import Footer from "../components/global/FooterComponent.astro"; +import PageLayout from "@/layouts/PageLayout.astro"; --- - -
- + diff --git a/src/pages/index.astro b/src/pages/index.astro index 98f6a43..6d4f1b4 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -8,13 +8,13 @@ import Experience from "../components/experience/ExperienceComponent.astro"; import Skills from "../components/skills/SkillsComponent.astro"; import Contact from "../components/contact/ContactComponent.astro"; import Footer from "../components/global/FooterComponent.astro"; +import PageLayout from "@/layouts/PageLayout.astro"; --- - -
@@ -36,7 +36,7 @@ import Footer from "../components/global/FooterComponent.astro";