Skip to content

Commit b6e6044

Browse files
author
graceyuen
committed
feat: update portfolio details and improve layout
1 parent 69e499f commit b6e6044

File tree

7 files changed

+140
-52
lines changed

7 files changed

+140
-52
lines changed

app/layout.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ const inter = Inter({ subsets: ["latin"] })
77

88
export const metadata: Metadata = {
99
title: "Grace Yuen - Portfolio",
10-
description: "Full-stack developer passionate about creating digital experiences",
11-
generator: 'v0.dev'
10+
description: "A Year 2 Computer Science student @HKU",
1211
}
1312

1413
export default function RootLayout({

app/page.tsx

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,24 +43,59 @@ export default function Portfolio() {
4343
}
4444

4545

46-
const experiences = [
46+
const experiences = [
4747
{
4848
type: "internship" as const,
4949
title: "IT Wagering Solutions Summer Intern",
5050
company: "Hong Kong Jockey Club",
5151
period: "Jul - Aug 2025 (Upcoming)",
5252
description:
53-
"",
53+
"Expected to assist with wagering system maintenance and optimization to support transaction processing improvements under senior developer guidance.",
54+
},
55+
{
56+
type: "internship" as const,
57+
title: "Business Development Intern (Part-time)",
58+
company: "Versitech Limited / Technology Transfer Office (TTO), HKU",
59+
period: "Sep 2024 - Jun 2025",
60+
description:
61+
"Analyzed invention manuscripts and assessed commercial viability to identify global business opportunities, and built a comprehensive documentation website for the Versitech e-Form processor GUI application using the Docsify framework.",
62+
},
63+
{
64+
type: "internship" as const,
65+
title: "Student Research Assistant Intern",
66+
company: "Department of Data and Systems Engineering (DASE), HKU",
67+
period: "Dec 2024 - Jan 2025",
68+
description:
69+
"Collected 7.5+ hours of motion capture data from 15 participants to train AI models for camera-free gesture recognition and demonstrated the system to 100+ industry partners.",
70+
},
71+
{
72+
type: "internship" as const,
73+
title: "Student Teaching Assistant for ENGG1330 Computer Programming I",
74+
company: "School of Computing & Data Science, HKU",
75+
period: "Sep - Nov 2024",
76+
description:
77+
"Selected as one of 38 tutors from 535 students to host weekly Python tutorial sessions for first-year engineering students. Provided comprehensive guidance on fundamental programming concepts including syntax, control statements, functions, and data types through teaching materials and active participation in course forums.",
78+
},
79+
{
80+
type: "internship" as const,
81+
title: "IT Software Technician Intern",
82+
company: "Yew Chung Education Foundation",
83+
period: "Jun - Aug 2024",
84+
description:
85+
"Coordinated the deployment of the Asset Management and Tracking System (AMTS) across 10 campuses for 18,000+ assets.",
5486
},
5587
]
5688

89+
5790

5891

5992
const projects = [ {
6093
id: 1,
6194
title: "The Hidden Eden",
6295
description: "An immersive 3D artwork designed for exploration in virtual reality",
6396
technologies: ["Blender", "Meta Quest 3", "Gravity Sketch"],
97+
prototypeUrl: "",
98+
githubUrl: "https://github.com/gracetyy/CCST9049_ProjectII_Group2D3",
6499
details:
65100
"Created as a CCST9049 group project, where I was responsible for creating the whole 3D world model. Players can view and interact with the 3D world in Gravity Sketch on Meta Quest 3, such as painting on the easel, grabbing the food, and diving into the underwater world.",
66101
},
@@ -95,7 +130,7 @@ export default function Portfolio() {
95130
{
96131
id: 5,
97132
title: "Baby Chat",
98-
description: "AI-powered Parenting App made for the GenAI Hackathon for Social Good 2023 hosted by HKU",
133+
description: "AI-powered Parenting App made for the GenAI Hackathon for Social Good 2023",
99134
technologies: ["OpenAI API", "Embeddings", "RAG", "Sketch", "GitHub"],
100135
videoUrl: "https://the-bithub.com/BabyChatDemoVid",
101136
details:
@@ -125,8 +160,8 @@ export default function Portfolio() {
125160
className="text-center z-10 px-4"
126161
>
127162
<motion.h1
128-
className="text-5xl md:text-6xl lg:text-7xl font-bold mb-8 relative"
129-
style={{ fontFamily: "Inter, sans-serif" }}
163+
className="text-5xl md:text-7xl lg:text-8xl font-bold mb-8 relative leading-tight md:leading-[1.1] lg:leading-[1.08] px-2 md:px-8"
164+
style={{ fontFamily: "Inter, sans-serif", wordBreak: "keep-all", whiteSpace: "pre-line" }}
130165
>
131166
Hey👋, I'm{" "}
132167
<span className="relative inline-block">
@@ -192,7 +227,7 @@ export default function Portfolio() {
192227
initial={{ opacity: 0, y: 20 }}
193228
whileInView={{ opacity: 1, y: 0 }}
194229
transition={{ duration: 0.6 }}
195-
className="text-3xl md:text-4xl font-bold text-center mb-16"
230+
className="text-4xl md:text-5xl font-bold text-center mb-16"
196231
>
197232
About Me
198233
</motion.h2>
@@ -258,7 +293,7 @@ export default function Portfolio() {
258293
initial={{ opacity: 0, y: 20 }}
259294
whileInView={{ opacity: 1, y: 0 }}
260295
transition={{ duration: 0.6 }}
261-
className="text-3xl md:text-4xl font-bold text-center mb-16"
296+
className="text-4xl md:text-5xl font-bold text-center mb-16"
262297
>
263298
Experience
264299
</motion.h2>
@@ -272,7 +307,7 @@ export default function Portfolio() {
272307
initial={{ opacity: 0, y: 20 }}
273308
whileInView={{ opacity: 1, y: 0 }}
274309
transition={{ duration: 0.6 }}
275-
className="text-3xl md:text-4xl font-bold text-center mb-16"
310+
className="text-4xl md:text-5xl font-bold text-center mb-16"
276311
>
277312
Featured Projects
278313
</motion.h2>
@@ -292,9 +327,9 @@ export default function Portfolio() {
292327
initial={{ opacity: 0, y: 20 }}
293328
whileInView={{ opacity: 1, y: 0 }}
294329
transition={{ duration: 0.6 }}
295-
className="text-3xl md:text-4xl font-bold text-center mb-16"
330+
className="text-4xl md:text-5xl font-bold text-center mb-16"
296331
>
297-
Let's Connect
332+
Let's Get in Touch
298333
</motion.h2>
299334

300335
<ContactCard />
@@ -307,10 +342,13 @@ export default function Portfolio() {
307342
initial={{ opacity: 0, scale: 0.8 }}
308343
animate={{ opacity: 1, scale: 1 }}
309344
exit={{ opacity: 0, scale: 0.8 }}
345+
whileHover={{ scale: 1.15, boxShadow: "0 6px 24px 0 rgba(0,0,0,0.18)" }}
346+
whileTap={{ scale: 0.92, rotate: -12 }}
310347
onClick={scrollToTop}
311348
className={`fixed z-[60] p-3 bg-primary text-primary-foreground rounded-full shadow-lg hover:shadow-xl transition-all duration-300 hover:scale-110
312349
${isMobile ? 'bottom-24 right-4' : 'bottom-20 right-8'}`}
313350
style={isMobile ? { marginBottom: '0', marginRight: '0' } : {}}
351+
aria-label="Back to Top"
314352
>
315353
<ChevronUp className="w-5 h-5" />
316354
</motion.button>

components/contact-card.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ export function ContactCard() {
5353
<CardContent className="p-12 relative">
5454
<div className="text-center mb-12">
5555
<motion.h2
56-
className="text-4xl md:text-5xl font-bold mb-4"
56+
className="text-3xl md:text-4xl font-bold mb-4"
5757
initial={{ opacity: 0, y: 20 }}
5858
whileInView={{ opacity: 1, y: 0 }}
5959
transition={{ duration: 0.6, delay: 0.2 }}
6060
>
61-
Let's get in touch
61+
Contact Info
6262
</motion.h2>
6363

6464
<motion.p

components/floating-nav.tsx

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useState, useEffect, useRef } from "react"
44
import { motion } from "framer-motion"
55
import { Button } from "@/components/ui/button"
6+
import { useIsMobile } from "@/components/ui/use-mobile"
67

78
const navItems = [
89
{ name: "Home", href: "#home" },
@@ -13,6 +14,7 @@ const navItems = [
1314
]
1415

1516
export function FloatingNav({ mobileOffset = false }: { mobileOffset?: boolean } = {}) {
17+
const isMobile = useIsMobile();
1618
const [isAtBottom, setIsAtBottom] = useState(false)
1719
const [activeSection, setActiveSection] = useState("home")
1820
const [isFloating, setIsFloating] = useState(true)
@@ -83,29 +85,35 @@ export function FloatingNav({ mobileOffset = false }: { mobileOffset?: boolean }
8385
variant="ghost"
8486
size="sm"
8587
onClick={() => scrollToSection(item.href)}
86-
className={`relative transition-all duration-300 touch-manipulation active:bg-accent/80 hover:bg-accent/80 text-xs md:text-sm px-2 md:px-3 ${
88+
className={`relative transition-all duration-300 touch-manipulation text-xs md:text-sm px-2 md:px-3 ${
8789
activeSection === item.href.slice(1)
8890
? "text-primary"
89-
: "text-muted-foreground hover:text-foreground"
91+
: "text-muted-foreground"
92+
}${
93+
!isMobile ? " hover:bg-accent/80 hover:text-foreground active:bg-accent/80" : ""
9094
}`}
9195
tabIndex={0}
9296
// Remove hover/active styles on touch devices
9397
onTouchStart={e => {
9498
e.currentTarget.classList.remove("hover:bg-accent", "active:bg-accent/80")
9599
}}
96100
>
97-
<motion.span whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
101+
<motion.span whileHover={!isMobile ? { scale: 1.05 } : {}} whileTap={{ scale: 0.95 }}>
98102
{item.name}
99103
</motion.span>
100104

101105
{activeSection === item.href.slice(1) && (
102-
<motion.div
103-
className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary rounded-full"
104-
initial={false}
105-
transition={{ ease: "easeInOut", duration: 0.35 }}
106-
style={{ y: 0, x: 0 }}
107-
{...(isFloating ? { layoutId: item.name } : {})}
108-
/>
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+
)
109117
)}
110118
</Button>
111119
</motion.div>

components/grid-background.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { motion } from "framer-motion"
55

66
export function GridBackground() {
77
const [isDark, setIsDark] = useState(false)
8+
const [dotPositions, setDotPositions] = useState<Array<{ left: number; top: number; duration: number; delay: number }>>([])
89

910
useEffect(() => {
1011
// Detect dark mode using matchMedia
@@ -15,6 +16,15 @@ export function GridBackground() {
1516
// Also listen for class changes (theme toggle)
1617
const observer = new MutationObserver(update)
1718
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })
19+
// Generate random dot positions only on client
20+
setDotPositions(
21+
Array.from({ length: 20 }).map(() => ({
22+
left: Math.random() * 100,
23+
top: Math.random() * 100,
24+
duration: 3 + Math.random() * 2,
25+
delay: Math.random() * 2,
26+
}))
27+
)
1828
return () => {
1929
match.removeEventListener('change', update)
2030
observer.disconnect()
@@ -66,29 +76,29 @@ export function GridBackground() {
6676
}}
6777
/>
6878

69-
{/* Subtle animated dots */}
79+
{/* Subtle animated dots - only render on client after mount */}
7080
<motion.div
7181
className="absolute inset-0"
7282
initial={{ opacity: 0 }}
7383
animate={{ opacity: 1 }}
7484
transition={{ duration: 2 }}
7585
>
76-
{Array.from({ length: 20 }).map((_, i) => (
86+
{dotPositions.map((dot, i) => (
7787
<motion.div
7888
key={i}
7989
className="absolute w-1 h-1 bg-primary/20 rounded-full"
8090
style={{
81-
left: `${Math.random() * 100}%`,
82-
top: `${Math.random() * 100}%`,
91+
left: `${dot.left}%`,
92+
top: `${dot.top}%`,
8393
}}
8494
animate={{
8595
opacity: [0.2, 0.8, 0.2],
8696
scale: [1, 1.5, 1],
8797
}}
8898
transition={{
89-
duration: 3 + Math.random() * 2,
99+
duration: dot.duration,
90100
repeat: Number.POSITIVE_INFINITY,
91-
delay: Math.random() * 2,
101+
delay: dot.delay,
92102
}}
93103
/>
94104
))}

components/project-card.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { motion, AnimatePresence } from "framer-motion"
55
import { Card, CardContent } from "@/components/ui/card"
66
import { Button } from "@/components/ui/button"
77
import { Badge } from "@/components/ui/badge"
8-
import { ExternalLink, Github, X, Video, PenTool, FileText } from "lucide-react"
8+
import { ExternalLink, Github, X, CirclePlay, Box, Presentation } from "lucide-react"
99
import Image from "next/image"
1010

1111
interface Project {
@@ -16,10 +16,10 @@ interface Project {
1616
technologies?: string[]
1717
liveUrl?: string
1818
githubUrl?: string
19-
details: string
2019
videoUrl?: string
2120
prototypeUrl?: string
2221
pitchDeckUrl?: string
22+
details: string
2323
}
2424

2525
interface ProjectCardProps {
@@ -106,7 +106,7 @@ export function ProjectCard({ project, index }: ProjectCardProps) {
106106
}}
107107
className="flex-1"
108108
>
109-
<Video className="w-3 h-3 mr-1" />
109+
<CirclePlay className="w-3 h-3 mr-1" />
110110
Video
111111
</Button>
112112
)}
@@ -120,7 +120,7 @@ export function ProjectCard({ project, index }: ProjectCardProps) {
120120
}}
121121
className="flex-1"
122122
>
123-
<PenTool className="w-3 h-3 mr-1" />
123+
<Box className="w-3 h-3 mr-1" />
124124
Prototype
125125
</Button>
126126
)}
@@ -134,7 +134,7 @@ export function ProjectCard({ project, index }: ProjectCardProps) {
134134
}}
135135
className="flex-1"
136136
>
137-
<FileText className="w-3 h-3 mr-1" />
137+
<Presentation className="w-3 h-3 mr-1" />
138138
Pitch Deck
139139
</Button>
140140
)}
@@ -183,15 +183,14 @@ export function ProjectCard({ project, index }: ProjectCardProps) {
183183

184184
<div className="p-6">
185185
<h2 className="text-2xl font-bold mb-4">{project.title}</h2>
186-
186+
<p className="text-muted-foreground mb-4">{project.description}</p>
187187
<div className="flex flex-wrap gap-2 mb-4">
188188
{project.technologies && project.technologies.length > 0 && project.technologies.map((tech) => (
189189
<Badge key={tech} variant="secondary">
190190
{tech}
191191
</Badge>
192192
))}
193193
</div>
194-
195194
<p className="text-muted-foreground mb-6 leading-relaxed">{project.details}</p>
196195

197196
<div className="flex gap-4">
@@ -209,19 +208,19 @@ export function ProjectCard({ project, index }: ProjectCardProps) {
209208
)}
210209
{project.videoUrl && project.videoUrl !== "" && (
211210
<Button variant="outline" onClick={() => window.open(project.videoUrl, "_blank")} className="flex-1">
212-
<Video className="w-4 h-4 mr-2" />
211+
<CirclePlay className="w-4 h-4 mr-2" />
213212
Video
214213
</Button>
215214
)}
216215
{project.prototypeUrl && project.prototypeUrl !== "" && (
217216
<Button variant="outline" onClick={() => window.open(project.prototypeUrl, "_blank")} className="flex-1">
218-
<PenTool className="w-4 h-4 mr-2" />
217+
<Box className="w-4 h-4 mr-2" />
219218
Prototype
220219
</Button>
221220
)}
222221
{project.pitchDeckUrl && project.pitchDeckUrl !== "" && (
223222
<Button variant="outline" onClick={() => window.open(project.pitchDeckUrl, "_blank")} className="flex-1">
224-
<FileText className="w-4 h-4 mr-2" />
223+
<Presentation className="w-4 h-4 mr-2" />
225224
Pitch Deck
226225
</Button>
227226
)}

0 commit comments

Comments
 (0)