日常分享布局调整

This commit is contained in:
wxf
2026-06-26 14:08:18 +08:00
parent b2e9924ead
commit d35a97f325
8 changed files with 192 additions and 166 deletions
+5 -5
View File
@@ -16,7 +16,7 @@ import {
const navItems = [
{ label: "技术文章", href: "#articles" },
{ label: "生活分享", href: "#life" },
{ label: "日常分享", href: "#life" },
{ label: "联系我", href: "mailto:3505891631@qq.com" },
{ label: "Gitea", href: "http://124.221.147.173:3000/fly/fly-personal-site" },
];
@@ -31,10 +31,10 @@ const portals = [
gradient: "from-cyan-300 via-blue-500 to-violet-500",
},
{
title: "生活分享",
title: "日常分享",
href: "#life",
eyebrow: "Life stream",
description: "技术之外的日常片段,呈现更完整的个人侧面。",
description: "记录工具使用、阶段复盘、生活观察和那些亲自走过的经验。",
icon: LifeBuoy,
gradient: "from-lime-300 via-emerald-400 to-cyan-400",
},
@@ -298,10 +298,10 @@ export function HomePage() {
life channel
</motion.p>
<motion.h2 variants={reveal} className="mt-4 font-display text-5xl font-semibold sm:text-7xl">
</motion.h2>
<motion.p variants={reveal} className="mt-6 max-w-xl text-base leading-8 text-white/66">
使
</motion.p>
</motion.div>
<motion.div variants={container} className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
+39 -93
View File
@@ -1,9 +1,10 @@
"use client";
import Link from "next/link";
import Image from "next/image";
import { motion, useReducedMotion } from "motion/react";
import { ArrowLeft, ArrowUpRight, Calendar, Clock, Sparkles, ChevronLeft, ChevronRight } from "lucide-react";
import { useState } from "react";
import { ArrowLeft, Calendar, Clock, Sparkles } from "lucide-react";
import { countLifePostParagraphs } from "@/content/life/content";
import type { LifePost } from "@/content/life/types";
import { BackToTopButton } from "@/components/back-to-top-button";
@@ -18,7 +19,7 @@ export function LifeArticlePage({ post, prevPost, nextPost }: LifeArticlePagePro
const distance = reduceMotion ? 0 : 24;
const duration = reduceMotion ? 0 : 0.55;
const easeOut = [0.22, 1, 0.36, 1] as const;
const [currentImageIndex, setCurrentImageIndex] = useState(0);
const paragraphCount = countLifePostParagraphs(post);
const container = {
hidden: {},
@@ -40,18 +41,6 @@ export function LifeArticlePage({ post, prevPost, nextPost }: LifeArticlePagePro
show: { opacity: 1, y: 0, scale: 1, transition: { duration, ease: easeOut } },
};
const nextImage = () => {
if (post.images.length > 0) {
setCurrentImageIndex((prev) => (prev + 1) % post.images.length);
}
};
const prevImage = () => {
if (post.images.length > 0) {
setCurrentImageIndex((prev) => (prev - 1 + post.images.length) % post.images.length);
}
};
return (
<main className="relative min-h-dvh overflow-x-clip bg-[#f4f5f1] text-[#151515]">
{/* 背景装饰 */}
@@ -111,7 +100,7 @@ export function LifeArticlePage({ post, prevPost, nextPost }: LifeArticlePagePro
</span>
<span className="inline-flex items-center gap-1.5 rounded-full border border-[#151515]/8 bg-white/50 px-3 py-1 font-mono text-xs text-[#151515]/50">
<Clock className="h-3 w-3" />
{Math.ceil(post.paragraphs.length * 0.8)}
{Math.ceil(paragraphCount * 0.8)}
</span>
</div>
@@ -124,85 +113,42 @@ export function LifeArticlePage({ post, prevPost, nextPost }: LifeArticlePagePro
</p>
</motion.header>
{/* 图片画廊 */}
{post.images && post.images.length > 0 && (
<motion.div variants={scaleReveal}>
<div className="relative overflow-hidden rounded-[1.5rem] border border-[#151515]/8 bg-white shadow-xl shadow-[#5f6f52]/8">
{/* 主图片 */}
<div className="relative aspect-[16/9] w-full overflow-hidden">
<img
src={post.images[currentImageIndex]?.src}
alt={post.images[currentImageIndex]?.alt}
className="h-full w-full object-cover transition-opacity duration-300"
key={currentImageIndex}
/>
{/* 图片渐变遮罩 */}
<div className="absolute inset-0 bg-gradient-to-t from-black/20 via-transparent to-transparent" />
{/* 图片切换按钮 */}
{post.images.length > 1 && (
<>
<button
onClick={prevImage}
className="absolute left-4 top-1/2 -translate-y-1/2 inline-flex h-10 w-10 items-center justify-center rounded-full bg-white/80 text-[#151515]/70 shadow-lg backdrop-blur transition hover:bg-white hover:text-[#5f6f52] focus-visible:ring-2 focus-visible:ring-[#5f6f52]/40"
aria-label="上一张图片"
>
<ChevronLeft className="h-5 w-5" />
</button>
<button
onClick={nextImage}
className="absolute right-4 top-1/2 -translate-y-1/2 inline-flex h-10 w-10 items-center justify-center rounded-full bg-white/80 text-[#151515]/70 shadow-lg backdrop-blur transition hover:bg-white hover:text-[#5f6f52] focus-visible:ring-2 focus-visible:ring-[#5f6f52]/40"
aria-label="下一张图片"
>
<ChevronRight className="h-5 w-5" />
</button>
</>
)}
{/* 图片计数 */}
{post.images.length > 1 && (
<div className="absolute right-4 top-4 inline-flex items-center gap-1 rounded-full bg-black/40 px-2.5 py-1 font-mono text-xs text-white backdrop-blur">
{currentImageIndex + 1} / {post.images.length}
</div>
)}
</div>
{/* 图片说明 */}
<div className="p-5 sm:p-6">
<p className="text-sm leading-6 text-[#151515]/55">
{post.images[currentImageIndex]?.caption}
<motion.div variants={reveal} className="space-y-8 pt-4">
{post.content.map((block, index) => {
if (block.type === "paragraph") {
return (
<p
key={`paragraph-${index}`}
className="text-base leading-8 text-[#151515]/75 sm:text-lg sm:leading-9"
>
{block.text}
</p>
{post.images.length > 1 && (
<div className="mt-4 flex items-center justify-center gap-2">
{post.images.map((_, index) => (
<button
key={index}
onClick={() => setCurrentImageIndex(index)}
className={`h-2 rounded-full transition-all ${
index === currentImageIndex
? "w-6 bg-[#5f6f52]"
: "w-2 bg-[#151515]/20 hover:bg-[#151515]/30"
}`}
aria-label={`查看第 ${index + 1} 张图片`}
/>
))}
</div>
)}
</div>
</div>
</motion.div>
)}
);
}
{/* 正文内容 */}
<motion.div variants={reveal} className="space-y-6 pt-4">
{post.paragraphs.map((paragraph, index) => (
<p
key={index}
className="text-base leading-8 text-[#151515]/75 sm:text-lg sm:leading-9"
>
{paragraph}
</p>
))}
return (
<motion.figure
key={`image-${index}`}
variants={scaleReveal}
className="overflow-hidden rounded-[1.5rem] border border-[#151515]/8 bg-white shadow-xl shadow-[#5f6f52]/8"
>
<div className="relative aspect-[16/9] w-full overflow-hidden bg-[#e7e8df]">
<Image
src={block.src}
alt={block.alt}
fill
sizes="(max-width: 768px) 100vw, 896px"
className="object-cover"
/>
</div>
{block.caption ? (
<figcaption className="px-5 py-4 text-sm leading-6 text-[#151515]/55 sm:px-6">
{block.caption}
</figcaption>
) : null}
</motion.figure>
);
})}
</motion.div>
{/* 文章结尾装饰 */}
+65 -51
View File
@@ -1,8 +1,10 @@
"use client";
import Link from "next/link";
import NextImage from "next/image";
import { motion, useReducedMotion } from "motion/react";
import { ArrowLeft, ArrowUpRight, Calendar, Clock, Sparkles, BookOpen, Image, Coffee, Music, Camera, Plane, Brain } from "lucide-react";
import { ArrowUpRight, Calendar, Clock, Sparkles, BookOpen, Image as ImageIcon, Coffee, Music, Camera, Plane, Brain } from "lucide-react";
import { getLifePostCoverImage, getLifePostImages } from "@/content/life/content";
import type { LifePost } from "@/content/life/types";
const topics = [
@@ -45,6 +47,8 @@ export function LifePage({ posts }: LifePageProps) {
};
const latestPost = posts[0];
const latestPostImages = latestPost ? getLifePostImages(latestPost) : [];
const latestPostCoverImage = latestPost ? getLifePostCoverImage(latestPost) : undefined;
return (
<main className="relative min-h-dvh overflow-x-clip bg-[#f4f5f1] text-[#151515]">
@@ -118,7 +122,7 @@ export function LifePage({ posts }: LifePageProps) {
</Link>
<span className="relative rounded-full px-4 py-2 text-sm font-semibold text-[#5f6f52]">
<span className="absolute inset-0 rounded-full bg-[#5f6f52]/10" />
<span className="relative"></span>
<span className="relative"></span>
</span>
</motion.div>
</motion.header>
@@ -145,7 +149,7 @@ export function LifePage({ posts }: LifePageProps) {
</motion.h1>
<motion.p variants={reveal} className="mt-6 max-w-xl text-lg leading-8 text-[#151515]/58">
使
</motion.p>
{/* 主题标签 */}
@@ -222,7 +226,11 @@ export function LifePage({ posts }: LifePageProps) {
{/* 悬停时的光晕效果 */}
<div className="absolute -top-1/2 -right-1/2 h-full w-full rounded-full bg-[#5f6f52]/0 blur-3xl transition duration-700 group-hover:bg-[#5f6f52]/8" />
<div className="relative grid gap-8 p-6 sm:p-8 lg:grid-cols-[1fr_20rem] lg:items-center lg:p-10">
<div
className={`relative grid gap-8 p-6 sm:p-8 lg:items-center lg:p-10 ${
latestPostCoverImage ? "lg:grid-cols-[1fr_20rem]" : ""
}`}
>
<div className="space-y-5">
<div className="flex items-center gap-3">
<span className="inline-flex items-center gap-1.5 rounded-full bg-[#5f6f52]/12 px-3 py-1 font-mono text-xs font-medium text-[#5f6f52]">
@@ -230,8 +238,8 @@ export function LifePage({ posts }: LifePageProps) {
{latestPost.date}
</span>
<span className="inline-flex items-center gap-1 font-mono text-xs uppercase text-[#151515]/30">
<Image className="h-3 w-3" />
{latestPost.images?.length || 0}
<ImageIcon className="h-3 w-3" />
{latestPostImages.length}
</span>
</div>
<h2 className="font-display text-3xl font-semibold leading-tight text-[#151515] transition duration-300 group-hover:text-[#5f6f52] sm:text-4xl">
@@ -249,21 +257,23 @@ export function LifePage({ posts }: LifePageProps) {
</div>
{/* 图片预览区域 */}
{latestPost.images?.[0] && (
{latestPostCoverImage && (
<div className="relative overflow-hidden rounded-2xl border border-[#151515]/8 bg-[#f4f5f1]">
<div className="aspect-[4/3] w-full">
<img
src={latestPost.images[0].src}
alt={latestPost.images[0].alt}
className="h-full w-full object-cover transition duration-500 group-hover:scale-105"
<div className="relative aspect-[4/3] w-full">
<NextImage
src={latestPostCoverImage.src}
alt={latestPostCoverImage.alt}
fill
sizes="(max-width: 1024px) 100vw, 320px"
className="object-cover transition duration-500 group-hover:scale-105"
/>
{/* 图片渐变遮罩 */}
<div className="absolute inset-0 bg-gradient-to-t from-[#5f6f52]/10 via-transparent to-transparent" />
</div>
{/* 图片装饰角标 */}
<div className="absolute left-3 top-3 inline-flex items-center gap-1 rounded-full bg-white/80 px-2 py-1 font-mono text-[0.65rem] font-medium text-[#5f6f52] backdrop-blur">
<Image className="h-3 w-3" />
{latestPost.images.length}P
<ImageIcon className="h-3 w-3" />
{latestPostImages.length}P
</div>
</div>
)}
@@ -295,48 +305,52 @@ export function LifePage({ posts }: LifePageProps) {
</motion.div>
<div className="space-y-3">
{posts.map((post, index) => (
<motion.div key={post.slug} variants={reveal}>
<Link
href={`/life/${post.slug}`}
className="group relative flex items-center gap-4 overflow-hidden rounded-2xl border border-[#151515]/8 bg-white/45 p-5 transition-all duration-300 hover:border-[#5f6f52]/20 hover:bg-white/80 hover:shadow-lg hover:shadow-[#5f6f52]/6 focus-visible:ring-2 focus-visible:ring-[#5f6f52]/40 sm:gap-6 sm:p-6"
>
{/* 左侧装饰条 */}
<div className="absolute left-0 top-0 h-full w-1 bg-[#5f6f52]/0 transition-all duration-300 group-hover:bg-[#5f6f52]/40" />
{posts.map((post, index) => {
const postImages = getLifePostImages(post);
{/* 序号 */}
<span className="hidden font-mono text-sm font-medium text-[#151515]/20 sm:block">
{String(index + 1).padStart(2, "0")}
</span>
return (
<motion.div key={post.slug} variants={reveal}>
<Link
href={`/life/${post.slug}`}
className="group relative flex items-center gap-4 overflow-hidden rounded-2xl border border-[#151515]/8 bg-white/45 p-5 transition-all duration-300 hover:border-[#5f6f52]/20 hover:bg-white/80 hover:shadow-lg hover:shadow-[#5f6f52]/6 focus-visible:ring-2 focus-visible:ring-[#5f6f52]/40 sm:gap-6 sm:p-6"
>
{/* 左侧装饰条 */}
<div className="absolute left-0 top-0 h-full w-1 bg-[#5f6f52]/0 transition-all duration-300 group-hover:bg-[#5f6f52]/40" />
{/* 日期和图片数 */}
<div className="flex shrink-0 flex-col gap-1.5">
<time className="font-mono text-xs uppercase tracking-normal text-[#151515]/40">
{post.date}
</time>
<span className="hidden items-center gap-1 font-mono text-[0.65rem] text-[#151515]/25 sm:inline-flex">
<Image className="h-3 w-3" />
{post.images?.length || 0}
{/* 序号 */}
<span className="hidden font-mono text-sm font-medium text-[#151515]/20 sm:block">
{String(index + 1).padStart(2, "0")}
</span>
</div>
{/* 标题和描述 */}
<div className="min-w-0 flex-1">
<h3 className="truncate text-xl font-medium text-[#151515] transition-colors duration-300 group-hover:text-[#5f6f52] sm:text-2xl">
{post.title}
</h3>
<p className="mt-1 truncate text-sm text-[#151515]/48">
{post.description}
</p>
</div>
{/* 日期和图片数 */}
<div className="flex shrink-0 flex-col gap-1.5">
<time className="font-mono text-xs uppercase tracking-normal text-[#151515]/40">
{post.date}
</time>
<span className="hidden items-center gap-1 font-mono text-[0.65rem] text-[#151515]/25 sm:inline-flex">
<ImageIcon className="h-3 w-3" />
{postImages.length}
</span>
</div>
{/* 箭头 */}
<span className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-[#151515]/8 text-[#151515]/30 transition-all duration-300 group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:border-[#5f6f52]/30 group-hover:bg-[#5f6f52]/8 group-hover:text-[#5f6f52]">
<ArrowUpRight className="h-4 w-4" />
</span>
</Link>
</motion.div>
))}
{/* 标题和描述 */}
<div className="min-w-0 flex-1">
<h3 className="truncate text-xl font-medium text-[#151515] transition-colors duration-300 group-hover:text-[#5f6f52] sm:text-2xl">
{post.title}
</h3>
<p className="mt-1 truncate text-sm text-[#151515]/48">
{post.description}
</p>
</div>
{/* 箭头 */}
<span className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-[#151515]/8 text-[#151515]/30 transition-all duration-300 group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:border-[#5f6f52]/30 group-hover:bg-[#5f6f52]/8 group-hover:text-[#5f6f52]">
<ArrowUpRight className="h-4 w-4" />
</span>
</Link>
</motion.div>
);
})}
</div>
{/* 底部装饰 */}