Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
T
tran-quang-hung-portfolio
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
HungTQ
tran-quang-hung-portfolio
Commits
d31f77fc
Commit
d31f77fc
authored
Jun 29, 2026
by
HungTQ
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
style(ui): enhance portfolio motion design
parent
d288b585
Changes
2
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
568 additions
and
143 deletions
+568
-143
App.css
src/App.css
+508
-127
App.tsx
src/App.tsx
+60
-16
No files found.
src/App.css
View file @
d31f77fc
This diff is collapsed.
Click to expand it.
src/App.tsx
View file @
d31f77fc
import
{
use
Memo
,
useState
}
from
'react'
import
{
use
Effect
,
useMemo
,
useState
,
type
CSSProperties
}
from
'react'
import
{
ArrowUpRight
,
Bot
,
...
...
@@ -72,6 +72,12 @@ const quickQuestions = [
'How can I contact Hung?'
,
]
const
metrics
=
[
{
label
:
'Mobile projects'
,
value
:
'02'
},
{
label
:
'Core stack'
,
value
:
'RN + Flutter'
},
{
label
:
'Graduation'
,
value
:
'2027'
},
]
function
answerQuestion
(
question
:
string
)
{
const
normalized
=
question
.
toLowerCase
()
...
...
@@ -102,21 +108,42 @@ function answerQuestion(question: string) {
function
App
()
{
const
[
question
,
setQuestion
]
=
useState
(
quickQuestions
[
0
])
const
[
answer
,
setAnswer
]
=
useState
(
answerQuestion
(
quickQuestions
[
0
]))
const
[
answerPulse
,
setAnswerPulse
]
=
useState
(
0
)
const
featuredSkills
=
useMemo
(
()
=>
skillGroups
.
flatMap
((
group
)
=>
group
.
items
).
slice
(
0
,
9
),
[],
)
useEffect
(()
=>
{
const
revealItems
=
document
.
querySelectorAll
<
HTMLElement
>
(
'.reveal'
)
const
observer
=
new
IntersectionObserver
(
(
entries
)
=>
{
entries
.
forEach
((
entry
)
=>
{
if
(
entry
.
isIntersecting
)
{
entry
.
target
.
classList
.
add
(
'is-visible'
)
observer
.
unobserve
(
entry
.
target
)
}
})
},
{
threshold
:
0.18
},
)
revealItems
.
forEach
((
item
)
=>
observer
.
observe
(
item
))
return
()
=>
observer
.
disconnect
()
},
[])
function
handleAsk
(
nextQuestion
=
question
)
{
const
trimmed
=
nextQuestion
.
trim
()
if
(
!
trimmed
)
return
setQuestion
(
trimmed
)
setAnswer
(
answerQuestion
(
trimmed
))
setAnswerPulse
((
current
)
=>
current
+
1
)
}
return
(
<
main
>
<
div
className=
"ambient-grid"
aria
-
hidden=
"true"
/>
<
nav
className=
"topbar"
aria
-
label=
"Primary navigation"
>
<
a
className=
"brand"
href=
"#hero"
>
TQH
</
a
>
<
div
className=
"nav-links"
>
...
...
@@ -128,9 +155,13 @@ function App() {
</
nav
>
<
section
className=
"hero"
id=
"hero"
>
<
div
className=
"hero-copy"
>
<
div
className=
"hero-copy
reveal
"
>
<
p
className=
"eyebrow"
>
Mobile portfolio / React Native intern
</
p
>
<
h1
>
{
profile
.
name
}
</
h1
>
<
h1
>
<
span
>
Tran
</
span
>
<
span
>
Quang
</
span
>
<
span
>
Hung
</
span
>
</
h1
>
<
p
className=
"role"
>
{
profile
.
role
}
</
p
>
<
p
className=
"summary"
>
{
profile
.
summary
}
</
p
>
...
...
@@ -149,11 +180,22 @@ function App() {
<
span
>
Firebase
</
span
>
<
span
>
Figma UI/UX
</
span
>
</
div
>
<
div
className=
"hero-metrics"
aria
-
label=
"Profile metrics"
>
{
metrics
.
map
((
metric
)
=>
(
<
div
key=
{
metric
.
label
}
>
<
strong
>
{
metric
.
value
}
</
strong
>
<
span
>
{
metric
.
label
}
</
span
>
</
div
>
))
}
</
div
>
</
div
>
<
div
className=
"hero-visual"
aria
-
label=
"CV preview"
>
<
div
className=
"hero-visual reveal"
aria
-
label=
"CV preview"
>
<
div
className=
"device-halo"
aria
-
hidden=
"true"
/>
<
div
className=
"phone-frame"
>
<
div
className=
"phone-speaker"
/>
<
div
className=
"screen-scan"
aria
-
hidden=
"true"
/>
<
img
src=
"/cv-preview.png"
alt=
"Tran Quang Hung CV preview"
/>
</
div
>
<
div
className=
"floating-note"
>
...
...
@@ -163,7 +205,7 @@ function App() {
</
div
>
</
section
>
<
section
className=
"section intro-grid"
aria
-
label=
"Profile summary"
>
<
section
className=
"section intro-grid
reveal
"
aria
-
label=
"Profile summary"
>
<
div
>
<
p
className=
"section-kicker"
>
Internship fit
</
p
>
<
h2
>
Building mobile interfaces with product-minded UI decisions.
</
h2
>
...
...
@@ -175,15 +217,16 @@ function App() {
</
p
>
</
section
>
<
section
className=
"section"
id=
"projects"
>
<
div
className=
"section-heading"
>
<
section
className=
"section
projects-section
"
id=
"projects"
>
<
div
className=
"section-heading
reveal
"
>
<
p
className=
"section-kicker"
>
Selected work
</
p
>
<
h2
>
Projects from my CV
</
h2
>
</
div
>
<
div
className=
"project-grid"
>
{
projects
.
map
((
project
)
=>
(
<
article
className=
"project-card"
key=
{
project
.
name
}
>
{
projects
.
map
((
project
,
index
)
=>
(
<
article
className=
"project-card reveal"
key=
{
project
.
name
}
style=
{
{
'--delay'
:
`${index * 110}ms`
}
as
CSSProperties
}
>
<
div
className=
"project-index"
>
0
{
index
+
1
}
</
div
>
<
div
className=
"project-meta"
>
<
span
>
{
project
.
type
}
</
span
>
<
span
>
{
project
.
time
}
</
span
>
...
...
@@ -208,7 +251,7 @@ function App() {
</
section
>
<
section
className=
"section skills-section"
id=
"skills"
>
<
div
className=
"section-heading"
>
<
div
className=
"section-heading
reveal
"
>
<
p
className=
"section-kicker"
>
Capability map
</
p
>
<
h2
>
Technical skills
</
h2
>
</
div
>
...
...
@@ -220,8 +263,8 @@ function App() {
</
div
>
<
div
className=
"skill-groups"
>
{
skillGroups
.
map
((
group
)
=>
(
<
div
className=
"skill-group
"
key=
{
group
.
title
}
>
{
skillGroups
.
map
((
group
,
index
)
=>
(
<
div
className=
"skill-group
reveal"
key=
{
group
.
title
}
style=
{
{
'--delay'
:
`${index * 80}ms`
}
as
CSSProperties
}
>
<
h3
>
{
group
.
title
}
</
h3
>
<
ul
>
{
group
.
items
.
map
((
item
)
=>
(
...
...
@@ -234,7 +277,7 @@ function App() {
</
section
>
<
section
className=
"section ai-panel"
id=
"ai"
>
<
div
className=
"ai-copy"
>
<
div
className=
"ai-copy
reveal
"
>
<
p
className=
"section-kicker"
>
AI applied
</
p
>
<
h2
>
Profile assistant
</
h2
>
<
p
>
...
...
@@ -243,10 +286,11 @@ function App() {
</
p
>
</
div
>
<
div
className=
"assistant-box"
>
<
div
className=
"assistant-box
reveal
"
>
<
div
className=
"assistant-header"
>
<
Bot
size=
{
22
}
/>
<
span
>
Ask about Hung
</
span
>
<
i
aria
-
hidden=
"true"
/>
</
div
>
<
div
className=
"quick-questions"
>
...
...
@@ -272,14 +316,14 @@ function App() {
</
button
>
</
div
>
<
div
className=
"answer-box"
>
<
div
className=
"answer-box"
key=
{
answerPulse
}
>
<
span
>
Answer
</
span
>
<
p
>
{
answer
}
</
p
>
</
div
>
</
div
>
</
section
>
<
section
className=
"section contact-section"
id=
"contact"
>
<
section
className=
"section contact-section
reveal
"
id=
"contact"
>
<
div
>
<
p
className=
"section-kicker"
>
Contact
</
p
>
<
h2
>
Ready for mobile app internship work.
</
h2
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment