Skip to content

Commit e4d9a93

Browse files
committed
feat: update
1 parent 10a98f9 commit e4d9a93

File tree

11 files changed

+292
-113
lines changed

11 files changed

+292
-113
lines changed

app/LayoutWrapper.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use client'
2+
3+
import { usePathname } from 'next/navigation'
4+
import SectionContainer from '@/components/SectionContainer'
5+
import Header from '@/components/Header'
6+
import Footer from '@/components/Footer'
7+
import { SearchProvider, SearchConfig } from 'pliny/search'
8+
import siteMetadata from '@/data/siteMetadata'
9+
10+
export default function LayoutWrapper({ children }: { children: React.ReactNode }) {
11+
const pathname = usePathname()
12+
const isRootPath = pathname === '/'
13+
14+
if (isRootPath) {
15+
return <main className="mb-auto">{children}</main>
16+
}
17+
18+
return (
19+
<SectionContainer>
20+
<SearchProvider searchConfig={siteMetadata.search as SearchConfig}>
21+
<Header />
22+
<main className="mb-auto">{children}</main>
23+
</SearchProvider>
24+
<Footer />
25+
</SectionContainer>
26+
)
27+
}

app/Main.tsx

Lines changed: 125 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,147 @@
1+
'use client'
2+
3+
import { useEffect } from 'react'
14
import Link from '@/components/Link'
25
import Tag from '@/components/Tag'
36
import siteMetadata from '@/data/siteMetadata'
47
import { formatDate } from 'pliny/utils/formatDate'
58
import NewsletterForm from 'pliny/ui/NewsletterForm'
69
import { TypedBios } from '@/components/TypedBios'
10+
import SectionContainer from '@/components/SectionContainer'
11+
import HomeHeader from '@/components/HomeHeader'
12+
import { SearchProvider, SearchConfig } from 'pliny/search'
13+
import Footer from '@/components/Footer'
14+
import HeroImage from '@/components/HeroImage'
715

816
const MAX_DISPLAY = 5
917

