feat: integrate Sanity client and enhance project showcase with dynamic data fetching
This commit is contained in:
parent
31dbaf0261
commit
1254192b13
@ -39,6 +39,7 @@
|
|||||||
"@radix-ui/react-toggle": "1.1.1",
|
"@radix-ui/react-toggle": "1.1.1",
|
||||||
"@radix-ui/react-toggle-group": "1.1.1",
|
"@radix-ui/react-toggle-group": "1.1.1",
|
||||||
"@radix-ui/react-tooltip": "1.1.6",
|
"@radix-ui/react-tooltip": "1.1.6",
|
||||||
|
"@sanity/image-url": "^1.1.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@ -51,6 +52,7 @@
|
|||||||
"lucide-react": "^0.454.0",
|
"lucide-react": "^0.454.0",
|
||||||
"next": "15.2.4",
|
"next": "15.2.4",
|
||||||
"next-intl": "^4.1.0",
|
"next-intl": "^4.1.0",
|
||||||
|
"next-sanity": "^9.11.1",
|
||||||
"next-themes": "latest",
|
"next-themes": "latest",
|
||||||
"react": "^19",
|
"react": "^19",
|
||||||
"react-day-picker": "8.10.1",
|
"react-day-picker": "8.10.1",
|
||||||
|
|||||||
8086
pnpm-lock.yaml
generated
8086
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,2 +1,3 @@
|
|||||||
onlyBuiltDependencies:
|
onlyBuiltDependencies:
|
||||||
|
- esbuild
|
||||||
- sharp
|
- sharp
|
||||||
|
|||||||
@ -149,7 +149,9 @@ export default function HomePage() {
|
|||||||
<GsapSkillsTree/>
|
<GsapSkillsTree/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div>
|
||||||
<GsapProjectsShowcase/>
|
<GsapProjectsShowcase/>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
{/* Projects Section with enhanced visuals */}
|
{/* Projects Section with enhanced visuals */}
|
||||||
{/*<ProjectsSection/>*/}
|
{/*<ProjectsSection/>*/}
|
||||||
|
|||||||
@ -74,12 +74,12 @@ export default function AboutSection() {
|
|||||||
|
|
||||||
<div className="space-y-4 text-lg text-muted-foreground">
|
<div className="space-y-4 text-lg text-muted-foreground">
|
||||||
<p>我是一名全栈开发者,专注于 Java/JavaScript 开发,目前正在转向 Kotlin/TypeScript
|
<p>我是一名全栈开发者,专注于 Java/JavaScript 开发,目前正在转向 Kotlin/TypeScript
|
||||||
全栈开发。</p>
|
全栈开发,对于 Web 与跨平台有自己的见解。</p>
|
||||||
<p>
|
<p>
|
||||||
我热爱创建优雅、高效的应用程序,无论是 Web 应用还是 Android
|
我热爱创建优雅、高效的应用程序,无论是 Web 应用还是 Android
|
||||||
应用。我相信良好的用户体验和干净的代码同样重要。
|
应用。对我来说良好的用户体验和干净的代码同样重要。
|
||||||
</p>
|
</p>
|
||||||
<p>目前我可能位于中国湖南长沙,与中南大学有关联。我活跃于开源社区,并在个人博客上分享技术文章。</p>
|
<p>目前我活跃于开源社区,并在个人博客上分享技术文章。</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
@ -128,10 +128,16 @@ export default function AboutSection() {
|
|||||||
<SkillBadge name="React"/>
|
<SkillBadge name="React"/>
|
||||||
<SkillBadge name="Next.js"/>
|
<SkillBadge name="Next.js"/>
|
||||||
<SkillBadge name="Vue.js"/>
|
<SkillBadge name="Vue.js"/>
|
||||||
<SkillBadge name="Spring Boot"/>
|
<SkillBadge name="Nuxt.js"/>
|
||||||
<SkillBadge name="TypeScript"/>
|
<SkillBadge name="TypeScript"/>
|
||||||
<SkillBadge name="JavaScript"/>
|
<SkillBadge name="JavaScript"/>
|
||||||
<SkillBadge name="Kotlin"/>
|
<SkillBadge name="Kotlin"/>
|
||||||
|
<SkillBadge name="Python"/>
|
||||||
|
<SkillBadge name="Jupyter Notebook"/>
|
||||||
|
<SkillBadge name="FastAPI"/>
|
||||||
|
<SkillBadge name="PyTorch"/>
|
||||||
|
<SkillBadge name="Spring Boot"/>
|
||||||
|
<SkillBadge name="Ktor"/>
|
||||||
<SkillBadge name="Java"/>
|
<SkillBadge name="Java"/>
|
||||||
<SkillBadge name="Jetpack Compose"/>
|
<SkillBadge name="Jetpack Compose"/>
|
||||||
</div>
|
</div>
|
||||||
@ -140,9 +146,13 @@ export default function AboutSection() {
|
|||||||
<div>
|
<div>
|
||||||
<p className="font-mono text-sm text-primary mb-2">{"// 开发工具"}</p>
|
<p className="font-mono text-sm text-primary mb-2">{"// 开发工具"}</p>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<SkillBadge name="WebStorm"/>
|
||||||
<SkillBadge name="VS Code"/>
|
<SkillBadge name="VS Code"/>
|
||||||
<SkillBadge name="IntelliJ IDEA"/>
|
<SkillBadge name="IntelliJ IDEA"/>
|
||||||
<SkillBadge name="Android Studio"/>
|
<SkillBadge name="Android Studio"/>
|
||||||
|
<SkillBadge name="RustRover"/>
|
||||||
|
<SkillBadge name="PyCharm"/>
|
||||||
|
<SkillBadge name="CLion"/>
|
||||||
<SkillBadge name="Git"/>
|
<SkillBadge name="Git"/>
|
||||||
<SkillBadge name="Figma"/>
|
<SkillBadge name="Figma"/>
|
||||||
<SkillBadge name="Docker"/>
|
<SkillBadge name="Docker"/>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import {motion} from "framer-motion";
|
import {motion} from "framer-motion";
|
||||||
import {InfiniteMovingCards} from "@/components/ui/infinite-moving-cards";
|
import {InfiniteMovingCards} from "@/components/ui/infinite-moving-cards";
|
||||||
|
|
||||||
interface CardItem{
|
interface CardItem {
|
||||||
quote: string;
|
quote: string;
|
||||||
name: string;
|
name: string;
|
||||||
title: string;
|
title: string;
|
||||||
@ -10,48 +10,88 @@ interface CardItem{
|
|||||||
|
|
||||||
const cardItems:CardItem[] = [
|
const cardItems:CardItem[] = [
|
||||||
{
|
{
|
||||||
quote: "Java",
|
quote: "湖南长沙,中南学子,逐梦全栈的 Archlinux 爱好者。",
|
||||||
name: "Java",
|
name: "grtsinry43",
|
||||||
title: "Java"
|
title: "坐标与身份:湖南长沙 | 中南大学 | 大二学生"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
quote: "JavaScript",
|
quote: "主攻前端 (Vue/React),心系 Kotlin/TypeScript 的未来。",
|
||||||
name: "JavaScript",
|
name: "技术栈 (当前):JavaScript, Vue.js, React.js",
|
||||||
title: "JavaScript"
|
title: "技术栈 (未来):Kotlin, TypeScript"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
quote: "TypeScript",
|
quote: "JetBrains 全家桶用户,曾是 Vim 的忠实粉丝。",
|
||||||
name: "TypeScript",
|
name: "开发工具:JetBrains IDE",
|
||||||
title: "TypeScript"
|
title: "曾用工具:Vim"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
quote: "Kotlin",
|
quote: "对 ML 怀揣兴趣,也曾涉猎 AE/C4D 视频渲染。",
|
||||||
name: "Kotlin",
|
name: "兴趣领域:机器学习",
|
||||||
title: "Kotlin"
|
title: "略懂技能:视频渲染 (AE, C4D)"
|
||||||
},
|
},
|
||||||
]
|
{
|
||||||
|
quote: "讨厌 NV 驱动的 Kernel Panic!Arch 用户的小执念。",
|
||||||
|
name: "系统偏好:Arch Linux",
|
||||||
|
title: "痛点:NVIDIA 驱动问题"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: "音游达人,活跃于 Arcaea, Phigros 等多个平台。",
|
||||||
|
name: "兴趣爱好:音游",
|
||||||
|
title: "涉猎音游:Arcaea, Phigros, Muse Dash, osu!, Malody, pjsk, Rizline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: "INFJ-T 型格,在内耗与渴望中寻找平衡。",
|
||||||
|
name: "性格特点:INFJ-T",
|
||||||
|
title: "内心状态:内省,共情,社恐与社交渴望并存"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: "自诩“屎山”制造者,却怀揣用代码改变世界的梦想。",
|
||||||
|
name: "开发理念:实用至上",
|
||||||
|
title: "开发目标:用技术创造价值"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: "重写博客与团委网站,在开源社区留下足迹。",
|
||||||
|
name: "开源项目:Grtblog, 中南大学团委网站",
|
||||||
|
title: "开源态度:积极参与,乐于分享"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: "年度关键词:迷茫求索,热爱坚守,于血泪中成长。",
|
||||||
|
name: "2024 年度总结",
|
||||||
|
title: "感悟:在迷茫中成长,因热爱而坚持"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: "新年 Flag:减肥至 60kg 以下,深耕前端框架。",
|
||||||
|
name: "2025 年度目标 (部分)",
|
||||||
|
title: "个人与技术双重提升"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote: "渴望成为有影响力的开源作者,在技术道路上不断探索。",
|
||||||
|
name: "未来愿景",
|
||||||
|
title: "成为独当一面的前端工程师与开源贡献者"
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const CardSection = () => {
|
const CardSection = () => {
|
||||||
return (
|
return (
|
||||||
<div className="w-full pb-6 bg-slate-950">
|
<div className="w-full pb-6 bg-slate-950">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 40 }}
|
initial={{opacity: 0, y: 40}}
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{opacity: 1, y: 0}}
|
||||||
transition={{ duration: 0.8 }}
|
transition={{duration: 0.8}}
|
||||||
viewport={{ once: true, amount: 0.3 }}
|
viewport={{once: true, amount: 0.3}}
|
||||||
className="flex items-center justify-center space-x-2 mb-12"
|
className="flex items-center justify-center space-x-2 mb-12"
|
||||||
>
|
>
|
||||||
<div className="h-px w-12 bg-primary/60" />
|
<div className="h-px w-12 bg-primary/60"/>
|
||||||
<h2 className="text-lg font-medium text-primary">我的关键词</h2>
|
<h2 className="text-lg font-medium text-primary">我的关键词</h2>
|
||||||
<div className="h-px w-12 bg-primary/60" />
|
<div className="h-px w-12 bg-primary/60"/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.h3
|
<motion.h3
|
||||||
className="text-3xl md:text-4xl font-bold tracking-tight text-center mb-16 text-white"
|
className="text-3xl md:text-4xl font-bold tracking-tight text-center mb-16 text-white"
|
||||||
initial={{ opacity: 0 }}
|
initial={{opacity: 0}}
|
||||||
whileInView={{ opacity: 1 }}
|
whileInView={{opacity: 1}}
|
||||||
transition={{ duration: 0.8, delay: 0.2 }}
|
transition={{duration: 0.8, delay: 0.2}}
|
||||||
viewport={{ once: true, amount: 0.3 }}
|
viewport={{once: true, amount: 0.3}}
|
||||||
>
|
>
|
||||||
我是一个<span className="text-primary">"成分复杂"</span>的人
|
我是一个<span className="text-primary">"成分复杂"</span>的人
|
||||||
</motion.h3>
|
</motion.h3>
|
||||||
|
|||||||
@ -242,7 +242,7 @@ export default function GsapPersonalIntro() {
|
|||||||
/>
|
/>
|
||||||
<AnimatedGradientText
|
<AnimatedGradientText
|
||||||
text="grtsinry43"
|
text="grtsinry43"
|
||||||
className="text-7xl font-bold tracking-tight"
|
className="text-7xl font-bold tracking-tight pb-2"
|
||||||
gradient="from-blue-600 via-purple-600 to-blue-600"
|
gradient="from-blue-600 via-purple-600 to-blue-600"
|
||||||
delay={0.5}
|
delay={0.5}
|
||||||
/>
|
/>
|
||||||
@ -265,10 +265,11 @@ export default function GsapPersonalIntro() {
|
|||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-lg text-muted-foreground">
|
<p className="text-lg text-muted-foreground">
|
||||||
我专注于构建优雅、高效的应用程序,无论是 Web 应用还是 Android 应用。
|
我是一名全栈开发者,热衷于创造出色的用户体验和高效的代码。
|
||||||
我相信良好的用户体验和干净的代码同样重要。
|
<br/>
|
||||||
|
喜欢探索新技术,分享见解,合作学习
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-4 items-center">
|
<div className="flex flex-wrap gap-4 items-center pb-4">
|
||||||
<motion.div
|
<motion.div
|
||||||
className="flex items-center gap-2 icon-item"
|
className="flex items-center gap-2 icon-item"
|
||||||
whileHover={{scale: 1.05, x: 5}}
|
whileHover={{scale: 1.05, x: 5}}
|
||||||
|
|||||||
@ -66,19 +66,19 @@ export default function PersonalitySection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="personality" ref={containerRef} className="relative min-h-screen py-24 md:py-32 overflow-hidden">
|
<section id="personality" ref={containerRef} className="relative min-h-screen py-24 md:py-32 overflow-hidden bg-neutral-950">
|
||||||
|
|
||||||
{/* Background elements */}
|
{/* Background elements */}
|
||||||
<div className="absolute inset-0 z-0 overflow-hidden">
|
<div className="absolute inset-0 z-0 overflow-hidden">
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute top-1/3 right-1/4 w-64 h-64 rounded-full bg-purple-500/5 blur-3xl"
|
className="absolute top-1/3 right-1/4 w-64 h-64 rounded-full bg-purple-900/30 blur-3xl"
|
||||||
style={{
|
style={{
|
||||||
y: useTransform(scrollYProgress, [0, 1], [0, -100]),
|
y: useTransform(scrollYProgress, [0, 1], [0, -100]),
|
||||||
x: useTransform(scrollYProgress, [0, 1], [0, 50]),
|
x: useTransform(scrollYProgress, [0, 1], [0, 50]),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute bottom-1/3 left-1/4 w-96 h-96 rounded-full bg-emerald-500/5 blur-3xl"
|
className="absolute bottom-1/3 left-1/4 w-96 h-96 rounded-full bg-emerald-900/30 blur-3xl"
|
||||||
style={{
|
style={{
|
||||||
y: useTransform(scrollYProgress, [0, 1], [0, -150]),
|
y: useTransform(scrollYProgress, [0, 1], [0, -150]),
|
||||||
x: useTransform(scrollYProgress, [0, 1], [0, -50]),
|
x: useTransform(scrollYProgress, [0, 1], [0, -50]),
|
||||||
@ -95,19 +95,19 @@ export default function PersonalitySection() {
|
|||||||
viewport={{ once: true, amount: 0.3 }}
|
viewport={{ once: true, amount: 0.3 }}
|
||||||
className="flex items-center justify-center space-x-2 mb-12"
|
className="flex items-center justify-center space-x-2 mb-12"
|
||||||
>
|
>
|
||||||
<div className="h-px w-12 bg-primary/60" />
|
<div className="h-px w-12 bg-blue-700/60" />
|
||||||
<h2 className="text-lg font-medium text-primary">我的性格</h2>
|
<h2 className="text-lg font-medium text-blue-400">我的性格</h2>
|
||||||
<div className="h-px w-12 bg-primary/60" />
|
<div className="h-px w-12 bg-blue-700/60" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.h3
|
<motion.h3
|
||||||
className="text-3xl md:text-4xl font-bold tracking-tight text-center mb-16"
|
className="text-3xl md:text-4xl font-bold tracking-tight text-center mb-16 text-neutral-100"
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
whileInView={{ opacity: 1 }}
|
whileInView={{ opacity: 1 }}
|
||||||
transition={{ duration: 0.8, delay: 0.2 }}
|
transition={{ duration: 0.8, delay: 0.2 }}
|
||||||
viewport={{ once: true, amount: 0.3 }}
|
viewport={{ once: true, amount: 0.3 }}
|
||||||
>
|
>
|
||||||
你会好奇<span className="text-primary">我的性格</span>
|
你会好奇<span className="text-blue-400">我的性格</span>
|
||||||
</motion.h3>
|
</motion.h3>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-16 items-center">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-16 items-center">
|
||||||
@ -116,7 +116,7 @@ export default function PersonalitySection() {
|
|||||||
variants={containerVariants}
|
variants={containerVariants}
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
animate={isInView ? "visible" : "hidden"}
|
animate={isInView ? "visible" : "hidden"}
|
||||||
className="space-y-4 text-lg text-neutral-700 dark:text-neutral-300"
|
className="space-y-4 text-lg text-neutral-200"
|
||||||
>
|
>
|
||||||
<motion.p variants={itemVariants} custom={1}>
|
<motion.p variants={itemVariants} custom={1}>
|
||||||
我的性格大部分情况下不可能不会有固定的表现,如果中将需要量化评价的话,我属于 16
|
我的性格大部分情况下不可能不会有固定的表现,如果中将需要量化评价的话,我属于 16
|
||||||
@ -124,10 +124,10 @@ export default function PersonalitySection() {
|
|||||||
INFJ。
|
INFJ。
|
||||||
</motion.p>
|
</motion.p>
|
||||||
<motion.p variants={itemVariants} custom={2}>
|
<motion.p variants={itemVariants} custom={2}>
|
||||||
INFJ的特行者在迷雾中寻找光芒,坚持自己的理想和信念,希望能为这个世界带来一些改变。
|
在迷雾中寻找光芒,坚持自己的理想和信念,希望能为这个世界带来一些改变。
|
||||||
</motion.p>
|
</motion.p>
|
||||||
<motion.p variants={itemVariants} custom={3}>
|
<motion.p variants={itemVariants} custom={3}>
|
||||||
如果上面这样一段话评价 infj 人格:
|
如果用这样一段话评价 infj 人格:
|
||||||
</motion.p>
|
</motion.p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -153,11 +153,11 @@ export default function PersonalitySection() {
|
|||||||
className="relative"
|
className="relative"
|
||||||
>
|
>
|
||||||
<blockquote
|
<blockquote
|
||||||
className="relative z-10 rounded-2xl bg-gradient-to-r from-emerald-50 to-blue-50 dark:from-emerald-900/30 dark:to-blue-900/30 p-8 italic text-neutral-700 dark:text-neutral-200 border-l-4 border-blue-500 shadow-lg">
|
className="relative z-10 rounded-2xl bg-gradient-to-r from-emerald-950 to-blue-950 p-8 italic text-neutral-200 border-l-4 border-blue-700 shadow-lg">
|
||||||
"明明拿了反派的成长剧本,却依旧想成为正道的光。"
|
"明明拿了反派的成长剧本,却依旧想成为正道的光。"
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute top-0 right-0 -mt-4 -mr-4 h-24 w-24 rounded-full bg-gradient-to-r from-emerald-500/10 to-blue-500/10 blur-xl"
|
className="absolute top-0 right-0 -mt-4 -mr-4 h-24 w-24 rounded-full bg-gradient-to-r from-emerald-800/30 to-blue-800/30 blur-xl"
|
||||||
animate={{
|
animate={{
|
||||||
scale: [1, 1.2, 1],
|
scale: [1, 1.2, 1],
|
||||||
opacity: [0.5, 0.8, 0.5],
|
opacity: [0.5, 0.8, 0.5],
|
||||||
@ -169,7 +169,7 @@ export default function PersonalitySection() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute bottom-0 left-0 -mb-4 -ml-4 h-24 w-24 rounded-full bg-gradient-to-r from-emerald-500/10 to-blue-500/10 blur-xl"
|
className="absolute bottom-0 left-0 -mb-4 -ml-4 h-24 w-24 rounded-full bg-gradient-to-r from-emerald-800/30 to-blue-800/30 blur-xl"
|
||||||
animate={{
|
animate={{
|
||||||
scale: [1, 1.2, 1],
|
scale: [1, 1.2, 1],
|
||||||
opacity: [0.5, 0.8, 0.5],
|
opacity: [0.5, 0.8, 0.5],
|
||||||
@ -188,7 +188,7 @@ export default function PersonalitySection() {
|
|||||||
initial={{opacity: 0, y: 20}}
|
initial={{opacity: 0, y: 20}}
|
||||||
animate={isInView ? {opacity: 1, y: 0} : {}}
|
animate={isInView ? {opacity: 1, y: 0} : {}}
|
||||||
transition={{duration: 0.6, delay: 0.7}}
|
transition={{duration: 0.6, delay: 0.7}}
|
||||||
className="text-xl font-bold mb-6"
|
className="text-xl font-bold mb-6 text-neutral-100"
|
||||||
>
|
>
|
||||||
INFJ 特质
|
INFJ 特质
|
||||||
</motion.h4>
|
</motion.h4>
|
||||||
@ -215,11 +215,11 @@ export default function PersonalitySection() {
|
|||||||
x: 10,
|
x: 10,
|
||||||
transition: {duration: 0.2},
|
transition: {duration: 0.2},
|
||||||
}}
|
}}
|
||||||
className="flex items-center p-4 rounded-xl bg-white dark:bg-neutral-900 shadow-sm border border-neutral-200 dark:border-neutral-800 transform-gpu"
|
className="flex items-center p-4 rounded-xl bg-neutral-900 shadow-sm border border-neutral-800 transform-gpu"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="h-3 w-3 rounded-full bg-gradient-to-r from-emerald-500 to-blue-500 mr-4"/>
|
className="h-3 w-3 rounded-full bg-gradient-to-r from-emerald-500 to-blue-500 mr-4"/>
|
||||||
<span className="text-neutral-700 dark:text-neutral-300">{trait}</span>
|
<span className="text-neutral-200">{trait}</span>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -238,9 +238,9 @@ export default function PersonalitySection() {
|
|||||||
rotate: 0,
|
rotate: 0,
|
||||||
transition: {duration: 0.3},
|
transition: {duration: 0.3},
|
||||||
}}
|
}}
|
||||||
className="bg-white dark:bg-neutral-900 p-8 rounded-2xl border border-neutral-200 dark:border-neutral-800 shadow-lg transform-gpu"
|
className="bg-neutral-900 p-8 rounded-2xl border border-neutral-800 shadow-lg transform-gpu"
|
||||||
>
|
>
|
||||||
<h4 className="text-xl font-bold mb-6 text-center">INFJ 性格雷达图</h4>
|
<h4 className="text-xl font-bold mb-6 text-center text-neutral-100">INFJ 性格雷达图</h4>
|
||||||
<div className="aspect-square relative z-10">
|
<div className="aspect-square relative z-10">
|
||||||
<svg viewBox="0 0 200 200" className="w-full h-full">
|
<svg viewBox="0 0 200 200" className="w-full h-full">
|
||||||
<defs>
|
<defs>
|
||||||
@ -256,22 +256,22 @@ export default function PersonalitySection() {
|
|||||||
</filter>
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
<circle cx="100" cy="100" r="80" fill="none" stroke="rgba(100,116,139,0.2)"
|
<circle cx="100" cy="100" r="80" fill="none" stroke="rgba(51,65,85,0.4)"
|
||||||
strokeWidth="1"/>
|
strokeWidth="1"/>
|
||||||
<circle cx="100" cy="100" r="60" fill="none" stroke="rgba(100,116,139,0.2)"
|
<circle cx="100" cy="100" r="60" fill="none" stroke="rgba(51,65,85,0.4)"
|
||||||
strokeWidth="1"/>
|
strokeWidth="1"/>
|
||||||
<circle cx="100" cy="100" r="40" fill="none" stroke="rgba(100,116,139,0.2)"
|
<circle cx="100" cy="100" r="40" fill="none" stroke="rgba(51,65,85,0.4)"
|
||||||
strokeWidth="1"/>
|
strokeWidth="1"/>
|
||||||
<circle cx="100" cy="100" r="20" fill="none" stroke="rgba(100,116,139,0.2)"
|
<circle cx="100" cy="100" r="20" fill="none" stroke="rgba(51,65,85,0.4)"
|
||||||
strokeWidth="1"/>
|
strokeWidth="1"/>
|
||||||
|
|
||||||
<line x1="100" y1="20" x2="100" y2="180" stroke="rgba(100,116,139,0.2)"
|
<line x1="100" y1="20" x2="100" y2="180" stroke="rgba(51,65,85,0.4)"
|
||||||
strokeWidth="1"/>
|
strokeWidth="1"/>
|
||||||
<line x1="20" y1="100" x2="180" y2="100" stroke="rgba(100,116,139,0.2)"
|
<line x1="20" y1="100" x2="180" y2="100" stroke="rgba(51,65,85,0.4)"
|
||||||
strokeWidth="1"/>
|
strokeWidth="1"/>
|
||||||
<line x1="37" y1="37" x2="163" y2="163" stroke="rgba(100,116,139,0.2)"
|
<line x1="37" y1="37" x2="163" y2="163" stroke="rgba(51,65,85,0.4)"
|
||||||
strokeWidth="1"/>
|
strokeWidth="1"/>
|
||||||
<line x1="37" y1="163" x2="163" y2="37" stroke="rgba(100,116,139,0.2)"
|
<line x1="37" y1="163" x2="163" y2="37" stroke="rgba(51,65,85,0.4)"
|
||||||
strokeWidth="1"/>
|
strokeWidth="1"/>
|
||||||
|
|
||||||
<motion.polygon
|
<motion.polygon
|
||||||
@ -295,32 +295,31 @@ export default function PersonalitySection() {
|
|||||||
filter="url(#personalityGlow)"
|
filter="url(#personalityGlow)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<text x="100" y="15" textAnchor="middle" fill="currentColor" fontSize="8">
|
<text x="100" y="15" textAnchor="middle" fill="#e0e7ef" fontSize="8">
|
||||||
理想主义
|
理想主义
|
||||||
</text>
|
</text>
|
||||||
<text x="185" y="100" textAnchor="start" fill="currentColor" fontSize="8">
|
<text x="185" y="100" textAnchor="start" fill="#e0e7ef" fontSize="8">
|
||||||
创造力
|
创造力
|
||||||
</text>
|
</text>
|
||||||
<text x="100" y="190" textAnchor="middle" fill="currentColor" fontSize="8">
|
<text x="100" y="190" textAnchor="middle" fill="#e0e7ef" fontSize="8">
|
||||||
同理心
|
同理心
|
||||||
</text>
|
</text>
|
||||||
<text x="15" y="100" textAnchor="end" fill="currentColor" fontSize="8">
|
<text x="15" y="100" textAnchor="end" fill="#e0e7ef" fontSize="8">
|
||||||
内省
|
内省
|
||||||
</text>
|
</text>
|
||||||
<text x="37" y="37" textAnchor="middle" fill="currentColor" fontSize="8">
|
<text x="37" y="37" textAnchor="middle" fill="#e0e7ef" fontSize="8">
|
||||||
洞察力
|
洞察力
|
||||||
</text>
|
</text>
|
||||||
<text x="37" y="163" textAnchor="middle" fill="currentColor" fontSize="8">
|
<text x="37" y="163" textAnchor="middle" fill="#e0e7ef" fontSize="8">
|
||||||
坚持
|
坚持
|
||||||
</text>
|
</text>
|
||||||
<text x="163" y="37" textAnchor="middle" fill="currentColor" fontSize="8">
|
<text x="163" y="37" textAnchor="middle" fill="#e0e7ef" fontSize="8">
|
||||||
完美主义
|
完美主义
|
||||||
</text>
|
</text>
|
||||||
<text x="163" y="163" textAnchor="middle" fill="currentColor" fontSize="8">
|
<text x="163" y="163" textAnchor="middle" fill="#e0e7ef" fontSize="8">
|
||||||
敏感
|
敏感
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
{/* Animated points */}
|
|
||||||
{[
|
{[
|
||||||
{cx: 100, cy: 30},
|
{cx: 100, cy: 30},
|
||||||
{cx: 150, cy: 45},
|
{cx: 150, cy: 45},
|
||||||
@ -374,24 +373,24 @@ export default function PersonalitySection() {
|
|||||||
scale: 1.05,
|
scale: 1.05,
|
||||||
transition: {duration: 0.3},
|
transition: {duration: 0.3},
|
||||||
}}
|
}}
|
||||||
className="bg-white dark:bg-neutral-900 p-8 rounded-2xl border border-neutral-200 dark:border-neutral-800 shadow-lg"
|
className="bg-neutral-900 p-8 rounded-2xl border border-neutral-800 shadow-lg"
|
||||||
>
|
>
|
||||||
<h4 className="text-xl font-bold mb-6">INFJ 分布</h4>
|
<h4 className="text-xl font-bold mb-6 text-neutral-100">INFJ 分布</h4>
|
||||||
<div className="relative pt-8">
|
<div className="relative pt-8">
|
||||||
<div
|
<div
|
||||||
className="absolute top-0 left-0 w-full flex justify-between text-xs text-neutral-500">
|
className="absolute top-0 left-0 w-full flex justify-between text-xs text-neutral-400">
|
||||||
<span>0%</span>
|
<span>0%</span>
|
||||||
<span>1%</span>
|
<span>1%</span>
|
||||||
<span>2%</span>
|
<span>2%</span>
|
||||||
<span>3%</span>
|
<span>3%</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="h-16 bg-gradient-to-r from-emerald-50 to-blue-50 dark:from-emerald-900/30 dark:to-blue-900/30 rounded-lg relative">
|
className="h-16 bg-gradient-to-r from-emerald-950 to-blue-950 rounded-lg relative">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{width: 0}}
|
initial={{width: 0}}
|
||||||
animate={isInView ? {width: "16%"} : {}}
|
animate={isInView ? {width: "16%"} : {}}
|
||||||
transition={{duration: 1.5, delay: 1.2, ease: "easeOut"}}
|
transition={{duration: 1.5, delay: 1.2, ease: "easeOut"}}
|
||||||
className="absolute top-0 left-0 h-full bg-gradient-to-r from-emerald-500 to-blue-500 rounded-lg opacity-70"
|
className="absolute top-0 left-0 h-full bg-gradient-to-r from-emerald-600 to-blue-700 rounded-lg opacity-80"
|
||||||
/>
|
/>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{opacity: 0, scale: 0}}
|
initial={{opacity: 0, scale: 0}}
|
||||||
@ -408,7 +407,7 @@ export default function PersonalitySection() {
|
|||||||
}
|
}
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
className="absolute top-1/2 left-[16%] -translate-x-1/2 -translate-y-1/2 h-8 w-8 rounded-full bg-white dark:bg-neutral-800 border-2 border-emerald-500 flex items-center justify-center text-xs font-bold text-emerald-600 dark:text-emerald-400"
|
className="absolute top-1/2 left-[16%] -translate-x-1/2 -translate-y-1/2 h-8 w-8 rounded-full bg-neutral-900 border-2 border-emerald-500 flex items-center justify-center text-xs font-bold text-emerald-400"
|
||||||
>
|
>
|
||||||
1.5%
|
1.5%
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -430,8 +429,8 @@ export default function PersonalitySection() {
|
|||||||
className="absolute top-full left-[16%] -translate-x-1/2 mt-2 text-center"
|
className="absolute top-full left-[16%] -translate-x-1/2 mt-2 text-center"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="text-sm font-medium text-neutral-700 dark:text-neutral-300">INFJ</span>
|
className="text-sm font-medium text-neutral-200">INFJ</span>
|
||||||
<p className="text-xs text-neutral-500">全球人口比例</p>
|
<p className="text-xs text-neutral-400">全球人口比例</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -444,4 +443,3 @@ export default function PersonalitySection() {
|
|||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,62 +6,47 @@ import {Github, ExternalLink, ArrowRight} from "lucide-react"
|
|||||||
import {Button} from "@/components/ui/button"
|
import {Button} from "@/components/ui/button"
|
||||||
import {Badge} from "@/components/ui/badge"
|
import {Badge} from "@/components/ui/badge"
|
||||||
import {useTheme} from "next-themes"
|
import {useTheme} from "next-themes"
|
||||||
|
import {client} from "@/sanity/client";
|
||||||
|
import {SanityDocument} from "next-sanity"
|
||||||
|
import imageUrlBuilder from "@sanity/image-url"
|
||||||
|
import type {SanityImageSource} from "@sanity/image-url/lib/types/types"
|
||||||
|
|
||||||
interface Project {
|
interface Project {
|
||||||
id: number
|
_id: number
|
||||||
title: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
tags: string[]
|
techStack: string
|
||||||
image: string
|
cover: any // Sanity image object
|
||||||
githubUrl?: string
|
githubUrl?: string
|
||||||
liveUrl?: string
|
deployUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PROJECTS_QUERY = `*[_type == "project"]|order(publishedAt desc)[0...12]
|
||||||
|
{_id,name,description,githubUrl,deployUrl,progress,techStack,publishedAt,cover}`;
|
||||||
|
|
||||||
|
const options = {next: {revalidate: 30}};
|
||||||
|
|
||||||
|
const {projectId, dataset} = client.config()
|
||||||
|
const urlFor = (source: SanityImageSource) =>
|
||||||
|
projectId && dataset
|
||||||
|
? imageUrlBuilder({projectId, dataset}).image(source)
|
||||||
|
: null
|
||||||
|
|
||||||
export default function GsapProjectsShowcase() {
|
export default function GsapProjectsShowcase() {
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
const horizontalRef = useRef<HTMLDivElement>(null)
|
const horizontalRef = useRef<HTMLDivElement>(null)
|
||||||
const [containerWidth, setContainerWidth] = useState(0)
|
const [containerWidth, setContainerWidth] = useState(0)
|
||||||
const [isInView, setIsInView] = useState(false)
|
const [isInView, setIsInView] = useState(false)
|
||||||
const {theme} = useTheme()
|
const {theme} = useTheme()
|
||||||
|
const [projects, setProjects] = useState<Project[]>([])
|
||||||
|
|
||||||
// Project data
|
useEffect(() => {
|
||||||
const projects: Project[] = [
|
const fetchData = async () => {
|
||||||
{
|
const data = await client.fetch<Project[]>(PROJECTS_QUERY, {}, options);
|
||||||
id: 1,
|
setProjects(data);
|
||||||
title: "grtblog",
|
};
|
||||||
description:
|
fetchData();
|
||||||
"个人博客站点,一站式快速解决方案—一套源,扩展性强,快速部署的博客框架,使用 Nextjs + SpringBoot 构建,致力于轻松搭建个性化你的博客站点",
|
}, []);
|
||||||
tags: ["React", "Next.js", "Spring Boot", "Umi.js", "Socket.io", "Radix UI"],
|
|
||||||
image: "/placeholder.svg?height=600&width=800",
|
|
||||||
githubUrl: "https://github.com/grtsinry43/grtblog",
|
|
||||||
liveUrl: "https://grtblog.js.org",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: "csu-dynamic-youth",
|
|
||||||
description: "途有青,去追山!学校官微毕业节的微信网页程序,统计参与次数与时长,生成排行榜,完成完成打卡",
|
|
||||||
tags: ["微信小程序", "Vue.js", "微信API"],
|
|
||||||
image: "/placeholder.svg?height=600&width=800",
|
|
||||||
githubUrl: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: "Portfolio Website",
|
|
||||||
description: "个人作品集网站,展示我的项目和技能,使用 Next.js 和 Framer Motion 构建",
|
|
||||||
tags: ["Next.js", "Framer Motion", "Tailwind CSS", "TypeScript"],
|
|
||||||
image: "/placeholder.svg?height=600&width=800",
|
|
||||||
githubUrl: "#",
|
|
||||||
liveUrl: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: "AI Chat Application",
|
|
||||||
description: "基于人工智能的聊天应用,支持多种语言和自然语言处理",
|
|
||||||
tags: ["React", "Node.js", "OpenAI API", "WebSockets"],
|
|
||||||
image: "/placeholder.svg?height=600&width=800",
|
|
||||||
githubUrl: "#",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
// 检测元素是否在视口中
|
// 检测元素是否在视口中
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -88,8 +73,7 @@ export default function GsapProjectsShowcase() {
|
|||||||
// 计算水平滚动容器的宽度
|
// 计算水平滚动容器的宽度
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (horizontalRef.current) {
|
if (horizontalRef.current) {
|
||||||
// 计算所有项目卡片的总宽度 + 间距
|
const totalWidth = projects.length * 600 + (projects.length - 1) * 40 + 300 + 40 + 800
|
||||||
const totalWidth = projects.length * 600 + (projects.length - 1) * 40
|
|
||||||
setContainerWidth(totalWidth)
|
setContainerWidth(totalWidth)
|
||||||
}
|
}
|
||||||
}, [projects.length])
|
}, [projects.length])
|
||||||
@ -193,7 +177,6 @@ export default function GsapProjectsShowcase() {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tags) {
|
if (tags) {
|
||||||
gsap.fromTo(
|
gsap.fromTo(
|
||||||
tags,
|
tags,
|
||||||
@ -212,7 +195,6 @@ export default function GsapProjectsShowcase() {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (links) {
|
if (links) {
|
||||||
gsap.fromTo(
|
gsap.fromTo(
|
||||||
links,
|
links,
|
||||||
@ -234,7 +216,6 @@ export default function GsapProjectsShowcase() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// 清理动画
|
|
||||||
scrollTween.kill()
|
scrollTween.kill()
|
||||||
ScrollTrigger.getAll().forEach((trigger) => trigger.kill())
|
ScrollTrigger.getAll().forEach((trigger) => trigger.kill())
|
||||||
}
|
}
|
||||||
@ -281,7 +262,7 @@ export default function GsapProjectsShowcase() {
|
|||||||
transition={{duration: 0.8, delay: 0.3}}
|
transition={{duration: 0.8, delay: 0.3}}
|
||||||
viewport={{once: true, amount: 0.3}}
|
viewport={{once: true, amount: 0.3}}
|
||||||
>
|
>
|
||||||
向下滚动探索我的项目作品集 - 体验视差滚动效果
|
向下滚动探索我的项目作品集,包含了多个领域的探索~
|
||||||
</motion.p>
|
</motion.p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -291,27 +272,31 @@ export default function GsapProjectsShowcase() {
|
|||||||
ref={horizontalRef}
|
ref={horizontalRef}
|
||||||
className="absolute top-0 left-0 h-screen w-fit flex items-center gap-10 pl-[calc(50vw-300px)] pr-[100px] pt-[250px]"
|
className="absolute top-0 left-0 h-screen w-fit flex items-center gap-10 pl-[calc(50vw-300px)] pr-[100px] pt-[250px]"
|
||||||
>
|
>
|
||||||
{projects.map((project, index) => (
|
{projects.map((project, index) => {
|
||||||
|
const coverUrl = project.cover
|
||||||
|
? urlFor(project.cover)?.width(600).height(500).url()
|
||||||
|
: "/placeholder.svg"
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={project.id}
|
key={project._id}
|
||||||
className="project-card flex-shrink-0 w-[600px] h-[500px] bg-card/90 backdrop-blur-sm rounded-2xl overflow-hidden shadow-xl border border-border/50 relative"
|
className="project-card flex-shrink-0 w-[600px] h-[500px] bg-card/90 backdrop-blur-sm rounded-2xl overflow-hidden shadow-xl border border-border/50 relative"
|
||||||
>
|
>
|
||||||
<div className="project-image absolute inset-0 z-0">
|
<div className="project-image absolute inset-0 z-0">
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 bg-gradient-to-t from-card via-card/80 to-transparent z-10"/>
|
className="absolute inset-0 bg-gradient-to-t from-card via-card/80 to-transparent z-10"/>
|
||||||
<img
|
<img
|
||||||
src={project.image || "/placeholder.svg"}
|
src={coverUrl}
|
||||||
alt={project.title}
|
alt={project.name}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative z-20 flex flex-col justify-end h-full p-8">
|
<div className="relative z-20 flex flex-col justify-end h-full p-8">
|
||||||
<h4 className="project-title text-3xl font-bold mb-4">{project.title}</h4>
|
<h4 className="project-title text-3xl font-bold mb-4">{project.name}</h4>
|
||||||
<p className="project-desc text-lg text-muted-foreground mb-6">{project.description}</p>
|
<p className="project-desc text-lg text-muted-foreground mb-6">{project.description}</p>
|
||||||
|
|
||||||
<div className="project-tags flex flex-wrap gap-2 mb-6">
|
<div className="project-tags flex flex-wrap gap-2 mb-6">
|
||||||
{project.tags.map((tag) => (
|
{project.techStack.split(',').map((tag: string) => (
|
||||||
<Badge key={tag} variant="outline"
|
<Badge key={tag} variant="outline"
|
||||||
className="bg-primary/10 text-primary border-primary/20">
|
className="bg-primary/10 text-primary border-primary/20">
|
||||||
{tag}
|
{tag}
|
||||||
@ -334,10 +319,10 @@ export default function GsapProjectsShowcase() {
|
|||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{project.liveUrl && (
|
{project.deployUrl && (
|
||||||
<Button asChild size="sm" variant="ghost">
|
<Button asChild size="sm" variant="ghost">
|
||||||
<a
|
<a
|
||||||
href={project.liveUrl}
|
href={project.deployUrl}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-2"
|
className="flex items-center gap-2"
|
||||||
@ -355,7 +340,8 @@ export default function GsapProjectsShowcase() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
{/* 结束提示 */}
|
{/* 结束提示 */}
|
||||||
<div className="flex-shrink-0 w-[300px] h-[500px] flex items-center justify-center">
|
<div className="flex-shrink-0 w-[300px] h-[500px] flex items-center justify-center">
|
||||||
@ -363,7 +349,7 @@ export default function GsapProjectsShowcase() {
|
|||||||
<p className="text-muted-foreground mb-4">想了解更多项目?</p>
|
<p className="text-muted-foreground mb-4">想了解更多项目?</p>
|
||||||
<Button asChild>
|
<Button asChild>
|
||||||
<a href="#contact" className="flex items-center gap-2">
|
<a href="#contact" className="flex items-center gap-2">
|
||||||
联系我 <ArrowRight className="h-4 w-4"/>
|
转到项目页面 <ArrowRight className="h-4 w-4"/>
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
8
src/sanity/client.ts
Normal file
8
src/sanity/client.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import {createClient} from "next-sanity";
|
||||||
|
|
||||||
|
export const client = createClient({
|
||||||
|
projectId: "3p9gcb1i",
|
||||||
|
dataset: "production",
|
||||||
|
apiVersion: "2024-01-01",
|
||||||
|
useCdn: false,
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user