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";
---
-
+
-
-
-
+
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";

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 (
+ <>
+
+
+
+ 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";
-
+