1018
export default function Home({ posts }) {
1119
return (
1220
<>
13-
<div className="pt-6 xl:grid xl:grid-cols-3">
14-
<div className="space-y-4 md:space-y-6 md:pr-8 xl:col-span-2">
15-
<p className="text-4xl font-semibold">Hello, stranger 👋</p>
16-
<div className="text-base leading-7 text-gray-600 md:text-lg md:leading-8 dark:text-gray-400">
17-
<p>I'm Dang Quang Minh - a passionate Software Engineer in Vietnam</p>
18-
<TypedBios />
19-
<div className="mt-4 mb-6 md:mb-8">
20-
<p>
21-
{' '}
22-
I graduated from Hanoi University of Science and Technology in August 2022 with an
23-
Engineer’s degree—which is a fancy way of saying I survived late nights, too much
24-
coffee, and endless debugging.
25-
</p>
26-
<p>
27-
I’m fascinated by large distributed systems (the kind of tech puzzles that keep you
28-
awake at night, in a good way). I’m still a fresher, but I make up for it with
29-
curiosity and a habit of picking up new skills like side quests. In the short run,
30-
my goal is simple: evolve into a senior software engineer 👨‍💻 — the version of me
31-
that ships fewer bugs and maybe actually sleeps.
32-
</p>
33-
</div>
34-
{/* <BlogLinks /> */}
35-
<p className="my-6 flex md:my-8">
36-
<span className="mr-2">Happy reading</span>
37-
{/* <Twemoji emoji="clinking-beer-mugs" /> */}
38-
</p>
21+
<SearchProvider searchConfig={siteMetadata.search as SearchConfig}>
22+
<div className="relative overflow-hidden">
23+
<HomeHeader />
24+
<div
25+
className="relative bg-cover bg-center bg-no-repeat"
26+
style={{
27+
backgroundImage:
28+
"linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), url('/static/images/homepage_background_image.webp')",
29+
}}
30+
>
31+
<SectionContainer>
32+
<div className="py-10 md:flex md:min-h-[calc(100vh-140px)] md:items-center md:py-10">
33+
<div className="space-y-4 md:w-1/2 md:space-y-4 md:pr-8">
34+
<p className="text-4xl font-semibold text-white">Hello, stranger</p>
35+
<div className="text-base leading-7 text-gray-200 md:text-lg md:leading-8 dark:text-gray-300">
36+
<p>I'm Dang Quang Minh - a passionate Software Engineer in Vietnam</p>
37+
<TypedBios />
38+
<div className="mt-4 mb-6 md:mb-8">
39+
<p>
40+
{' '}
41+
I graduated from Hanoi University of Science and Technology in August 2022
42+
with an Engineer's degree—which is a fancy way of saying I survived late
43+
nights, too much coffee, and endless debugging.
44+
</p>
45+
<p>
46+
In the short run, my goal is simple: evolve into a senior software engineer
47+
👨‍💻 — the version of me that ships fewer bugs and maybe actually sleeps.
48+
</p>
49+
</div>
50+
{/* <BlogLinks /> */}
51+
<p className="my-6 flex md:my-8">
52+
<span className="mr-2">Happy reading</span>
53+
{/* <Twemoji emoji="clinking-beer-mugs" /> */}
54+
</p>
55+
</div>
56+
</div>
57+
<div className="hidden md:block md:w-1/2 md:pl-8">
58+
<HeroImage />
59+
</div>
60+
</div>
61+
</SectionContainer>
62+
<div className="absolute right-0 bottom-0 left-0 h-32 bg-gradient-to-t from-white to-transparent dark:from-gray-950" />
3963
</div>
4064
</div>
41-
<div className="hidden pt-8 pl-4 xl:block">{/* <ProfileCard /> */}</div>
42-
</div>
43-
<div className="divide-y divide-gray-200 dark:divide-gray-700">
44-
<div className="space-y-2 pt-6 pb-8 md:space-y-5">
45-
<h1 className="text-3xl leading-9 font-extrabold tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-4xl md:leading-14 dark:text-gray-100">
46-
Latest
47-
</h1>
48-
<p className="text-lg leading-7 text-gray-500 dark:text-gray-400">
49-
{siteMetadata.description}
50-
</p>
51-
</div>
52-
<ul className="divide-y divide-gray-200 dark:divide-gray-700">
53-
{!posts.length && 'No posts found.'}
54-
{posts.slice(0, MAX_DISPLAY).map((post) => {
55-
const { slug, date, title, summary, tags } = post
56-
return (
57-
<li key={slug} className="py-12">
58-
<article>
59-
<div className="space-y-2 xl:grid xl:grid-cols-4 xl:items-baseline xl:space-y-0">
60-
<dl>
61-
<dt className="sr-only">Published on</dt>
62-
<dd className="text-base leading-6 font-medium text-gray-500 dark:text-gray-400">
63-
<time dateTime={date}>{formatDate(date, siteMetadata.locale)}</time>
64-
</dd>
65-
</dl>
66-
<div className="space-y-5 xl:col-span-3">
67-
<div className="space-y-6">
68-
<div>
69-
<h2 className="text-2xl leading-8 font-bold tracking-tight">
70-
<Link
71-
href={`/blog/${slug}`}
72-
className="text-gray-900 dark:text-gray-100"
73-
>
74-
{title}
75-
</Link>
76-
</h2>
77-
<div className="flex flex-wrap">
78-
{tags.map((tag) => (
79-
<Tag key={tag} text={tag} />
80-
))}
65+
<SectionContainer>
66+
<div className="divide-y divide-gray-200 dark:divide-gray-700" />
67+
<div className="space-y-2 pt-6 pb-8 md:space-y-5">
68+
<h1 className="text-3xl leading-9 font-extrabold tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-4xl md:leading-14 dark:text-gray-100">
69+
Latest
70+
</h1>
71+
<p className="text-lg leading-7 text-gray-500 dark:text-gray-400">
72+
{siteMetadata.description}
73+
</p>
74+
</div>
75+
<ul className="divide-y divide-gray-200 dark:divide-gray-700">
76+
{!posts.length && 'No posts found.'}
77+
{posts.slice(0, MAX_DISPLAY).map((post) => {
78+
const { slug, date, title, summary, tags } = post
79+
return (
80+
<li key={slug} className="py-12">
81+
<article>
82+
<div className="space-y-2 xl:grid xl:grid-cols-4 xl:items-baseline xl:space-y-0">
83+
<dl>
84+
<dt className="sr-only">Published on</dt>
85+
<dd className="text-base leading-6 font-medium text-gray-500 dark:text-gray-400">
86+
<time dateTime={date}>{formatDate(date, siteMetadata.locale)}</time>
87+
</dd>
88+
</dl>
89+
<div className="space-y-5 xl:col-span-3">
90+
<div className="space-y-6">
91+
<div>
92+
<h2 className="text-2xl leading-8 font-bold tracking-tight">
93+
<Link
94+
href={`/blog/${slug}`}
95+
className="text-gray-900 dark:text-gray-100"
96+
>
97+
{title}
98+
</Link>
99+
</h2>
100+
<div className="flex flex-wrap">
101+
{tags.map((tag) => (
102+
<Tag key={tag} text={tag} />
103+
))}
104+
</div>
105+
</div>
106+
<div className="prose max-w-none text-gray-500 dark:text-gray-400">
107+
{summary}
81108
</div>
82109
</div>
83-
<div className="prose max-w-none text-gray-500 dark:text-gray-400">
84-
{summary}
110+
<div className="text-base leading-6 font-medium">
111+
<Link
112+
href={`/blog/${slug}`}
113+
className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
114+
aria-label={`Read more: "${title}"`}
115+
>
116+
Read more &rarr;
117+
</Link>
85118
</div>
86119
</div>
87-
<div className="text-base leading-6 font-medium">
88-
<Link
89-
href={`/blog/${slug}`}
90-
className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
91-
aria-label={`Read more: "${title}"`}
92-
>
93-
Read more &rarr;
94-
</Link>
95-
</div>
96120
</div>
97-
</div>
98-
</article>
99-
</li>
100-
)
101-
})}
102-
</ul>
103-
</div>
104-
{posts.length > MAX_DISPLAY && (
105-
<div className="flex justify-end text-base leading-6 font-medium">
106-
<Link
107-
href="/blog"
108-
className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
109-
aria-label="All posts"
110-
>
111-
All Posts &rarr;
112-
</Link>
113-
</div>
114-
)}
115-
{siteMetadata.newsletter?.provider && (
116-
<div className="flex items-center justify-center pt-4">
117-
<NewsletterForm />
118-
</div>
119-
)}
121+
</article>
122+
</li>
123+
)
124+
})}
125+
</ul>
126+
{posts.length > MAX_DISPLAY && (
127+
<div className="flex justify-end text-base leading-6 font-medium">
128+
<Link
129+
href="/blog"
130+
className="text-primary-500 hover:text-primary-600 dark:hover:text-primary-400"
131+
aria-label="All posts"
132+
>
133+
All Posts &rarr;
134+
</Link>
135+
</div>
136+
)}
137+
{siteMetadata.newsletter?.provider && (
138+
<div className="flex items-center justify-center pt-4">
139+
<NewsletterForm />
140+
</div>
141+
)}
142+
</SectionContainer>
143+
<Footer />
144+
</SearchProvider>
120145
</>
121146
)
122147
}

