Skip to content

Commit 33c2a11

Browse files
committed
feat: postCard
1 parent 14d382c commit 33c2a11

File tree

10 files changed

+365
-430
lines changed

10 files changed

+365
-430
lines changed

markdown/index.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
"path": "yyblog.md",
44
"title": "yyblog",
55
"tag": "技术/react",
6-
"coverImage": "yyblog-cover.jpg"
6+
"coverImage": "https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/0b76076eab124179816d68b846280cdc~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgTWVnYXRyb25LaW5n:q75.awebp?rk3s=f64ab15b&x-expires=1731736114&x-signature=3ggdE9od5WvMZaVJ7VFlQtt48rA%3D"
77
},
88
{
99
"path": "react19.md",
1010
"title": "react19",
1111
"tag": "随笔/生活",
12-
"coverImage": "yyblog-cover.jpg"
12+
"coverImage": "https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/0b76076eab124179816d68b846280cdc~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgTWVnYXRyb25LaW5n:q75.awebp?rk3s=f64ab15b&x-expires=1731736114&x-signature=3ggdE9od5WvMZaVJ7VFlQtt48rA%3D"
1313
}
1414
]

src/app/(app)/(home)/page.tsx

Lines changed: 5 additions & 325 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,9 @@
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';
162
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';
175

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();
417

428
export default function Home() {
439
return (
@@ -49,300 +15,14 @@ export default function Home() {
4915
);
5016
}
5117

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-
24918
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-
27719
return (
27820
<div className=" w-full mt-10 md:mt-16 flex flex-col gap-y-8 px-4">
27921
<span className="text-2xl font-medium leading-loose justify-center md:justify-start md:ml-4 font-mono gap-x-2 items-center flex">
28022
最近文章
28123
<span className=" i-material-symbols-kid-star-outline cursor-pointer hover:rotate-[720deg] animate-ease-out duration-150" />
28224
</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} />
34626
</div>
34727
);
34828
};

0 commit comments

Comments
 (0)