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-group": "1.1.1",
|
||||
"@radix-ui/react-tooltip": "1.1.6",
|
||||
"@sanity/image-url": "^1.1.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@ -51,6 +52,7 @@
|
||||
"lucide-react": "^0.454.0",
|
||||
"next": "15.2.4",
|
||||
"next-intl": "^4.1.0",
|
||||
"next-sanity": "^9.11.1",
|
||||
"next-themes": "latest",
|
||||
"react": "^19",
|
||||
"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:
|
||||
- esbuild
|
||||
- sharp
|
||||
|
||||
@ -149,7 +149,9 @@ export default function HomePage() {
|
||||
<GsapSkillsTree/>
|
||||
</motion.div>
|
||||
|
||||
<GsapProjectsShowcase/>
|
||||
<motion.div>
|
||||
<GsapProjectsShowcase/>
|
||||
</motion.div>
|
||||
|
||||
{/* Projects Section with enhanced visuals */}
|
||||
{/*<ProjectsSection/>*/}
|
||||
|
||||
@ -74,12 +74,12 @@ export default function AboutSection() {
|
||||
|
||||
<div className="space-y-4 text-lg text-muted-foreground">
|
||||
<p>我是一名全栈开发者,专注于 Java/JavaScript 开发,目前正在转向 Kotlin/TypeScript
|
||||
全栈开发。</p>
|
||||
全栈开发,对于 Web 与跨平台有自己的见解。</p>
|
||||
<p>
|
||||
我热爱创建优雅、高效的应用程序,无论是 Web 应用还是 Android
|
||||
应用。我相信良好的用户体验和干净的代码同样重要。
|
||||
应用。对我来说良好的用户体验和干净的代码同样重要。
|
||||
</p>
|
||||
<p>目前我可能位于中国湖南长沙,与中南大学有关联。我活跃于开源社区,并在个人博客上分享技术文章。</p>
|
||||
<p>目前我活跃于开源社区,并在个人博客上分享技术文章。</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
@ -128,10 +128,16 @@ export default function AboutSection() {
|
||||
<SkillBadge name="React"/>
|
||||
<SkillBadge name="Next.js"/>
|
||||
<SkillBadge name="Vue.js"/>
|
||||
<SkillBadge name="Spring Boot"/>
|
||||
<SkillBadge name="Nuxt.js"/>
|
||||
<SkillBadge name="TypeScript"/>
|
||||
<SkillBadge name="JavaScript"/>
|
||||
<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="Jetpack Compose"/>
|
||||
</div>
|
||||
@ -140,9 +146,13 @@ export default function AboutSection() {
|
||||
<div>
|
||||
<p className="font-mono text-sm text-primary mb-2">{"// 开发工具"}</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<SkillBadge name="WebStorm"/>
|
||||
<SkillBadge name="VS Code"/>
|
||||
<SkillBadge name="IntelliJ IDEA"/>
|
||||
<SkillBadge name="Android Studio"/>
|
||||
<SkillBadge name="RustRover"/>
|
||||
<SkillBadge name="PyCharm"/>
|
||||
<SkillBadge name="CLion"/>
|
||||
<SkillBadge name="Git"/>
|
||||
<SkillBadge name="Figma"/>
|
||||
<SkillBadge name="Docker"/>
|
||||
|
||||
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import {motion} from "framer-motion";
|
||||
import {InfiniteMovingCards} from "@/components/ui/infinite-moving-cards";
|
||||
|
||||
interface CardItem{
|
||||
interface CardItem {
|
||||
quote: string;
|
||||
name: string;
|
||||
title: string;
|
||||
@ -10,48 +10,88 @@ interface CardItem{
|
||||
|
||||
const cardItems:CardItem[] = [
|
||||
{
|
||||
quote: "Java",
|
||||
name: "Java",
|
||||
title: "Java"
|
||||
quote: "湖南长沙,中南学子,逐梦全栈的 Archlinux 爱好者。",
|
||||
name: "grtsinry43",
|
||||
title: "坐标与身份:湖南长沙 | 中南大学 | 大二学生"
|
||||
},
|
||||
{
|
||||
quote: "JavaScript",
|
||||
name: "JavaScript",
|
||||
title: "JavaScript"
|
||||
quote: "主攻前端 (Vue/React),心系 Kotlin/TypeScript 的未来。",
|
||||
name: "技术栈 (当前):JavaScript, Vue.js, React.js",
|
||||
title: "技术栈 (未来):Kotlin, TypeScript"
|
||||
},
|
||||
{
|
||||
quote: "TypeScript",
|
||||
name: "TypeScript",
|
||||
title: "TypeScript"
|
||||
quote: "JetBrains 全家桶用户,曾是 Vim 的忠实粉丝。",
|
||||
name: "开发工具:JetBrains IDE",
|
||||
title: "曾用工具:Vim"
|
||||
},
|
||||
{
|
||||
quote: "Kotlin",
|
||||
name: "Kotlin",
|
||||
title: "Kotlin"
|
||||
quote: "对 ML 怀揣兴趣,也曾涉猎 AE/C4D 视频渲染。",
|
||||
name: "兴趣领域:机器学习",
|
||||
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 = () => {
|
||||
return (
|
||||
<div className="w-full pb-6 bg-slate-950">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8 }}
|
||||
viewport={{ once: true, amount: 0.3 }}
|
||||
initial={{opacity: 0, y: 40}}
|
||||
whileInView={{opacity: 1, y: 0}}
|
||||
transition={{duration: 0.8}}
|
||||
viewport={{once: true, amount: 0.3}}
|
||||
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>
|
||||
<div className="h-px w-12 bg-primary/60" />
|
||||
<div className="h-px w-12 bg-primary/60"/>
|
||||
</motion.div>
|
||||
|
||||
<motion.h3
|
||||
className="text-3xl md:text-4xl font-bold tracking-tight text-center mb-16 text-white"
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
viewport={{ once: true, amount: 0.3 }}
|
||||
initial={{opacity: 0}}
|
||||
whileInView={{opacity: 1}}
|
||||
transition={{duration: 0.8, delay: 0.2}}
|
||||
viewport={{once: true, amount: 0.3}}
|
||||
>
|
||||
我是一个<span className="text-primary">"成分复杂"</span>的人
|
||||
</motion.h3>
|
||||
|
||||
@ -242,7 +242,7 @@ export default function GsapPersonalIntro() {
|
||||
/>
|
||||
<AnimatedGradientText
|
||||
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"
|
||||
delay={0.5}
|
||||
/>
|
||||
@ -265,10 +265,11 @@ export default function GsapPersonalIntro() {
|
||||
</h4>
|
||||
</div>
|
||||
<p className="text-lg text-muted-foreground">
|
||||
我专注于构建优雅、高效的应用程序,无论是 Web 应用还是 Android 应用。
|
||||
我相信良好的用户体验和干净的代码同样重要。
|
||||
我是一名全栈开发者,热衷于创造出色的用户体验和高效的代码。
|
||||
<br/>
|
||||
喜欢探索新技术,分享见解,合作学习
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-4 items-center">
|
||||
<div className="flex flex-wrap gap-4 items-center pb-4">
|
||||
<motion.div
|
||||
className="flex items-center gap-2 icon-item"
|
||||
whileHover={{scale: 1.05, x: 5}}
|
||||
|
||||
@ -66,19 +66,19 @@ export default function PersonalitySection() {
|
||||
}
|
||||
|
||||
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 */}
|
||||
<div className="absolute inset-0 z-0 overflow-hidden">
|
||||
<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={{
|
||||
y: useTransform(scrollYProgress, [0, 1], [0, -100]),
|
||||
x: useTransform(scrollYProgress, [0, 1], [0, 50]),
|
||||
}}
|
||||
/>
|
||||
<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={{
|
||||
y: useTransform(scrollYProgress, [0, 1], [0, -150]),
|
||||
x: useTransform(scrollYProgress, [0, 1], [0, -50]),
|
||||
@ -95,19 +95,19 @@ export default function PersonalitySection() {
|
||||
viewport={{ once: true, amount: 0.3 }}
|
||||
className="flex items-center justify-center space-x-2 mb-12"
|
||||
>
|
||||
<div className="h-px w-12 bg-primary/60" />
|
||||
<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-blue-700/60" />
|
||||
<h2 className="text-lg font-medium text-blue-400">我的性格</h2>
|
||||
<div className="h-px w-12 bg-blue-700/60" />
|
||||
</motion.div>
|
||||
|
||||
<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 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
transition={{ duration: 0.8, delay: 0.2 }}
|
||||
viewport={{ once: true, amount: 0.3 }}
|
||||
>
|
||||
你会好奇<span className="text-primary">我的性格</span>
|
||||
你会好奇<span className="text-blue-400">我的性格</span>
|
||||
</motion.h3>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-16 items-center">
|
||||
@ -116,7 +116,7 @@ export default function PersonalitySection() {
|
||||
variants={containerVariants}
|
||||
initial="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}>
|
||||
我的性格大部分情况下不可能不会有固定的表现,如果中将需要量化评价的话,我属于 16
|
||||
@ -124,10 +124,10 @@ export default function PersonalitySection() {
|
||||
INFJ。
|
||||
</motion.p>
|
||||
<motion.p variants={itemVariants} custom={2}>
|
||||
INFJ的特行者在迷雾中寻找光芒,坚持自己的理想和信念,希望能为这个世界带来一些改变。
|
||||
在迷雾中寻找光芒,坚持自己的理想和信念,希望能为这个世界带来一些改变。
|
||||
</motion.p>
|
||||
<motion.p variants={itemVariants} custom={3}>
|
||||
如果上面这样一段话评价 infj 人格:
|
||||
如果用这样一段话评价 infj 人格:
|
||||
</motion.p>
|
||||
</motion.div>
|
||||
|
||||
@ -153,11 +153,11 @@ export default function PersonalitySection() {
|
||||
className="relative"
|
||||
>
|
||||
<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>
|
||||
<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={{
|
||||
scale: [1, 1.2, 1],
|
||||
opacity: [0.5, 0.8, 0.5],
|
||||
@ -169,7 +169,7 @@ export default function PersonalitySection() {
|
||||
}}
|
||||
/>
|
||||
<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={{
|
||||
scale: [1, 1.2, 1],
|
||||
opacity: [0.5, 0.8, 0.5],
|
||||
@ -188,7 +188,7 @@ export default function PersonalitySection() {
|
||||
initial={{opacity: 0, y: 20}}
|
||||
animate={isInView ? {opacity: 1, y: 0} : {}}
|
||||
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 特质
|
||||
</motion.h4>
|
||||
@ -215,11 +215,11 @@ export default function PersonalitySection() {
|
||||
x: 10,
|
||||
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
|
||||
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>
|
||||
))}
|
||||
</div>
|
||||
@ -238,9 +238,9 @@ export default function PersonalitySection() {
|
||||
rotate: 0,
|
||||
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">
|
||||
<svg viewBox="0 0 200 200" className="w-full h-full">
|
||||
<defs>
|
||||
@ -256,22 +256,22 @@ export default function PersonalitySection() {
|
||||
</filter>
|
||||
</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"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
||||
<motion.polygon
|
||||
@ -295,32 +295,31 @@ export default function PersonalitySection() {
|
||||
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 x="185" y="100" textAnchor="start" fill="currentColor" fontSize="8">
|
||||
<text x="185" y="100" textAnchor="start" fill="#e0e7ef" fontSize="8">
|
||||
创造力
|
||||
</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 x="15" y="100" textAnchor="end" fill="currentColor" fontSize="8">
|
||||
<text x="15" y="100" textAnchor="end" fill="#e0e7ef" fontSize="8">
|
||||
内省
|
||||
</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 x="37" y="163" textAnchor="middle" fill="currentColor" fontSize="8">
|
||||
<text x="37" y="163" textAnchor="middle" fill="#e0e7ef" fontSize="8">
|
||||
坚持
|
||||
</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 x="163" y="163" textAnchor="middle" fill="currentColor" fontSize="8">
|
||||
<text x="163" y="163" textAnchor="middle" fill="#e0e7ef" fontSize="8">
|
||||
敏感
|
||||
</text>
|
||||
|
||||
{/* Animated points */}
|
||||
{[
|
||||
{cx: 100, cy: 30},
|
||||
{cx: 150, cy: 45},
|
||||
@ -374,24 +373,24 @@ export default function PersonalitySection() {
|
||||
scale: 1.05,
|
||||
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="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>1%</span>
|
||||
<span>2%</span>
|
||||
<span>3%</span>
|
||||
</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
|
||||
initial={{width: 0}}
|
||||
animate={isInView ? {width: "16%"} : {}}
|
||||
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
|
||||
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%
|
||||
</motion.div>
|
||||
@ -430,8 +429,8 @@ export default function PersonalitySection() {
|
||||
className="absolute top-full left-[16%] -translate-x-1/2 mt-2 text-center"
|
||||
>
|
||||
<span
|
||||
className="text-sm font-medium text-neutral-700 dark:text-neutral-300">INFJ</span>
|
||||
<p className="text-xs text-neutral-500">全球人口比例</p>
|
||||
className="text-sm font-medium text-neutral-200">INFJ</span>
|
||||
<p className="text-xs text-neutral-400">全球人口比例</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
@ -444,4 +443,3 @@ export default function PersonalitySection() {
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -6,62 +6,47 @@ import {Github, ExternalLink, ArrowRight} from "lucide-react"
|
||||
import {Button} from "@/components/ui/button"
|
||||
import {Badge} from "@/components/ui/badge"
|
||||
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 {
|
||||
id: number
|
||||
title: string
|
||||
_id: number
|
||||
name: string
|
||||
description: string
|
||||
tags: string[]
|
||||
image: string
|
||||
techStack: string
|
||||
cover: any // Sanity image object
|
||||
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() {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const horizontalRef = useRef<HTMLDivElement>(null)
|
||||
const [containerWidth, setContainerWidth] = useState(0)
|
||||
const [isInView, setIsInView] = useState(false)
|
||||
const {theme} = useTheme()
|
||||
const [projects, setProjects] = useState<Project[]>([])
|
||||
|
||||
// Project data
|
||||
const projects: Project[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: "grtblog",
|
||||
description:
|
||||
"个人博客站点,一站式快速解决方案—一套源,扩展性强,快速部署的博客框架,使用 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(() => {
|
||||
const fetchData = async () => {
|
||||
const data = await client.fetch<Project[]>(PROJECTS_QUERY, {}, options);
|
||||
setProjects(data);
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
// 检测元素是否在视口中
|
||||
useEffect(() => {
|
||||
@ -88,8 +73,7 @@ export default function GsapProjectsShowcase() {
|
||||
// 计算水平滚动容器的宽度
|
||||
useEffect(() => {
|
||||
if (horizontalRef.current) {
|
||||
// 计算所有项目卡片的总宽度 + 间距
|
||||
const totalWidth = projects.length * 600 + (projects.length - 1) * 40
|
||||
const totalWidth = projects.length * 600 + (projects.length - 1) * 40 + 300 + 40 + 800
|
||||
setContainerWidth(totalWidth)
|
||||
}
|
||||
}, [projects.length])
|
||||
@ -193,7 +177,6 @@ export default function GsapProjectsShowcase() {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (tags) {
|
||||
gsap.fromTo(
|
||||
tags,
|
||||
@ -212,7 +195,6 @@ export default function GsapProjectsShowcase() {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (links) {
|
||||
gsap.fromTo(
|
||||
links,
|
||||
@ -234,7 +216,6 @@ export default function GsapProjectsShowcase() {
|
||||
})
|
||||
|
||||
return () => {
|
||||
// 清理动画
|
||||
scrollTween.kill()
|
||||
ScrollTrigger.getAll().forEach((trigger) => trigger.kill())
|
||||
}
|
||||
@ -269,9 +250,9 @@ export default function GsapProjectsShowcase() {
|
||||
transition={{duration: 0.8, delay: 0.2}}
|
||||
viewport={{once: true, amount: 0.3}}
|
||||
>
|
||||
<span className="bg-gradient-to-r from-emerald-600 to-blue-600 bg-clip-text text-transparent">
|
||||
以往项目展示
|
||||
</span>
|
||||
<span className="bg-gradient-to-r from-emerald-600 to-blue-600 bg-clip-text text-transparent">
|
||||
以往项目展示
|
||||
</span>
|
||||
</motion.h3>
|
||||
|
||||
<motion.p
|
||||
@ -281,7 +262,7 @@ export default function GsapProjectsShowcase() {
|
||||
transition={{duration: 0.8, delay: 0.3}}
|
||||
viewport={{once: true, amount: 0.3}}
|
||||
>
|
||||
向下滚动探索我的项目作品集 - 体验视差滚动效果
|
||||
向下滚动探索我的项目作品集,包含了多个领域的探索~
|
||||
</motion.p>
|
||||
</div>
|
||||
</div>
|
||||
@ -291,71 +272,76 @@ export default function GsapProjectsShowcase() {
|
||||
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]"
|
||||
>
|
||||
{projects.map((project, index) => (
|
||||
<div
|
||||
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"
|
||||
>
|
||||
<div className="project-image absolute inset-0 z-0">
|
||||
<div
|
||||
className="absolute inset-0 bg-gradient-to-t from-card via-card/80 to-transparent z-10"/>
|
||||
<img
|
||||
src={project.image || "/placeholder.svg"}
|
||||
alt={project.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<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">
|
||||
{project.tags.map((tag) => (
|
||||
<Badge key={tag} variant="outline"
|
||||
className="bg-primary/10 text-primary border-primary/20">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
{projects.map((project, index) => {
|
||||
const coverUrl = project.cover
|
||||
? urlFor(project.cover)?.width(600).height(500).url()
|
||||
: "/placeholder.svg"
|
||||
return (
|
||||
<div
|
||||
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"
|
||||
>
|
||||
<div className="project-image absolute inset-0 z-0">
|
||||
<div
|
||||
className="absolute inset-0 bg-gradient-to-t from-card via-card/80 to-transparent z-10"/>
|
||||
<img
|
||||
src={coverUrl}
|
||||
alt={project.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="project-links flex gap-4">
|
||||
{project.githubUrl && (
|
||||
<Button asChild variant="outline" size="sm"
|
||||
className="bg-primary/10 text-primary border-primary/20">
|
||||
<a
|
||||
href={project.githubUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Github className="h-4 w-4"/>
|
||||
GitHub
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
{project.liveUrl && (
|
||||
<Button asChild size="sm" variant="ghost">
|
||||
<a
|
||||
href={project.liveUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4"/>
|
||||
查看项目
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<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.name}</h4>
|
||||
<p className="project-desc text-lg text-muted-foreground mb-6">{project.description}</p>
|
||||
|
||||
{/* 项目序号 */}
|
||||
<div className="absolute top-8 right-8 text-8xl font-bold text-primary/10">
|
||||
{String(index + 1).padStart(2, "0")}
|
||||
<div className="project-tags flex flex-wrap gap-2 mb-6">
|
||||
{project.techStack.split(',').map((tag: string) => (
|
||||
<Badge key={tag} variant="outline"
|
||||
className="bg-primary/10 text-primary border-primary/20">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="project-links flex gap-4">
|
||||
{project.githubUrl && (
|
||||
<Button asChild variant="outline" size="sm"
|
||||
className="bg-primary/10 text-primary border-primary/20">
|
||||
<a
|
||||
href={project.githubUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Github className="h-4 w-4"/>
|
||||
GitHub
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
{project.deployUrl && (
|
||||
<Button asChild size="sm" variant="ghost">
|
||||
<a
|
||||
href={project.deployUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4"/>
|
||||
查看项目
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 项目序号 */}
|
||||
<div className="absolute top-8 right-8 text-8xl font-bold text-primary/10">
|
||||
{String(index + 1).padStart(2, "0")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
|
||||
{/* 结束提示 */}
|
||||
<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>
|
||||
<Button asChild>
|
||||
<a href="#contact" className="flex items-center gap-2">
|
||||
联系我 <ArrowRight className="h-4 w-4"/>
|
||||
转到项目页面 <ArrowRight className="h-4 w-4"/>
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
@ -382,4 +368,4 @@ export default function GsapProjectsShowcase() {
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
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