Skip to content

Commit 939721a

Browse files
committed
feat: update code to improve the UI
1 parent b6e6044 commit 939721a

File tree

9 files changed

+4737
-380
lines changed

9 files changed

+4737
-380
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"compile-hero.disable-compile-files-on-did-save-code": true
3+
}

app/dist/page.js

Lines changed: 256 additions & 0 deletions
Large diffs are not rendered by default.

app/page.tsx

Lines changed: 138 additions & 85 deletions
Large diffs are not rendered by default.

components/contact-card.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function ContactCard() {
5151
/>
5252

5353
<CardContent className="p-12 relative">
54-
<div className="text-center mb-12">
54+
{/* <div className="text-center mb-12">
5555
<motion.h2
5656
className="text-3xl md:text-4xl font-bold mb-4"
5757
initial={{ opacity: 0, y: 20 }}
@@ -70,7 +70,7 @@ export function ContactCard() {
7070
I'm always open to discussing new opportunities and interesting
7171
projects.
7272
</motion.p>
73-
</div>
73+
</div> */}
7474

7575
<div className="grid md:grid-cols-2 gap-6 mb-8">
7676
{contactInfo.map((contact, index) => (
@@ -122,16 +122,21 @@ export function ContactCard() {
122122
/>
123123
</defs>
124124
<text className="text-xs fill-current">
125-
<textPath href="#circle" className="tracking-widest">
125+
<textPath
126+
href="#circle"
127+
className="tracking-widest"
128+
textLength="230"
129+
lengthAdjust="spacingAndGlyphs"
130+
>
126131
GRACE YUEN • YUEN TIN YAN •
127132
</textPath>
128133
</text>
129134
</svg>
130-
<div className="absolute inset-0 flex items-center justify-center">
135+
{/* <div className="absolute inset-0 flex items-center justify-center">
131136
<div className="w-16 h-16 bg-primary rounded-full flex items-center justify-center">
132137
<Mail className="w-6 h-6 text-primary-foreground" />
133138
</div>
134-
</div>
139+
</div> */}
135140
</div>
136141
</motion.div>
137142
</CardContent>

components/dist/grid-background.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"use client";
2+
"use strict";
3+
exports.__esModule = true;
4+
exports.GridBackground = void 0;
5+
var react_1 = require("react");
6+
var framer_motion_1 = require("framer-motion");
7+
function GridBackground() {
8+
var _a = react_1.useState(false), isDark = _a[0], setIsDark = _a[1];
9+
var _b = react_1.useState([]), dotPositions = _b[0], setDotPositions = _b[1];
10+
react_1.useEffect(function () {
11+
// Detect dark mode using matchMedia
12+
var match = window.matchMedia("(prefers-color-scheme: dark)");
13+
var update = function () {
14+
return setIsDark(document.documentElement.classList.contains("dark") || match.matches);
15+
};
16+
update();
17+
match.addEventListener("change", update);
18+
// Also listen for class changes (theme toggle)
19+
var observer = new MutationObserver(update);
20+
observer.observe(document.documentElement, {
21+
attributes: true,
22+
attributeFilter: ["class"]
23+
});
24+
// Generate random dot positions only on client
25+
setDotPositions(Array.from({ length: 20 }).map(function () { return ({
26+
left: Math.random() * 100,
27+
top: Math.random() * 100,
28+
duration: 3 + Math.random() * 2,
29+
delay: Math.random() * 1.5
30+
}); }));
31+
return function () {
32+
match.removeEventListener("change", update);
33+
observer.disconnect();
34+
};
35+
}, []);
36+
return (React.createElement("div", { className: "absolute inset-0 w-full h-full overflow-hidden" },
37+
React.createElement("div", { className: "absolute inset-0 opacity-30 grid-bg block dark:hidden", style: {
38+
backgroundImage: "linear-gradient(rgba(0,0,0,0.1) 1px, transparent 1px),\nlinear-gradient(90deg, rgba(0,0,0,0.1) 1px, transparent 1px)",
39+
backgroundSize: "40px 40px"
40+
} }),
41+
React.createElement("div", { className: "absolute inset-0 opacity-40 grid-bg hidden dark:block", style: {
42+
backgroundImage: "linear-gradient(rgba(255,255,255,0.2) 1px, transparent 1px),\nlinear-gradient(90deg, rgba(255,255,255,0.2) 1px, transparent 1px)",
43+
backgroundSize: "40px 40px"
44+
} }),
45+
React.createElement("div", { className: "absolute inset-0 block dark:hidden", style: {
46+
background: "linear-gradient(\n to bottom,\n rgba(255,255,255,0) 0%,\n rgba(255,255,255,0.3) 60%,\n rgba(255,255,255,0.8) 85%,\n rgba(255,255,255,1) 100%\n )"
47+
} }),
48+
React.createElement("div", { className: "absolute inset-0 hidden dark:block", style: {
49+
background: "linear-gradient(\n to bottom,\n rgba(107, 123, 172, 0.5) 0%,\n rgba(10, 23, 97, 0.3) 60%,\n rgba(2, 8, 23, 0.85) 100%\n )"
50+
} }),
51+
React.createElement(framer_motion_1.motion.div, { className: "absolute inset-0", initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 2 } }, dotPositions.map(function (dot, i) { return (React.createElement(framer_motion_1.motion.div, { key: i, className: "absolute w-1 h-1 bg-primary/20 rounded-full", style: {
52+
left: dot.left + "%",
53+
top: dot.top + "%"
54+
}, animate: {
55+
opacity: [0.2, 0.8, 0.2],
56+
scale: [1, 1.5, 1]
57+
}, transition: {
58+
duration: dot.duration,
59+
repeat: Number.POSITIVE_INFINITY,
60+
delay: dot.delay
61+
} })); }))));
62+
}
63+
exports.GridBackground = GridBackground;

components/floating-nav.tsx

Lines changed: 127 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,142 @@
1-
"use client"
1+
"use client";
22

3-
import { useState, useEffect, useRef } from "react"
4-
import { motion } from "framer-motion"
5-
import { Button } from "@/components/ui/button"
6-
import { useIsMobile } from "@/components/ui/use-mobile"
3+
import { useState, useEffect, useRef } from "react";
4+
import { motion } from "framer-motion";
5+
import { Button } from "@/components/ui/button";
6+
import { useIsMobile } from "@/components/ui/use-mobile";
77

88
const navItems = [
9-
{ name: "Home", href: "#home" },
10-
{ name: "About", href: "#about" },
11-
{ name: "Experience", href: "#experience" },
12-
{ name: "Projects", href: "#projects" },
13-
{ name: "Contact", href: "#contact" },
14-
]
9+
{ name: "Home", href: "#home" },
10+
{ name: "About", href: "#about" },
11+
{ name: "Experience", href: "#experience" },
12+
{ name: "Projects", href: "#projects" },
13+
{ name: "Contact", href: "#contact" },
14+
];
1515

16-
export function FloatingNav({ mobileOffset = false }: { mobileOffset?: boolean } = {}) {
17-
const isMobile = useIsMobile();
18-
const [isAtBottom, setIsAtBottom] = useState(false)
19-
const [activeSection, setActiveSection] = useState("home")
20-
const [isFloating, setIsFloating] = useState(true)
21-
const navRef = useRef(null)
16+
export function FloatingNav({
17+
mobileOffset = false,
18+
}: { mobileOffset?: boolean } = {}) {
19+
const isMobile = useIsMobile();
20+
const [isAtBottom, setIsAtBottom] = useState(false);
21+
const [activeSection, setActiveSection] = useState("home");
22+
const [isFloating, setIsFloating] = useState(true);
23+
const navRef = useRef(null);
2224

23-
useEffect(() => {
24-
const handleScroll = () => {
25-
const scrollHeight = document.documentElement.scrollHeight
26-
const scrollTop = document.documentElement.scrollTop
27-
const clientHeight = document.documentElement.clientHeight
25+
useEffect(() => {
26+
const handleScroll = () => {
27+
const scrollHeight = document.documentElement.scrollHeight;
28+
const scrollTop = document.documentElement.scrollTop;
29+
const clientHeight = document.documentElement.clientHeight;
2830

29-
const bottomThreshold = 100
30-
const isBottom = scrollTop + clientHeight >= scrollHeight - bottomThreshold
31-
setIsAtBottom(isBottom)
32-
setIsFloating(!isBottom)
31+
const bottomThreshold = 100;
32+
const isBottom =
33+
scrollTop + clientHeight >= scrollHeight - bottomThreshold;
34+
setIsAtBottom(isBottom);
35+
setIsFloating(!isBottom);
3336

34-
// Update active section based on scroll position
35-
const sections = navItems.map((item) => item.href.slice(1))
36-
const currentSection = sections.find((section) => {
37-
const element = document.getElementById(section)
38-
if (element) {
39-
const rect = element.getBoundingClientRect()
40-
return rect.top <= 100 && rect.bottom >= 100
41-
}
42-
return false
43-
})
37+
// Update active section based on scroll position
38+
const sections = navItems.map((item) => item.href.slice(1));
39+
const currentSection = sections.find((section) => {
40+
const element = document.getElementById(section);
41+
if (element) {
42+
const rect = element.getBoundingClientRect();
43+
return rect.top <= 100 && rect.bottom >= 100;
44+
}
45+
return false;
46+
});
4447

45-
if (currentSection) {
46-
setActiveSection(currentSection)
47-
}
48-
}
48+
if (currentSection) {
49+
setActiveSection(currentSection);
50+
}
51+
};
4952

50-
window.addEventListener("scroll", handleScroll)
51-
return () => window.removeEventListener("scroll", handleScroll)
52-
}, [])
53+
window.addEventListener("scroll", handleScroll);
54+
return () => window.removeEventListener("scroll", handleScroll);
55+
}, []);
5356

54-
const scrollToSection = (href: string) => {
55-
const element = document.getElementById(href.slice(1))
56-
if (element) {
57-
element.scrollIntoView({ behavior: "smooth" })
58-
}
59-
}
57+
const scrollToSection = (href: string) => {
58+
const element = document.getElementById(href.slice(1));
59+
if (element) {
60+
element.scrollIntoView({ behavior: "smooth" });
61+
}
62+
};
6063

61-
return (
62-
<motion.nav
63-
ref={navRef}
64-
className={`fixed z-40 left-1/2 -translate-x-1/2 transition-all duration-500 pointer-events-auto
65-
${isAtBottom ? `bottom-0 w-full max-w-6xl` : `${mobileOffset ? 'bottom-4' : 'bottom-4'} w-auto`}
66-
${mobileOffset ? ' md:bottom-8 md:mb-0' : ''}
64+
return (
65+
<motion.nav
66+
ref={navRef}
67+
className={`fixed z-40 left-1/2 -translate-x-1/2 transition-all duration-500 pointer-events-auto
68+
${
69+
isAtBottom
70+
? `bottom-0 w-full max-w-6xl`
71+
: `${mobileOffset ? "bottom-4" : "bottom-4"} w-auto`
72+
}
73+
${mobileOffset ? " md:bottom-8 md:mb-0" : ""}
6774
`}
68-
>
69-
<motion.div
70-
layout
71-
className={`backdrop-blur-md bg-background/95 border border-border shadow-lg dark:bg-gray-900/95 dark:border-gray-700 transition-all duration-300 ${
72-
isAtBottom
73-
? "rounded-tl-2xl rounded-tr-2xl rounded-b-none w-full h-full"
74-
: "rounded-full w-auto h-full"
75-
}`}
76-
>
77-
<div className="flex items-center justify-center h-full px-4 md:px-6">
78-
<div className="flex items-center space-x-1">
79-
<span className="text-sm font-semibold mr-2 md:mr-4 hidden sm:block">Grace Yuen</span>
80-
<div className="h-4 w-px bg-border mr-2 md:mr-4 hidden sm:block" />
75+
>
76+
<motion.div
77+
layout
78+
className={`backdrop-blur-md bg-background/95 border border-border shadow-lg dark:bg-gray-900/95 dark:border-gray-700 transition-all duration-300 ${
79+
isAtBottom
80+
? "rounded-tl-2xl rounded-tr-2xl rounded-b-none w-full h-full"
81+
: "rounded-full w-auto h-full"
82+
}`}
83+
>
84+
<div className="flex items-center justify-center h-full px-4 md:px-6">
85+
<div className="flex items-center space-x-1">
86+
<span className="text-sm font-semibold mr-2 md:mr-4 hidden sm:block">
87+
Grace Yuen
88+
</span>
89+
<div className="h-4 w-px bg-border mr-2 md:mr-4 hidden sm:block" />
8190

82-
{navItems.map((item) => (
83-
<motion.div key={item.name}>
84-
<Button
85-
variant="ghost"
86-
size="sm"
87-
onClick={() => scrollToSection(item.href)}
88-
className={`relative transition-all duration-300 touch-manipulation text-xs md:text-sm px-2 md:px-3 ${
89-
activeSection === item.href.slice(1)
90-
? "text-primary"
91-
: "text-muted-foreground"
92-
}${
93-
!isMobile ? " hover:bg-accent/80 hover:text-foreground active:bg-accent/80" : ""
94-
}`}
95-
tabIndex={0}
96-
// Remove hover/active styles on touch devices
97-
onTouchStart={e => {
98-
e.currentTarget.classList.remove("hover:bg-accent", "active:bg-accent/80")
99-
}}
100-
>
101-
<motion.span whileHover={!isMobile ? { scale: 1.05 } : {}} whileTap={{ scale: 0.95 }}>
102-
{item.name}
103-
</motion.span>
91+
{navItems.map((item) => (
92+
<motion.div key={item.name}>
93+
<Button
94+
variant="ghost"
95+
size="sm"
96+
onClick={() => scrollToSection(item.href)}
97+
className={`relative transition-all duration-300 touch-manipulation text-xs md:text-sm px-2 md:px-3 ${
98+
activeSection === item.href.slice(1)
99+
? "text-primary"
100+
: "text-muted-foreground"
101+
}${
102+
!isMobile
103+
? " hover:bg-accent/80 hover:text-foreground active:bg-accent/80"
104+
: ""
105+
}`}
106+
tabIndex={0}
107+
// Remove hover/active styles on touch devices
108+
onTouchStart={(e) => {
109+
e.currentTarget.classList.remove(
110+
"hover:bg-accent",
111+
"active:bg-accent/80"
112+
);
113+
}}
114+
>
115+
<motion.span
116+
whileHover={!isMobile ? { scale: 1.05 } : {}}
117+
whileTap={{ scale: 0.95 }}
118+
>
119+
{item.name}
120+
</motion.span>
104121

105-
{activeSection === item.href.slice(1) && (
106-
isFloating ? (
107-
<motion.div
108-
className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary rounded-full"
109-
initial={false}
110-
transition={{ ease: "easeInOut", duration: 0.35 }}
111-
style={{ y: 0, x: 0 }}
112-
layoutId={item.name}
113-
/>
114-
) : (
115-
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary rounded-full" />
116-
)
117-
)}
118-
</Button>
119-
</motion.div>
120-
))}
121-
</div>
122-
</div>
123-
</motion.div>
124-
</motion.nav>
125-
)
122+
{activeSection === item.href.slice(1) &&
123+
(isFloating ? (
124+
<motion.div
125+
className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary rounded-full"
126+
initial={false}
127+
transition={{ ease: "easeInOut", duration: 0.35 }}
128+
style={{ y: 0, x: 0 }}
129+
layoutId={item.name}
130+
/>
131+
) : (
132+
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary rounded-full" />
133+
))}
134+
</Button>
135+
</motion.div>
136+
))}
137+
</div>
138+
</div>
139+
</motion.div>
140+
</motion.nav>
141+
);
126142
}

0 commit comments

Comments
 (0)