1
- 'use client' ;
2
-
3
- import { m } from 'framer-motion' ;
4
- import { createElement , useEffect , useState } from 'react' ;
5
- import Image from 'next/image' ;
6
-
7
- import { TextUpTransitionView } from '@/components/ui/transition/TextUpTransitionView' ;
8
- import { SocialIcon } from '@/components/modules/home/SocialIcon' ;
9
- import {
10
- FaSolidFeatherAlt ,
11
- FaSolidUserFriends ,
12
- MdiFlask ,
13
- RMixPlanet ,
14
- } from '@/components/icons/menu-collection' ;
15
- import { cn } from '@/lib/helper' ;
1
+ import { buildPostData } from '@/core' ;
16
2
import { FocusCards } from '@/components/ui/focus-cards.tsx' ;
3
+ import { Hero } from '@/components/modules/home/Hero' ;
4
+ import { WindVane } from '@/components/modules/home/WindVane/WindVane' ;
17
5
18
- const windsock = [
19
- {
20
- title : '文稿' ,
21
- type : 'Note' ,
22
- path : '/list' ,
23
- icon : FaSolidFeatherAlt ,
24
- } ,
25
- {
26
- title : '朋友们' ,
27
- icon : FaSolidUserFriends ,
28
- path : '/friends' ,
29
- } ,
30
- {
31
- title : '看看我做些啥' ,
32
- icon : MdiFlask ,
33
- path : '/projects' ,
34
- } ,
35
- {
36
- title : '跃迁' ,
37
- icon : RMixPlanet ,
38
- path : 'https://travel.moe/go.html' ,
39
- } ,
40
- ] ;
6
+ const { postDataList } = buildPostData ( ) ;
41
7
42
8
export default function Home ( ) {
43
9
return (
@@ -49,300 +15,14 @@ export default function Home() {
49
15
) ;
50
16
}
51
17
52
- const TwoColumnLayout = ( {
53
- children,
54
- leftContainerClassName,
55
- rightContainerClassName,
56
- className,
57
- } : {
58
- children :
59
- | [ React . ReactNode , React . ReactNode ]
60
- | [ React . ReactNode , React . ReactNode , React . ReactNode ] ;
61
-
62
- leftContainerClassName ?: string ;
63
- rightContainerClassName ?: string ;
64
- className ?: string ;
65
- } ) => {
66
- return (
67
- < div
68
- className = { cn (
69
- 'relative mx-auto block size-full min-w-0 max-w-[1800px] flex-col flex-wrap items-center lg:flex lg:flex-row' ,
70
- className ,
71
- ) }
72
- style = { { padding : '0 20px' } }
73
- >
74
- { children . slice ( 0 , 2 ) . map ( ( child , i ) => (
75
- < div
76
- key = { i }
77
- className = { cn (
78
- 'flex w-full flex-col center lg:h-auto lg:w-1/2' ,
79
- i === 0 ? leftContainerClassName : rightContainerClassName ,
80
- ) }
81
- >
82
- < div className = "relative max-w-full lg:max-w-2xl" > { child } </ div >
83
- </ div >
84
- ) ) }
85
-
86
- { children [ 2 ] }
87
- </ div >
88
- ) ;
89
- } ;
90
-
91
- const Hero = ( ) => {
92
- const title = {
93
- template : [
94
- { type : 'h1' , text : `Hi, I'm ` , class : ' font-light text-4xl font-900 inline-block' } ,
95
- { type : 'h1' , text : `zw` , class : ' text-4xl font-bold inline-block' } ,
96
- {
97
- type : 'h1' ,
98
- text : `👋` ,
99
- class :
100
- ' font-light text-4xl font-bold inline-block hover:scale-[1.05] cursor-pointer origin-center transition-all' ,
101
- } ,
102
- { type : 'h1' , text : ` ` , class : ' h-0 w-0 scale-0' } ,
103
- {
104
- type : 'span' ,
105
- text : 'A NodeJS Full Stack ' ,
106
- class : 'font-light text-4xl font-900 inline-block mt-[5px]' ,
107
- } ,
108
- {
109
- type : 'code' ,
110
- text : '<Developer />' ,
111
- class :
112
- ' inline-block font-medium mx-2 text-3xl rounded p-2 bg-gray-200 dark:bg-gray-800/0 hover:dark:bg-gray-800/100 bg-opacity-0 hover:bg-opacity-100 transition-background duration-200"><div><span class="inline-block whitespace-pre' ,
113
- } ,
114
- ] ,
115
- } ;
116
-
117
- const description = 'An independent developer coding with love.' ;
118
-
119
- const siteOwner = {
120
- avatar : '/image/owner.jpg' ,
121
- socialIds : {
122
- github : 'https://github.com/yangxuanxuan1998' ,
123
- twitter : 'https://twitter.com/yangxuanxuan1998' ,
124
- } ,
125
- } ;
126
-
127
- const { avatar, socialIds } = siteOwner ;
128
-
129
- const titleAnimateD =
130
- title . template . reduce ( ( acc , cur ) => {
131
- return acc + ( cur . text ?. length || 0 ) ;
132
- } , 0 ) * 50 ;
133
-
134
- return (
135
- < div className = "mt-[-2.5rem] min-w-0 md:px-44 max-w-screen overflow-hidden lg:mt-[-6.5rem] lg:h-dvh lg:min-h-[800px]" >
136
- < TwoColumnLayout leftContainerClassName = "mt-[110px] lg:mt-0 lg:h-[15rem] lg:h-1/2" >
137
- < >
138
- < m . div
139
- className = " relative text-center leading-[4] lg:text-left lg:ml-24 lg:mt-10"
140
- initial = { { y : 50 , opacity : 1 } }
141
- animate = { { y : 0 , opacity : 1 } }
142
- transition = { { type : 'spring' , damping : 10 , stiffness : 100 } }
143
- >
144
- { title . template . map ( ( t , i ) => {
145
- const { type, text, class : className } = t ;
146
-
147
- const prevAllTextLength = title . template . slice ( 0 , i ) . reduce ( ( acc , cur ) => {
148
- return acc + ( cur . text ?. length || 0 ) ;
149
- } , 0 ) ;
150
-
151
- return text
152
- ? createElement (
153
- type ,
154
- { key : i , className } ,
155
- < TextUpTransitionView eachDelay = { 0.05 } initialDelay = { prevAllTextLength * 0.05 } >
156
- { text }
157
- </ TextUpTransitionView > ,
158
- )
159
- : null ;
160
- } ) }
161
- </ m . div >
162
-
163
- < m . div
164
- initial = { { y : 50 , opacity : 0 } }
165
- animate = { { y : 0 , opacity : 1 } }
166
- transition = { {
167
- type : 'spring' ,
168
- damping : 10 ,
169
- stiffness : 100 ,
170
- delay : titleAnimateD / 1000 ,
171
- } }
172
- className = "my-3 text-center lg:text-left lg:ml-24"
173
- >
174
- < span className = " opacity-70" > { description } </ span >
175
- </ m . div >
176
-
177
- < ul className = "center mx-[60px] mt-8 flex flex-wrap gap-6 lg:mx-auto lg:mt-24 lg:justify-start lg:gap-4 lg:ml-24" >
178
- { Object . entries ( socialIds ) . map ( ( [ type , id ] , index ) => (
179
- < m . li
180
- key = { type }
181
- initial = { { y : 50 , opacity : 0 } }
182
- animate = { { y : 0 , opacity : 1 } }
183
- transition = { {
184
- type : 'spring' ,
185
- damping : 10 ,
186
- stiffness : 100 ,
187
- delay : titleAnimateD / 1000 + index * 0.08 ,
188
- } }
189
- className = "inline-block"
190
- >
191
- < SocialIcon id = { id } type = { type } />
192
- </ m . li >
193
- ) ) }
194
- </ ul >
195
- </ >
196
- < div className = { cn ( 'lg:size-[300px]' , 'size-[200px]' , 'mt-24 lg:mt-0' ) } >
197
- < Image
198
- height = { 300 }
199
- width = { 300 }
200
- src = { avatar ! }
201
- alt = "Site Owner Avatar"
202
- className = { cn (
203
- 'aspect-square rounded-full border border-slate-200 dark:border-neutral-800' ,
204
- 'w-full' ,
205
- ) }
206
- />
207
- </ div >
208
- < Quote />
209
- </ TwoColumnLayout >
210
- </ div >
211
- ) ;
212
- } ;
213
-
214
- const Quote = ( ) => {
215
- const [ quote , setQuote ] = useState ( { hitokoto : '' , from : '' } ) ;
216
- const getQuote = async ( ) => {
217
- const { hitokoto, from } = await fetch ( '/api/quote' ) . then ( ( res ) => res . json ( ) ) ;
218
- setQuote ( { hitokoto, from } ) ;
219
- } ;
220
-
221
- useEffect ( ( ) => {
222
- getQuote ( ) ;
223
- } , [ ] ) ;
224
-
225
- return (
226
- < m . div
227
- initial = { { opacity : 0.0001 , y : 50 } }
228
- animate = { { opacity : 1 , y : 0 } }
229
- transition = { { type : 'spring' , damping : 10 , stiffness : 100 } }
230
- className = { cn (
231
- 'center inset-x-0 bottom-0 mt-14 flex flex-col lg:absolute lg:bottom-[-12px] group' ,
232
- 'center text-neutral-800/80 dark:text-neutral-200/80' ,
233
- ) }
234
- >
235
- < small className = " flex text-center items-center " >
236
- { quote . hitokoto }
237
- < span className = " text-center ml-4" > — { quote . from } </ span >
238
- < m . span onClick = { ( ) => getQuote ( ) } className = " flex items-center ml-3 cursor-pointer" >
239
- < i className = "i-mingcute-refresh-2-line invisible group-hover:visible" > </ i >
240
- </ m . span >
241
- </ small >
242
- < span className = "mt-6 animate-bounce" >
243
- < i className = "i-mingcute-right-line rotate-90 text-2xl" />
244
- </ span >
245
- </ m . div >
246
- ) ;
247
- } ;
248
-
249
18
const BlogCardList = ( ) => {
250
- const cards = [
251
- {
252
- title : 'Forest Adventure' ,
253
- src : 'https://images.unsplash.com/photo-1518710843675-2540dd79065c?q=80&w=3387&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D' ,
254
- } ,
255
- {
256
- title : 'Valley of life' ,
257
- src : 'https://images.unsplash.com/photo-1600271772470-bd22a42787b3?q=80&w=3072&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D' ,
258
- } ,
259
- {
260
- title : 'Sala behta hi jayega' ,
261
- src : 'https://images.unsplash.com/photo-1505142468610-359e7d316be0?q=80&w=3070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D' ,
262
- } ,
263
- {
264
- title : 'Camping is for pros' ,
265
- src : 'https://images.unsplash.com/photo-1486915309851-b0cc1f8a0084?q=80&w=3387&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D' ,
266
- } ,
267
- {
268
- title : 'The road not taken' ,
269
- src : 'https://images.unsplash.com/photo-1507041957456-9c397ce39c97?q=80&w=3456&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D' ,
270
- } ,
271
- {
272
- title : 'The First Rule' ,
273
- src : 'https://assets.aceternity.com/the-first-rule.png' ,
274
- } ,
275
- ] ;
276
-
277
19
return (
278
20
< div className = " w-full mt-10 md:mt-16 flex flex-col gap-y-8 px-4" >
279
21
< span className = "text-2xl font-medium leading-loose justify-center md:justify-start md:ml-4 font-mono gap-x-2 items-center flex" >
280
22
最近文章
281
23
< span className = " i-material-symbols-kid-star-outline cursor-pointer hover:rotate-[720deg] animate-ease-out duration-150" />
282
24
</ span >
283
- < FocusCards cards = { cards } />
284
- </ div >
285
- ) ;
286
- } ;
287
-
288
- const WindVane = ( ) => {
289
- return (
290
- < div className = " w-full mt-14 md:mt-20 flex flex-col gap-y-8 px-4" >
291
- < span className = "text-2xl flex justify-center items-center gap-x-2 text-center font-medium leading-loose font-mono " >
292
- 风向标
293
- < span className = " i-mingcute-navigation-line cursor-pointer hover:rotate-[360deg] animate-ease-out duration-200" />
294
- </ span >
295
- < div className = " center flex" >
296
- < ul className = " flex flex-col flex-wrap gap-2 gap-y-10 opacity-80 lg:flex-row" >
297
- { windsock . map ( ( item , index ) => {
298
- return (
299
- < m . li
300
- initial = { { opacity : 0.0001 , y : 15 } }
301
- viewport = { { once : true } }
302
- whileInView = { {
303
- opacity : 1 ,
304
- y : 0 ,
305
- transition : {
306
- stiffness : 641 ,
307
- damping : 23 ,
308
- mass : 3.9 ,
309
- type : 'spring' ,
310
- delay : index * 0.05 ,
311
- } ,
312
- } }
313
- transition = { {
314
- delay : 0.001 ,
315
- } }
316
- whileHover = { {
317
- y : - 6 ,
318
- transition : {
319
- stiffness : 641 ,
320
- damping : 23 ,
321
- mass : 4.9 ,
322
- type : 'spring' ,
323
- } ,
324
- } }
325
- key = { index }
326
- className = "flex items-center justify-between text-sm group"
327
- >
328
- < a
329
- href = { item . path }
330
- className = "flex items-center gap-4 text-neutral-800 duration-200 hover:!text-accent dark:text-neutral-200 group-hover:text-[var(--accent-color)]"
331
- >
332
- { createElement ( item . icon , {
333
- className : 'w-6 h-6 group-hover:text-[var(--accent-color)]' ,
334
- } ) }
335
- < span className = " group-hover:text-[var(--accent-color)]" > { item . title } </ span >
336
- </ a >
337
-
338
- { index != windsock . length - 1 && (
339
- < span className = "mx-4 hidden select-none lg:inline" > · </ span >
340
- ) }
341
- </ m . li >
342
- ) ;
343
- } ) }
344
- </ ul >
345
- </ div >
25
+ < FocusCards postCards = { postDataList } />
346
26
</ div >
347
27
) ;
348
28
} ;
0 commit comments