Initial commit: CarrotSkin project setup
This commit is contained in:
@@ -8,6 +8,11 @@
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
--navbar-height: 64px; /* 与pt-16对应 */
|
||||
--primary-orange: #f97316;
|
||||
--primary-orange-dark: #ea580c;
|
||||
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--transition-normal: 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--transition-slow: 500ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@@ -21,6 +26,7 @@ body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-family: 'Inter', Arial, Helvetica, sans-serif;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Custom utility classes */
|
||||
@@ -28,34 +34,65 @@ body {
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
/* Custom component classes */
|
||||
/* Enhanced Custom component classes with micro-interactions */
|
||||
.btn-carrot {
|
||||
background-color: #f97316;
|
||||
background-color: var(--primary-orange);
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: background-color 0.2s;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
transition: all var(--transition-normal);
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-carrot:hover {
|
||||
background-color: #ea580c;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
background-color: var(--primary-orange-dark);
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-carrot:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn-carrot::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left var(--transition-slow);
|
||||
}
|
||||
|
||||
.btn-carrot:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.btn-carrot-outline {
|
||||
border: 2px solid #f97316;
|
||||
color: #f97316;
|
||||
border: 2px solid var(--primary-orange);
|
||||
color: var(--primary-orange);
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: all 0.2s;
|
||||
transition: all var(--transition-normal);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-carrot-outline:hover {
|
||||
background-color: #f97316;
|
||||
background-color: var(--primary-orange);
|
||||
color: white;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 15px -3px rgba(249, 115, 22, 0.3);
|
||||
}
|
||||
|
||||
.btn-carrot-outline:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.card-minecraft {
|
||||
@@ -63,11 +100,31 @@ body {
|
||||
border: 2px solid #fed7aa;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s;
|
||||
transition: all var(--transition-normal);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-minecraft:hover {
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-4px);
|
||||
border-color: var(--primary-orange);
|
||||
}
|
||||
|
||||
.card-minecraft::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, rgba(249, 115, 22, 0.1) 0%, transparent 50%);
|
||||
opacity: 0;
|
||||
transition: opacity var(--transition-normal);
|
||||
}
|
||||
|
||||
.card-minecraft:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@@ -75,6 +132,10 @@ body {
|
||||
background-color: #1f2937;
|
||||
border-color: #c2410c;
|
||||
}
|
||||
|
||||
.card-minecraft:hover {
|
||||
border-color: var(--primary-orange);
|
||||
}
|
||||
}
|
||||
|
||||
.text-gradient {
|
||||
@@ -82,10 +143,18 @@ body {
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.text-gradient:hover {
|
||||
background: linear-gradient(to right, #f97316, #ea580c);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
}
|
||||
|
||||
.bg-gradient-carrot {
|
||||
background: linear-gradient(to bottom right, #fb923c, #f97316, #ea580c);
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
/* 现代布局解决方案 */
|
||||
@@ -104,4 +173,277 @@ body {
|
||||
.min-h-screen-nav {
|
||||
min-height: calc(100vh - var(--navbar-height));
|
||||
}
|
||||
|
||||
/* 增强的过渡效果 */
|
||||
.transition-all-enhanced {
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.transition-colors-enhanced {
|
||||
transition: color var(--transition-normal), background-color var(--transition-normal), border-color var(--transition-normal);
|
||||
}
|
||||
|
||||
.transition-transform-enhanced {
|
||||
transition: transform var(--transition-normal);
|
||||
}
|
||||
|
||||
/* 微交互效果 */
|
||||
.micro-interaction {
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.micro-interaction:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.micro-interaction:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.animate-pulse-slow {
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
.animate-pulse-fast {
|
||||
animation: pulse 1s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
/* 弹跳动画 */
|
||||
.animate-bounce-slow {
|
||||
animation: bounce 2s infinite;
|
||||
}
|
||||
|
||||
.animate-bounce-fast {
|
||||
animation: bounce 1s infinite;
|
||||
}
|
||||
|
||||
/* 旋转动画 */
|
||||
.animate-spin-slow {
|
||||
animation: spin 3s linear infinite;
|
||||
}
|
||||
|
||||
.animate-spin-fast {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* 渐变动画 */
|
||||
.animate-gradient {
|
||||
background-size: 200% 200%;
|
||||
animation: gradient 3s ease infinite;
|
||||
}
|
||||
|
||||
/* 阴影动画 */
|
||||
.shadow-animated {
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
transition: box-shadow var(--transition-normal);
|
||||
}
|
||||
|
||||
.shadow-animated:hover {
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* 模糊动画 */
|
||||
.backdrop-blur-animated {
|
||||
backdrop-filter: blur(8px);
|
||||
transition: backdrop-filter var(--transition-normal);
|
||||
}
|
||||
|
||||
.backdrop-blur-animated:hover {
|
||||
backdrop-filter: blur(16px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 自定义关键帧动画 */
|
||||
@keyframes gradient {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInUp {
|
||||
from {
|
||||
transform: translateY(30px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInDown {
|
||||
from {
|
||||
transform: translateY(-30px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInLeft {
|
||||
from {
|
||||
transform: translateX(-30px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from {
|
||||
transform: translateX(30px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
from {
|
||||
transform: scale(0.9);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scaleOut {
|
||||
from {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: scale(0.9);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画工具类 */
|
||||
.animate-slide-in-up {
|
||||
animation: slideInUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
.animate-slide-in-down {
|
||||
animation: slideInDown 0.3s ease-out;
|
||||
}
|
||||
|
||||
.animate-slide-in-left {
|
||||
animation: slideInLeft 0.3s ease-out;
|
||||
}
|
||||
|
||||
.animate-slide-in-right {
|
||||
animation: slideInRight 0.3s ease-out;
|
||||
}
|
||||
|
||||
.animate-scale-in {
|
||||
animation: scaleIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
.animate-scale-out {
|
||||
animation: scaleOut 0.2s ease-out;
|
||||
}
|
||||
|
||||
/* 加载状态样式 */
|
||||
.loading-shimmer {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
#f0f0f0 0%,
|
||||
#e0e0e0 50%,
|
||||
#f0f0f0 100%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
.dark .loading-shimmer {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
#374151 0%,
|
||||
#4b5563 50%,
|
||||
#374151 100%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
/* 焦点样式 */
|
||||
.focus-visible-enhanced {
|
||||
outline: 2px solid var(--primary-orange);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: var(--primary-orange);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--primary-orange-dark);
|
||||
}
|
||||
|
||||
/* 响应式动效 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 触摸设备优化 */
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
.btn-carrot:hover,
|
||||
.btn-carrot-outline:hover,
|
||||
.card-minecraft:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.btn-carrot:active,
|
||||
.btn-carrot-outline:active,
|
||||
.card-minecraft:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import "./globals.css";
|
||||
import Navbar from "@/components/Navbar";
|
||||
import { AuthProvider } from "@/contexts/AuthContext";
|
||||
import { MainContent } from "@/components/MainContent";
|
||||
import { MessageNotificationContainer } from "@/components/MessageNotification";
|
||||
import { ErrorNotificationContainer } from "@/components/ErrorNotification";
|
||||
import ScrollToTop from "@/components/ScrollToTop";
|
||||
import PageTransition from "@/components/PageTransition";
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
@@ -35,8 +37,11 @@ export default function RootLayout({
|
||||
<body className={inter.className}>
|
||||
<AuthProvider>
|
||||
<Navbar />
|
||||
<MainContent>{children}</MainContent>
|
||||
<PageTransition>
|
||||
<MainContent>{children}</MainContent>
|
||||
</PageTransition>
|
||||
<ErrorNotificationContainer />
|
||||
<MessageNotificationContainer />
|
||||
<ScrollToTop />
|
||||
</AuthProvider>
|
||||
</body>
|
||||
|
||||
@@ -1,108 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { motion } from 'framer-motion';
|
||||
import { HomeIcon, ArrowLeftIcon } from '@heroicons/react/24/outline';
|
||||
import { NotFoundPage } from '@/components/ErrorPage';
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-orange-50 via-white to-amber-50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900">
|
||||
<motion.div
|
||||
className="text-center px-4"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, ease: 'easeOut' }}
|
||||
>
|
||||
{/* 404 数字 */}
|
||||
<motion.div
|
||||
className="mb-8"
|
||||
initial={{ scale: 0.5, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ delay: 0.2, duration: 0.6, type: 'spring', stiffness: 200 }}
|
||||
>
|
||||
<h1 className="text-9xl font-black bg-gradient-to-r from-orange-400 via-orange-500 to-amber-500 bg-clip-text text-transparent mb-4">
|
||||
404
|
||||
</h1>
|
||||
<div className="w-24 h-1 bg-gradient-to-r from-orange-400 to-amber-500 mx-auto rounded-full" />
|
||||
</motion.div>
|
||||
|
||||
{/* 错误信息 */}
|
||||
<motion.div
|
||||
className="mb-8"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.4, duration: 0.6 }}
|
||||
>
|
||||
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
页面不见了
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-400 max-w-md mx-auto leading-relaxed">
|
||||
抱歉,我们找不到您要访问的页面。它可能已被移动、删除,或者您输入的链接不正确。
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Minecraft 风格的装饰 */}
|
||||
<motion.div
|
||||
className="mb-8 flex justify-center"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: 0.6, duration: 0.6 }}
|
||||
>
|
||||
<div className="relative">
|
||||
<div className="w-16 h-16 bg-gradient-to-br from-orange-400 to-amber-500 rounded-lg flex items-center justify-center transform rotate-12 shadow-lg">
|
||||
<span className="text-2xl font-bold text-white">?</span>
|
||||
</div>
|
||||
<div className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 rounded-full flex items-center justify-center animate-pulse">
|
||||
<span className="text-white text-xs font-bold">!</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<motion.div
|
||||
className="flex flex-col sm:flex-row gap-4 justify-center items-center"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.8, duration: 0.6 }}
|
||||
>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center px-6 py-3 bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white font-semibold rounded-xl transition-all duration-200 shadow-lg hover:shadow-xl transform hover:scale-105"
|
||||
>
|
||||
<HomeIcon className="w-5 h-5 mr-2" />
|
||||
返回主页
|
||||
</Link>
|
||||
|
||||
<button
|
||||
onClick={() => window.history.back()}
|
||||
className="inline-flex items-center px-6 py-3 border-2 border-orange-500 text-orange-500 hover:bg-orange-500 hover:text-white font-semibold rounded-xl transition-all duration-200"
|
||||
>
|
||||
<ArrowLeftIcon className="w-5 h-5 mr-2" />
|
||||
返回上页
|
||||
</button>
|
||||
</motion.div>
|
||||
|
||||
{/* 额外的帮助信息 */}
|
||||
<motion.div
|
||||
className="mt-8 text-sm text-gray-500 dark:text-gray-400"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1, duration: 0.6 }}
|
||||
>
|
||||
<p>如果问题持续存在,请
|
||||
<Link href="/contact" className="text-orange-500 hover:text-orange-600 underline">
|
||||
联系我们
|
||||
</Link>
|
||||
的支持团队
|
||||
</p>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* 背景装饰 */}
|
||||
<div className="fixed inset-0 -z-10 overflow-hidden">
|
||||
<div className="absolute top-1/4 left-1/4 w-64 h-64 bg-orange-200/20 dark:bg-orange-900/20 rounded-full blur-3xl" />
|
||||
<div className="absolute bottom-1/4 right-1/4 w-96 h-96 bg-amber-200/20 dark:bg-amber-900/20 rounded-full blur-3xl" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <NotFoundPage />;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,10 @@ import { MagnifyingGlassIcon, EyeIcon, HeartIcon, ArrowDownTrayIcon, SparklesIco
|
||||
import { HeartIcon as HeartIconSolid } from '@heroicons/react/24/solid';
|
||||
import SkinViewer from '@/components/SkinViewer';
|
||||
import SkinDetailModal from '@/components/SkinDetailModal';
|
||||
import SkinCard from '@/components/SkinCard';
|
||||
import { searchTextures, toggleFavorite, type Texture } from '@/lib/api';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import { messageManager } from '@/components/MessageNotification';
|
||||
|
||||
export default function SkinsPage() {
|
||||
const [textures, setTextures] = useState<Texture[]>([]);
|
||||
@@ -106,7 +108,7 @@ export default function SkinsPage() {
|
||||
// 处理收藏
|
||||
const handleFavorite = async (textureId: number) => {
|
||||
if (!isAuthenticated) {
|
||||
alert('请先登录');
|
||||
messageManager.warning('请先登录', { duration: 3000 });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -309,130 +311,15 @@ export default function SkinsPage() {
|
||||
const isFavorited = favoritedIds.has(texture.id);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
<SkinCard
|
||||
key={texture.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className="group relative bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 overflow-hidden border border-white/20 dark:border-gray-700/30"
|
||||
>
|
||||
{/* 3D预览区域 - 更紧凑 */}
|
||||
<div className="relative aspect-square bg-gradient-to-br from-orange-50 to-amber-50 dark:from-gray-700 dark:to-gray-600 overflow-hidden">
|
||||
<SkinViewer
|
||||
skinUrl={texture.url}
|
||||
isSlim={texture.is_slim}
|
||||
width={300}
|
||||
height={300}
|
||||
className="w-full h-full"
|
||||
autoRotate={true}
|
||||
walking={false}
|
||||
/>
|
||||
|
||||
{/* 悬停操作按钮 */}
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
|
||||
<div className="flex gap-3">
|
||||
<motion.button
|
||||
onClick={() => handleDetailView(texture)}
|
||||
className="bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white p-3 rounded-full shadow-lg transition-all duration-200"
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
title="详细预览"
|
||||
>
|
||||
<EyeIcon className="w-5 h-5" />
|
||||
</motion.button>
|
||||
|
||||
<motion.button
|
||||
onClick={() => window.open(texture.url, '_blank')}
|
||||
className="bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white p-3 rounded-full shadow-lg transition-all duration-200"
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
title="查看原图"
|
||||
>
|
||||
<ArrowDownTrayIcon className="w-5 h-5" />
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 标签 */}
|
||||
<div className="absolute top-3 right-3 flex gap-1.5">
|
||||
<motion.span
|
||||
className={`px-2 py-1 text-white text-xs rounded-full font-medium backdrop-blur-sm ${
|
||||
texture.type === 'SKIN' ? 'bg-blue-500/80' : 'bg-purple-500/80'
|
||||
}`}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
>
|
||||
{texture.type === 'SKIN' ? '皮肤' : '披风'}
|
||||
</motion.span>
|
||||
{texture.is_slim && (
|
||||
<motion.span
|
||||
className="px-2 py-1 bg-pink-500/80 text-white text-xs rounded-full font-medium backdrop-blur-sm"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
>
|
||||
细臂
|
||||
</motion.span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Texture Info */}
|
||||
<div className="p-4">
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white mb-1 truncate">{texture.name}</h3>
|
||||
{texture.description && (
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3 line-clamp-2 leading-relaxed">
|
||||
{texture.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Stats */}
|
||||
<div className="flex items-center justify-between text-sm text-gray-500 dark:text-gray-400 mb-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<motion.span
|
||||
className="flex items-center space-x-1"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
>
|
||||
<HeartIcon className="w-4 h-4 text-red-400" />
|
||||
<span className="font-medium">{texture.favorite_count}</span>
|
||||
</motion.span>
|
||||
<motion.span
|
||||
className="flex items-center space-x-1"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
>
|
||||
<ArrowDownTrayIcon className="w-4 h-4 text-blue-400" />
|
||||
<span className="font-medium">{texture.download_count}</span>
|
||||
</motion.span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex gap-2">
|
||||
<motion.button
|
||||
onClick={() => handleDetailView(texture)}
|
||||
className="flex-1 bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white text-sm py-2 px-3 rounded-lg transition-all duration-200 font-medium shadow-md hover:shadow-lg flex items-center justify-center"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
<EyeIcon className="w-4 h-4 mr-1" />
|
||||
详细预览
|
||||
</motion.button>
|
||||
<motion.button
|
||||
onClick={() => handleFavorite(texture.id)}
|
||||
className={`px-3 py-2 border rounded-lg transition-all duration-200 font-medium ${
|
||||
isFavorited
|
||||
? 'bg-gradient-to-r from-red-500 to-pink-500 border-transparent text-white shadow-md'
|
||||
: 'border-orange-500 text-orange-500 hover:bg-gradient-to-r hover:from-orange-500 hover:to-orange-600 hover:text-white hover:border-transparent hover:shadow-md'
|
||||
}`}
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
{isFavorited ? (
|
||||
<HeartIconSolid className="w-4 h-4" />
|
||||
) : (
|
||||
<HeartIcon className="w-4 h-4" />
|
||||
)}
|
||||
</motion.button>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
texture={texture}
|
||||
isFavorited={isFavorited}
|
||||
onViewDetails={handleDetailView}
|
||||
onToggleFavorite={isAuthenticated ? handleFavorite : undefined}
|
||||
onDownload={(texture) => window.open(texture.url, '_blank')}
|
||||
showVisibilityBadge={false}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</motion.div>
|
||||
|
||||
Reference in New Issue
Block a user