app/layout.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@ import 'remark-github-blockquote-alert/alert.css'
44

55
import { Lato } from 'next/font/google'
66
import { Analytics, AnalyticsConfig } from 'pliny/analytics'
7-
import { SearchProvider, SearchConfig } from 'pliny/search'
8-
import Header from '@/components/Header'
9-
import SectionContainer from '@/components/SectionContainer'
10-
import Footer from '@/components/Footer'
117
import siteMetadata from '@/data/siteMetadata'
128
import { ThemeProviders } from './theme-providers'
139
import { Metadata } from 'next'
10+
import LayoutWrapper from './LayoutWrapper'
1411

1512
const lato = Lato({
1613
weight: ['400', '700'],
@@ -106,13 +103,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
106103
<body className="bg-white pl-[calc(100vw-100%)] text-black antialiased dark:bg-gray-950 dark:text-white">
107104
<ThemeProviders>
108105
<Analytics analyticsConfig={siteMetadata.analytics as AnalyticsConfig} />
109-
<SectionContainer>
110-
<SearchProvider searchConfig={siteMetadata.search as SearchConfig}>
111-
<Header />
112-
<main className="mb-auto">{children}</main>
113-
</SearchProvider>
114-
<Footer />
115-
</SectionContainer>
106+
<LayoutWrapper>{children}</LayoutWrapper>
116107
</ThemeProviders>
117108
</body>
118109
</html>

components/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import ThemeSwitch from './ThemeSwitch'
77
import SearchButton from './SearchButton'
88

99
const Header = () => {
10-
let headerClass = 'flex items-center w-full bg-white dark:bg-gray-950 justify-between py-10'
10+
let headerClass = 'flex items-center w-full bg-white dark:bg-gray-950 justify-between pt-8 pb-5'
1111
if (siteMetadata.stickyNav) {
1212
headerClass += ' sticky top-0 z-50'
1313
}

components/HeroImage.tsx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
'use client'
2+
3+
import React, { useState, useRef } from 'react'
4+
import Image from 'next/image'
5+
6+
const HeroImage = () => {
7+
const [rotate, setRotate] = useState({ x: 0, y: 0 })
8+
const containerRef = useRef<HTMLDivElement>(null)
9+
10+
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
11+
if (!containerRef.current) return
12+
13+
const rect = containerRef.current.getBoundingClientRect()
14+
const x = e.clientX - rect.left
15+
const y = e.clientY - rect.top
16+
17+
const centerX = rect.width / 2
18+
const centerY = rect.height / 2
19+
20+
// Calculate rotation: scale it down so it's subtle (e.g., max 15 degrees)
21+
const rotateY = ((x - centerX) / centerX) * 10
22+
const rotateX = ((centerY - y) / centerY) * 10
23+
24+
setRotate({ x: rotateX, y: rotateY })
25+
}
26+
27+
const handleMouseLeave = () => {
28+
setRotate({ x: 0, y: 0 })
29+
}
30+
31+
return (
32+
<div
33+
ref={containerRef}
34+
onMouseMove={handleMouseMove}
35+
onMouseLeave={handleMouseLeave}
36+
className="group relative transition-all duration-200 ease-out"
37+
style={{
38+
perspective: '1000px',
39+
}}
40+
>
41+
<div
42+
className="relative overflow-hidden transition-all duration-300 ease-out rounded-2xl"
43+
style={{
44+
transform: `rotateX(${rotate.x}deg) rotateY(${rotate.y}deg) scale(${rotate.x !== 0 || rotate.y !== 0 ? 1.05 : 1})`,
45+
boxShadow:
46+
rotate.x !== 0 || rotate.y !== 0
47+
? '0 30px 60px -12px rgba(0, 0, 0, 0.45)'
48+
: '0 10px 25px -5px rgba(0, 0, 0, 0.2)',
49+
}}
50+
>
51+
<Image
52+
src="/static/images/homepage_image_2.webp"
53+
alt="Homepage hero"
54+
width={600}
55+
height={600}
56+
className="h-auto w-full object-cover"
57+
priority
58+
/>
59+
{/* Shine effect on hover */}
60+
<div
61+
className="absolute inset-0 pointer-events-none transition-opacity duration-300 opacity-0 group-hover:opacity-20"
62+
style={{
63+
background: `radial-gradient(circle at ${(rotate.y + 10) * 5}% ${(10 - rotate.x) * 5}%, white, transparent)`,
64+
}}
65+
/>
66+
</div>
67+
</div>
68+
)
69+
}
70+
71+
export default HeroImage

0 commit comments

Comments
 (0)