mirror of
https://github.com/LiuYuYang01/ThriveX-Blog.git
synced 2026-05-07 06:07:34 +08:00
优化样式文件,统一引号格式,增强可读性。重构朋友圈页面,添加复制功能和全局背景装饰,提升用户体验。更新组件以支持新样式和动画效果,确保更流畅的视觉体验。
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
@use '@/styles/var' as *;
|
||||
@use '@/styles/fun' as *;
|
||||
@use "@/styles/var" as *;
|
||||
@use "@/styles/fun" as *;
|
||||
|
||||
.ContentMdComponent {
|
||||
.markdown-body {
|
||||
@@ -17,7 +17,7 @@
|
||||
font-family: inherit;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
left: 1rem;
|
||||
@@ -25,7 +25,9 @@
|
||||
height: 0.75rem;
|
||||
background: #ff5f56;
|
||||
border-radius: 50%;
|
||||
box-shadow: 1rem 0 0 #ffbd2e, 2rem 0 0 #27c93f;
|
||||
box-shadow:
|
||||
1rem 0 0 #ffbd2e,
|
||||
2rem 0 0 #27c93f;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@@ -69,7 +71,7 @@
|
||||
cursor: pointer;
|
||||
|
||||
&::after {
|
||||
content: '点击展开代码';
|
||||
content: "点击展开代码";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
@@ -118,7 +120,7 @@
|
||||
}
|
||||
|
||||
img {
|
||||
@apply blur-[20px] rounded-xl hover:scale-105 cursor-pointer transition-all;
|
||||
@apply blur-[20px] rounded-xl hover:scale-105 cursor-pointer transition-transform;
|
||||
}
|
||||
|
||||
h1,
|
||||
@@ -179,7 +181,7 @@
|
||||
counter-reset: counter;
|
||||
}
|
||||
|
||||
ol > li:not([id^='user-content-fn-']) {
|
||||
ol > li:not([id^="user-content-fn-"]) {
|
||||
&::before {
|
||||
@apply absolute w-4 h-4 mt-1 leading-none left-0 rounded-full text-center text-sm border border-[#11181C] dark:border-gray-400;
|
||||
counter-increment: counter;
|
||||
@@ -188,7 +190,7 @@
|
||||
|
||||
& ol > li::before {
|
||||
@apply absolute left-7 border-0 text-base mt-0 leading-normal;
|
||||
content: counter(counter) '.';
|
||||
content: counter(counter) ".";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +254,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
@@ -298,7 +300,7 @@
|
||||
font-size: 0.95em;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
|
||||
.line-content {
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function CopyableText({ text, children, className = '' }: Copyabl
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={`hover:text-primary cursor-pointer transition-colors ${className}`} onClick={handleCopy} title="点击复制">
|
||||
<span className={`hover:text-primary cursor-pointer ${className}`} onClick={handleCopy} title="点击复制">
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -1,97 +1,268 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
|
||||
import Slide from '@/components/Slide';
|
||||
import Starry from '@/components/Starry';
|
||||
import ApplyForAdd from './components/ApplyForAdd';
|
||||
import CopyableText from './components/CopyableText';
|
||||
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { Web } from '@/types/app/web';
|
||||
|
||||
import { useConfigStore, useAuthorStore } from '@/stores';
|
||||
|
||||
const Icons = {
|
||||
Copy: () => (
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
),
|
||||
Check: () => (
|
||||
<svg className="w-4 h-4 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
),
|
||||
Link: () => (
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
|
||||
</svg>
|
||||
),
|
||||
Rss: () => (
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 9a6 6 0 016 6m-6-9a9 9 0 019 9M3 3a18 18 0 0118 18M5 21h0" />
|
||||
</svg>
|
||||
),
|
||||
User: () => (
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
)
|
||||
};
|
||||
|
||||
// 默认头像
|
||||
const DEFAULT_AVATAR =
|
||||
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='64' height='64' viewBox='0 0 64 64'%3E%3Ccircle fill='%23e5e7eb' cx='32' cy='32' r='32'/%3E%3Cpath fill='%239ca3af' d='M32 32a8 8 0 1 1 0-16 8 8 0 0 1 0 16zm0 8c-8 0-16 4-16 12v4h32v-4c0-8-8-12-16-12z'/%3E%3C/svg%3E";
|
||||
|
||||
const CopyInput = ({ label, value, icon, isCode = false, highlight = false, truncate = false }: any) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = () => {
|
||||
if (!value) return;
|
||||
navigator.clipboard.writeText(value);
|
||||
setCopied(true);
|
||||
toast.success(`已复制: ${label}`, { autoClose: 1500 });
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="group flex items-center gap-3 p-2 pl-3 bg-white dark:bg-gray-800/60 rounded-xl border border-gray-200 dark:border-gray-700/80 hover:border-primary/50 dark:hover:border-primary/50 transition-[transform,box-shadow] duration-300 shadow-sm hover:shadow-md">
|
||||
<div className={`shrink-0 w-9 h-9 flex items-center justify-center rounded-lg ${highlight ? 'bg-orange-100 text-orange-600 dark:bg-orange-900/30 dark:text-orange-400' : 'bg-gray-100 text-gray-500 dark:bg-gray-700/50 dark:text-gray-400 group-hover:text-primary dark:group-hover:text-primary'}`}>
|
||||
{icon}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0 flex flex-col justify-center">
|
||||
<span className="text-[10px] uppercase tracking-wider text-gray-400 font-semibold mb-0.5">
|
||||
{label}
|
||||
</span>
|
||||
<div className={`text-sm ${isCode ? 'font-mono text-xs tracking-tight' : 'font-medium'} text-gray-800 dark:text-gray-200 ${truncate ? 'truncate' : ''}`}>
|
||||
{value || 'Wait for loading...'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="shrink-0 p-2 mr-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-400 hover:text-primary transition-transform active:scale-95"
|
||||
title="点击复制"
|
||||
>
|
||||
{copied ? <Icons.Check /> : <Icons.Copy />}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ({ data }: { data: { [string: string]: { order: number; list: Web[] } } }) => {
|
||||
const web = useConfigStore((state) => state.web);
|
||||
const author = useAuthorStore((state) => state.author);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Slide isRipple={false}>
|
||||
{/* 星空背景组件 */}
|
||||
<Starry />
|
||||
<title>😇 朋友圈</title>
|
||||
<meta name="description" content="😇 朋友圈" />
|
||||
|
||||
<div className="absolute top-[30%] left-[50%] transform -translate-x-1/2 flex flex-col items-center">
|
||||
<div className="text-white text-[20px] xs:text-[25px] sm:text-[30px] whitespace-nowrap custom_text_shadow">一个人的寂寞,一群人的狂欢!</div>
|
||||
<div className="mt-4 sm:mt-8">
|
||||
<ApplyForAdd />
|
||||
</div>
|
||||
</div>
|
||||
</Slide>
|
||||
{/* 全局背景装饰 */}
|
||||
<div className="fixed inset-0 overflow-hidden pointer-events-none">
|
||||
<div className="absolute inset-0 bg-[linear-gradient(rgba(148,163,184,0.06)_1px,transparent_1px),linear-gradient(90deg,rgba(148,163,184,0.06)_1px,transparent_1px)] bg-[size:64px_64px]" />
|
||||
<div className="absolute -top-1/2 left-1/2 -translate-x-1/2 w-[800px] h-[800px] rounded-full bg-primary/5 blur-[120px]" />
|
||||
<div className="absolute top-1/4 right-0 w-96 h-96 rounded-full bg-violet-400/5 blur-[80px]" />
|
||||
<div className="absolute bottom-1/4 left-0 w-80 h-80 rounded-full bg-cyan-400/5 blur-[80px]" />
|
||||
</div>
|
||||
|
||||
<div className="bg-[linear-gradient(180deg,#edf6ff_0%,#ffffff_100%)] dark:bg-[linear-gradient(to_right,#232931_0%,#232931_100%)]">
|
||||
<div className="relative -top-20 xs:-top-20 sm:-top-32 md:-top-36 w-[90%] xl:w-[1200px] p-10 pt-2 mx-auto bg-white dark:bg-black-b border dark:border-black-b rounded-2xl space-y-8 ">
|
||||
<div>
|
||||
<h3 className="w-full text-center text-xl p-4 dark:text-white ">本站信息</h3>
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-[#0f172a]">
|
||||
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-20 pt-24 relative z-20 space-y-16">
|
||||
|
||||
<div className="mx-auto p-3 space-y-2 border-l-[3px] border-primary bg-[#ecf7fe] dark:bg-[#333b48] rounded-md text-sm text-black-b dark:text-gray-300">
|
||||
<p>
|
||||
站点名称:<CopyableText text={web?.title}>{web?.title}</CopyableText>
|
||||
</p>
|
||||
<p>
|
||||
站点介绍:<CopyableText text={web?.description}>{web?.description}</CopyableText>
|
||||
</p>
|
||||
<p>
|
||||
站点图标:<CopyableText text={author?.avatar || ''}>{author?.avatar}</CopyableText>
|
||||
</p>
|
||||
<p>
|
||||
站点地址:<CopyableText text={web?.url}>{web?.url}</CopyableText>
|
||||
</p>
|
||||
<p>
|
||||
Rss地址:<CopyableText text={web?.url + '/api/rss'}>{web?.url + '/api/rss'}</CopyableText>
|
||||
</p>
|
||||
<section className="relative group w-full mx-auto">
|
||||
{/* 卡片背后的光晕装饰 */}
|
||||
<div className="absolute -inset-0.5 bg-gradient-to-r from-primary/30 to-purple-600/30 rounded-[2.5rem] blur-2xl opacity-20 group-hover:opacity-40 transition duration-1000" />
|
||||
|
||||
{/* 卡片主体 */}
|
||||
<div className="relative bg-white/70 dark:bg-[#1e293b]/60 backdrop-blur-xl border border-white/50 dark:border-gray-700/50 rounded-3xl shadow-2xl overflow-hidden ring-1 ring-gray-900/5">
|
||||
<div className="p-6 md:p-8 lg:p-10">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12 items-center">
|
||||
<div className="lg:col-span-5 flex flex-col justify-center items-center text-center">
|
||||
{/* Logo/头像展示区 */}
|
||||
<div
|
||||
className="relative group/avatar cursor-pointer animate-avatar-in"
|
||||
>
|
||||
{/* 旋转光晕边框 */}
|
||||
<div className="absolute -inset-1 rounded-full opacity-60 blur group-hover/avatar:opacity-100 transition duration-500 animate-tilt bg-gradient-to-r from-primary/40 to-purple-500/40" />
|
||||
<div className="relative w-32 h-32 md:w-36 md:h-36 rounded-full p-1.5 bg-white dark:bg-gray-800 mb-4 transition-transform duration-300 group-hover/avatar:scale-105 animate-avatar-float">
|
||||
<img
|
||||
src={author?.avatar || '/favicon.ico'}
|
||||
alt="Site Logo"
|
||||
className="w-full h-full rounded-full object-cover border border-gray-100 dark:border-gray-700 shadow-inner bg-white dark:bg-gray-900 transition-transform duration-300 group-hover/avatar:rotate-6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 标题与描述 */}
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white text-center">
|
||||
{web?.title}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 font-medium">
|
||||
{web?.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="lg:col-span-7 w-full">
|
||||
<div className="bg-gray-50/80 dark:bg-black/20 rounded-2xl p-6 border border-gray-100 dark:border-gray-700/30">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* 1. 站点名称 */}
|
||||
<div className="md:col-span-1">
|
||||
<CopyInput
|
||||
label="Site Title"
|
||||
value={web?.title}
|
||||
icon={<span className="font-serif font-bold text-lg">T</span>}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-1">
|
||||
<CopyInput
|
||||
label="Site Desc"
|
||||
value={web?.description}
|
||||
icon={<span className="font-serif font-bold text-lg">D</span>}
|
||||
truncate
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<CopyInput
|
||||
label="URL Address"
|
||||
value={web?.url}
|
||||
icon={<Icons.Link />}
|
||||
isCode
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<CopyInput
|
||||
label="Avatar Source"
|
||||
value={author?.avatar}
|
||||
icon={<div className="text-[10px] font-bold">IMG</div>}
|
||||
isCode
|
||||
truncate
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<CopyInput
|
||||
label="RSS Feed"
|
||||
value={web?.url ? `${web.url}/api/rss` : ''}
|
||||
icon={<Icons.Rss />}
|
||||
isCode
|
||||
highlight
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{Object.keys(data)?.map((type, index) => (
|
||||
<div key={index}>
|
||||
<h3 className="w-full text-center text-xl p-4 dark:text-white ">{type}</h3>
|
||||
<section key={index} className="animate-fade-in-up" style={{ animationDelay: `${index * 100}ms` }}>
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="relative">
|
||||
<h3 className="text-2xl font-bold text-gray-900 dark:text-gray-100 z-10 relative px-2">
|
||||
{type}
|
||||
</h3>
|
||||
<span className="absolute bottom-1 left-0 w-full h-3 bg-primary/20 -skew-x-12 rounded-sm"></span>
|
||||
</div>
|
||||
<div className="h-px flex-1 bg-gradient-to-r from-gray-200 to-transparent dark:from-gray-800"></div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{type === '全站置顶' && (
|
||||
<Link href="https://liuyuyang.net" target="_blank" className="group">
|
||||
<div className="flex items-center p-3 border group-hover:border-2 dark:border-[#3d4653] group-hover:!border-primary group-hover:shadow-[0_10px_20px_1px_rgb(83,157,253,.1)] rounded-md ">
|
||||
<img src="https://q1.qlogo.cn/g?b=qq&nk=3311118881&s=640" alt="项目作者" className="w-14 h-14 mr-4 rounded-full" />
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h4 className="text-sm text-gray-700 dark:text-white group-hover:text-primary">宇阳</h4>
|
||||
<p className="text-xs text-[#8c9ab1] line-clamp-2">ThriveX 博客管理系统作者</p>
|
||||
<Link href="https://liuyuyang.net" target="_blank" className="group block h-full">
|
||||
<div className="h-full flex items-center p-5 bg-gradient-to-br from-primary/5 to-purple-500/5 dark:from-primary/10 dark:to-purple-500/10 border border-primary/20 dark:border-primary/30 rounded-2xl transition-[transform,box-shadow] duration-300 hover:-translate-y-1 hover:shadow-xl hover:shadow-primary/10 hover:border-primary/50 relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 px-2 py-1 bg-primary text-[10px] font-bold text-white rounded-bl-xl shadow-sm">OWNER</div>
|
||||
<img
|
||||
src="https://q1.qlogo.cn/g?b=qq&nk=3311118881&s=640"
|
||||
alt="项目作者"
|
||||
className="w-16 h-16 rounded-full border-2 border-white dark:border-gray-700 shadow-md group-hover:rotate-6 transition-transform duration-300"
|
||||
/>
|
||||
<div className="ml-4 flex-1 min-w-0">
|
||||
<h4 className="text-base font-bold text-gray-900 dark:text-white group-hover:text-primary">宇阳</h4>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1 line-clamp-2 font-medium">ThriveX 博客管理系统作者</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{data[type].list?.map((item: Web) => (
|
||||
<Link key={item.id} href={item.url} target="_blank" className="group">
|
||||
<div key={item.id} className="flex items-center p-3 border group-hover:border-2 dark:border-[#3d4653] group-hover:!border-primary group-hover:shadow-[0_10px_20px_1px_rgb(83,157,253,.1)] rounded-md ">
|
||||
<img src={item.image} alt={item.title} className="w-14 h-14 mr-4 rounded-full" />
|
||||
<Link key={item.id} href={item.url} target="_blank" className="group block h-full">
|
||||
<div className="h-full flex items-start p-4 bg-white dark:bg-[#1e293b]/80 backdrop-blur-sm border border-gray-100 dark:border-gray-700/60 rounded-2xl transition-[transform,box-shadow] duration-300 hover:-translate-y-1 hover:shadow-lg hover:shadow-gray-200/50 dark:hover:shadow-black/30 hover:border-primary/40 dark:hover:border-primary/40 group-hover:bg-white/80 dark:group-hover:bg-[#1e293b]">
|
||||
<div className="relative shrink-0">
|
||||
<img
|
||||
src={item.image}
|
||||
alt={item.title}
|
||||
className="w-12 h-12 rounded-xl object-cover bg-gray-100 dark:bg-gray-800 border border-gray-100 dark:border-gray-700 transition-transform duration-300 group-hover:scale-110"
|
||||
onError={(e) => {
|
||||
const el = e.target as HTMLImageElement;
|
||||
if (el.src !== DEFAULT_AVATAR) el.src = DEFAULT_AVATAR;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h4 className="text-sm text-gray-700 dark:text-white group-hover:text-primary">{item.title}</h4>
|
||||
<p className="text-xs text-[#8c9ab1] line-clamp-2">{item.description}</p>
|
||||
<div className="ml-4 flex-1 min-w-0">
|
||||
<h4 className="text-sm font-bold text-gray-800 dark:text-gray-100 group-hover:text-primary truncate">
|
||||
{item.title}
|
||||
</h4>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1.5 leading-relaxed line-clamp-2 group-hover:text-gray-600 dark:group-hover:text-gray-300">
|
||||
{item.description || '暂无介绍...'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<ToastContainer />
|
||||
<ToastContainer
|
||||
position="bottom-right"
|
||||
autoClose={3000}
|
||||
hideProgressBar={false}
|
||||
newestOnTop={false}
|
||||
closeOnClick
|
||||
rtl={false}
|
||||
pauseOnFocusLoss
|
||||
draggable
|
||||
pauseOnHover
|
||||
theme="colored"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -69,17 +69,17 @@ export default function RecordCard({ id, content, images, createTime, user }: Re
|
||||
|
||||
{/* Footer: 互动栏 (装饰用) */}
|
||||
{/* <div className="mt-5 pt-4 border-t border-slate-50 dark:border-slate-700/50 flex items-center justify-end gap-4 text-slate-400 dark:text-slate-500 text-sm">
|
||||
<button type="button" className="flex items-center gap-1 hover:text-slate-600 dark:hover:text-slate-300 transition-colors" aria-label="喜欢">
|
||||
<button type="button" className="flex items-center gap-1 hover:text-slate-600 dark:hover:text-slate-300 " aria-label="喜欢">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" className="flex items-center gap-1 hover:text-slate-600 dark:hover:text-slate-300 transition-colors" aria-label="评论">
|
||||
<button type="button" className="flex items-center gap-1 hover:text-slate-600 dark:hover:text-slate-300 " aria-label="评论">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" className="flex items-center gap-1 hover:text-slate-600 dark:hover:text-slate-300 transition-colors" aria-label="分享">
|
||||
<button type="button" className="flex items-center gap-1 hover:text-slate-600 dark:hover:text-slate-300 " aria-label="分享">
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" />
|
||||
</svg>
|
||||
|
||||
@@ -240,19 +240,19 @@ export default ({ data }: { data: Resume }) => {
|
||||
|
||||
<div className="space-y-3">
|
||||
{safeLinks?.github && (
|
||||
<a href={safeLinks.github || defaultLinks.github} target="_blank" rel="noopener noreferrer" className="flex items-center text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 text-sm font-medium transition-colors">
|
||||
<a href={safeLinks.github || defaultLinks.github} target="_blank" rel="noopener noreferrer" className="flex items-center text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 text-sm font-medium ">
|
||||
<FaGithub className="mr-2" size={16} /> GitHub
|
||||
</a>
|
||||
)}
|
||||
|
||||
{safeLinks?.csdn && (
|
||||
<a href={safeLinks.csdn || defaultLinks.csdn} target="_blank" rel="noopener noreferrer" className="flex items-center text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 text-sm font-medium transition-colors">
|
||||
<a href={safeLinks.csdn || defaultLinks.csdn} target="_blank" rel="noopener noreferrer" className="flex items-center text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 text-sm font-medium ">
|
||||
<FaGlobe className="mr-2" size={16} /> CSDN 技术博客
|
||||
</a>
|
||||
)}
|
||||
|
||||
{safeLinks?.blog && (
|
||||
<a href={safeLinks.blog || defaultLinks.blog} target="_blank" rel="noopener noreferrer" className="flex items-center text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 text-sm font-medium transition-colors">
|
||||
<a href={safeLinks.blog || defaultLinks.blog} target="_blank" rel="noopener noreferrer" className="flex items-center text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 text-sm font-medium ">
|
||||
<FaProjectDiagram className="mr-2" size={16} /> 开源项目作品
|
||||
</a>
|
||||
)}
|
||||
@@ -375,7 +375,7 @@ export default ({ data }: { data: Resume }) => {
|
||||
{project.repositories?.map((item, index) => (
|
||||
<div key={index} className="flex items-center">
|
||||
<span>{item.name || '未命名链接'}:</span>
|
||||
<a href={(item.url as string) || '#'} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 text-xs font-medium bg-blue-50 dark:bg-blue-900/30 px-2 py-1 rounded transition-colors">
|
||||
<a href={(item.url as string) || '#'} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 text-xs font-medium bg-blue-50 dark:bg-blue-900/30 px-2 py-1 rounded ">
|
||||
{item.url || '未提供链接'}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -306,7 +306,7 @@ export default ({ onEmojiSelect, className = '' }: Props) => {
|
||||
const horizontalClass = colIndex === 0 ? 'left-0 translate-x-0' : colIndex === 5 ? 'right-0 translate-x-0' : 'left-1/2 -translate-x-1/2';
|
||||
|
||||
return (
|
||||
<button key={`${emojiItem.emoji}-${index}`} onClick={() => handleEmojiClick(emojiItem)} className="w-10 h-10 flex items-center justify-center text-2xl hover:bg-gray-100 rounded-lg transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-400 cursor-pointer group relative" aria-label={emojiItem.name}>
|
||||
<button key={`${emojiItem.emoji}-${index}`} onClick={() => handleEmojiClick(emojiItem)} className="w-10 h-10 flex items-center justify-center text-2xl hover:bg-gray-100 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400 cursor-pointer group relative" aria-label={emojiItem.name}>
|
||||
{emojiItem.emoji}
|
||||
{/* 悬停时显示名称:第一排向下,其余向上,并做左右边界处理 */}
|
||||
<div className={`absolute ${isFirstRow ? 'top-full mt-2' : 'bottom-full mb-2'} ${horizontalClass} transform px-2 py-1 bg-gray-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-10 pointer-events-none`}>{emojiItem.name}</div>
|
||||
|
||||
@@ -23,7 +23,7 @@ export default () => {
|
||||
const { isDark, setIsDark, theme } = useConfigStore();
|
||||
|
||||
// 这些路径段不需要改变导航样式
|
||||
const isPathSty = ['/my', '/wall', '/record', '/equipment', '/tags', '/resume', '/album', '/fishpond'].some((path) => patchName.includes(path));
|
||||
const isPathSty = ['/my', '/wall', '/record', '/equipment', '/tags', '/resume', '/album', '/fishpond', '/friend'].some((path) => patchName.includes(path));
|
||||
// 是否改变导航样式
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
|
||||
@@ -69,7 +69,7 @@ export default () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`header fixed top-0 w-full h-16 backdrop-blur-[5px] z-50 after:content-[''] after:block after:w-full after:h-0 after:bg-[linear-gradient(#fff,transparent_70%)] dark:after:bg-[linear-gradient(#2b333e,transparent_70%)] after: ${isPathSty || isScrolled ? 'bg-[rgba(255,255,255,0.9)] dark:bg-[rgba(44,51,62,0.9)] border-b dark:border-[#2b333e] after:!h-8 after:transition-height]' : 'border-transparent'} transition-border`}>
|
||||
<div className={`header fixed top-0 w-full h-16 backdrop-blur-[5px] z-50 after:content-[''] after:block after:w-full after:h-0 after:bg-[linear-gradient(#fff,transparent_70%)] dark:after:bg-[linear-gradient(#2b333e,transparent_70%)] after: ${isPathSty || isScrolled ? 'bg-[rgba(255,255,255,0.7)] dark:bg-[rgba(44,51,62,0.7)] backdrop-blur-md border-b dark:border-[#2b333e] after:!h-8 after:transition-height]' : 'border-transparent'}`}>
|
||||
<div className="relative flex justify-center lg:justify-start w-full lg:w-[1500px] h-16 mx-auto">
|
||||
<div className={`lg:hidden group absolute top-0 left-0 h-full py-2 px-3 pl-7 ${isPathSty || isScrolled ? 'hover:bg-[#e9edf4] dark:hover:bg-[#455162] rounded-lg' : ''} cursor-pointer `} onClick={() => setIsOpenSidebarNav(true)}>
|
||||
<BsTextIndentLeft className={`group-hover:text-primary h-full text-[30px] ${isPathSty || isScrolled ? 'text-[#333] dark:text-white' : 'text-white'} `} />
|
||||
@@ -104,7 +104,7 @@ export default () => {
|
||||
<ul className="hidden group-hover/one:block overflow-hidden absolute top-[50px] w-full rounded-md backdrop-blur-[5px] bg-[rgba(255,255,255,0.95)] dark:bg-[rgba(44,51,62,0.95)]" style={{ boxShadow: '0 12px 32px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.08)' }}>
|
||||
{one.children?.map((two) => (
|
||||
<li key={two.id} className="group/two">
|
||||
<Link href={`/cate/${two.id}?name=${two.name}`} target={`${two.url.startsWith('http') ? '_blank' : '_self'}`} className="relative inline-block w-full p-2.5 text-[15px] box-border text-[#666] dark:text-white hover:!text-primary transition-all after:content-[''] after:absolute after:left-2.5 after:top-1/2 after:-translate-y-1/2 after:w-0 after:h-[3px] after:bg-primary after:transition-width group-hover/two:bg-[#f2f2f2] dark:group-hover/two:bg-[#323e50] group-hover/two:pl-8 hover:after:w-2.5">
|
||||
<Link href={`/cate/${two.id}?name=${two.name}`} target={`${two.url.startsWith('http') ? '_blank' : '_self'}`} className="relative inline-block w-full p-2.5 text-[15px] box-border text-[#666] dark:text-white hover:!text-primary transition-[padding] after:content-[''] after:absolute after:left-2.5 after:top-1/2 after:-translate-y-1/2 after:w-0 after:h-[3px] after:bg-primary after:transition-[width] group-hover/two:bg-[#f2f2f2] dark:group-hover/two:bg-[#323e50] group-hover/two:pl-8 hover:after:w-2.5">
|
||||
{two.name}
|
||||
</Link>
|
||||
</li>
|
||||
@@ -129,7 +129,7 @@ export default () => {
|
||||
<ul className="hidden group-hover/one:block overflow-hidden absolute top-[50px] w-full rounded-md backdrop-blur-sm bg-[rgba(255,255,255,0.95)] dark:bg-[rgba(44,51,62,0.95)]" style={{ boxShadow: '0 12px 32px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.08)' }}>
|
||||
{one.children?.map((two) => (
|
||||
<li key={two.id} className="group/two relative">
|
||||
<Link href={two.url} className={`relative inline-block w-full p-2.5 pl-5 text-[15px] box-border text-[#666] dark:text-white hover:!text-primary transition-all after:content-[''] after:absolute after:left-2.5 after:top-1/2 after:-translate-y-1/2 after:w-0 after:h-[3px] after:bg-primary after:transition-width group-hover/two:pl-8 hover:after:w-2.5`}>
|
||||
<Link href={two.url} className={`relative inline-block w-full p-2.5 pl-5 text-[15px] box-border text-[#666] dark:text-white hover:!text-primary transition-[padding] after:content-[''] after:absolute after:left-2.5 after:top-1/2 after:-translate-y-1/2 after:w-0 after:h-[3px] after:bg-primary after:transition-[width] group-hover/two:pl-8 hover:after:w-2.5`}>
|
||||
{two.icon} {two.name}
|
||||
</Link>
|
||||
</li>
|
||||
@@ -148,7 +148,6 @@ export default () => {
|
||||
</div>
|
||||
|
||||
{/* 侧边导航:移动端时候显示 */}
|
||||
{/* <SidebarNav list={cateList} open={isOpenSidebarNav} onClose={() => setIsOpenSidebarNav(false)} /> */}
|
||||
<SidebarNav list={cateList} open={isOpenSidebarNav} onClose={() => setIsOpenSidebarNav(false)} />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -34,7 +34,7 @@ const HotArticle = () => {
|
||||
|
||||
<div className="w-full pt-2.5 mt-2 min-h-[120px] space-y-4">
|
||||
{list?.map((item, index) => (
|
||||
<div key={index} className="item relative h-32 bg-no-repeat bg-center rounded-md transition-all after:content-[''] after:absolute after:bottom-0 after:left-0 after:w-full after:h-12 after:transition-opacity after:rounded-md after:bg-[linear-gradient(transparent,#000)]" style={{ backgroundImage: `url(${item.cover || covers[getRandom(0, covers.length - 1)]})` }}>
|
||||
<div key={index} className="item relative h-32 bg-no-repeat bg-center rounded-md after:content-[''] after:absolute after:bottom-0 after:left-0 after:w-full after:h-12 after:transition-opacity after:rounded-md after:bg-[linear-gradient(transparent,#000)]" style={{ backgroundImage: `url(${item.cover || covers[getRandom(0, covers.length - 1)]})` }}>
|
||||
<Link href={`/article/${item.id}`} target="_blank" className="inline-block w-full h-full">
|
||||
<h4 className=" absolute bottom-2.5 w-[95%] px-2.5 text-white text-[15px] font-normal line-clamp-1 z-10">{item.title}</h4>
|
||||
</Link>
|
||||
|
||||
@@ -41,7 +41,7 @@ export default ({ data, className }: { data: SwiperType[]; className?: string })
|
||||
return (
|
||||
<>
|
||||
<div className={`group relative w-full h-[200px] sm:h-[270px] lg:h-[350px] rounded-2xl overflow-hidden after:content-[''] after:w-full after:h-[60%] after:absolute after:bottom-0 after:left-0 after:bg-[linear-gradient(to_top,#2c333e,transparent)] ${className}`} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
|
||||
<div onClick={handlePrev} className="flex justify-center items-center w-11 h-11 bg-[#fff3] rounded-full hover:bg-transparent hover:backdrop-blur-md hover:scale-110 group-hover:opacity-100 opacity-0 transition-all absolute top-1/2 left-3.5 -translate-y-1/2 z-20 cursor-pointer">
|
||||
<div onClick={handlePrev} className="flex justify-center items-center w-11 h-11 bg-[#fff3] rounded-full hover:bg-transparent hover:backdrop-blur-md hover:scale-110 group-hover:opacity-100 opacity-0 transition-[transform,opacity] absolute top-1/2 left-3.5 -translate-y-1/2 z-20 cursor-pointer">
|
||||
<BiChevronLeft className="text-4xl text-gray-100" />
|
||||
</div>
|
||||
|
||||
@@ -58,7 +58,7 @@ export default ({ data, className }: { data: SwiperType[]; className?: string })
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div onClick={handleNext} className="flex justify-center items-center w-11 h-11 bg-[#fff3] rounded-full hover:bg-transparent hover:backdrop-blur-md hover:scale-110 group-hover:opacity-100 opacity-0 transition-all absolute top-1/2 right-3.5 -translate-y-1/2 z-20 cursor-pointer">
|
||||
<div onClick={handleNext} className="flex justify-center items-center w-11 h-11 bg-[#fff3] rounded-full hover:bg-transparent hover:backdrop-blur-md hover:scale-110 group-hover:opacity-100 opacity-0 transition-[transform,opacity] absolute top-1/2 right-3.5 -translate-y-1/2 z-20 cursor-pointer">
|
||||
<BiChevronRight className="text-4xl text-gray-100" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -146,6 +146,50 @@ body {
|
||||
// 确保 transform 在动画结束后可以被 hover 覆盖
|
||||
}
|
||||
|
||||
// 头像区域:入场 + 轻微浮动 + 光晕旋转
|
||||
@keyframes avatar-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.88);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes avatar-float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-6px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tilt {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(-5deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(5deg);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-avatar-in {
|
||||
animation: avatar-in 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
||||
}
|
||||
|
||||
.animate-avatar-float {
|
||||
animation: avatar-float 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animate-tilt {
|
||||
animation: tilt 5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
// 瀑布流布局
|
||||
.masonry-grid {
|
||||
display: -webkit-box;
|
||||
|
||||
Reference in New Issue
Block a user