Refine gallery border flow thickness
This commit is contained in:
@@ -707,114 +707,113 @@ export default function GalleryPage() {
|
||||
{columnWorks.map((work) => {
|
||||
const mediaPreviewUrl = work.thumbnailUrl || (work.url && !work.url.startsWith('data:') ? work.url : '');
|
||||
return (
|
||||
<div
|
||||
key={work.id}
|
||||
className="gallery-work-shell group"
|
||||
style={getGalleryCardStyle(cardPalettes[work.id])}
|
||||
>
|
||||
<div className="gallery-glow-layer" aria-hidden="true" />
|
||||
<Card
|
||||
className={`${galleryGlassCard} gallery-work-card w-full overflow-hidden cursor-pointer !rounded-2xl !py-0`}
|
||||
onClick={() => setSelectedWork(work)}
|
||||
>
|
||||
<div className="relative overflow-hidden bg-black/25">
|
||||
{mediaPreviewUrl ? (
|
||||
<img
|
||||
src={mediaPreviewUrl}
|
||||
alt={(work.prompt || '').slice(0, 30)}
|
||||
className="block h-auto w-full object-contain"
|
||||
loading="lazy"
|
||||
onLoad={(e) => handleCardImageLoad(work.id, e)}
|
||||
onDoubleClick={(e) => { e.stopPropagation(); setFullscreenSrc(work.url); }}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex aspect-square w-full flex-col items-center justify-center bg-gradient-to-br from-muted to-muted/50">
|
||||
<Sparkles className="h-8 w-8 text-muted-foreground/20" />
|
||||
</div>
|
||||
)}
|
||||
{isAdmin && apiWorkIds.has(work.id) && (
|
||||
<button
|
||||
className={`absolute left-2 top-2 z-20 flex h-7 w-7 items-center justify-center rounded-lg border text-xs font-semibold backdrop-blur-md transition-colors ${
|
||||
selectedGalleryIds.has(work.id)
|
||||
? 'border-primary/60 bg-primary text-primary-foreground'
|
||||
: 'border-white/20 bg-black/45 text-white hover:bg-black/65'
|
||||
}`}
|
||||
onClick={(e) => toggleSelectGalleryWork(work.id, e)}
|
||||
title={selectedGalleryIds.has(work.id) ? '取消选择' : '选择作品'}
|
||||
<div
|
||||
key={work.id}
|
||||
className="gallery-work-shell group"
|
||||
style={getGalleryCardStyle(cardPalettes[work.id])}
|
||||
>
|
||||
{selectedGalleryIds.has(work.id) ? '✓' : ''}
|
||||
</button>
|
||||
)}
|
||||
{(work.type === 'video' || work.type === 'text2video' || work.type === 'img2video') && (
|
||||
<Badge className={`absolute left-2 ${isAdmin && apiWorkIds.has(work.id) ? 'top-11' : 'top-2'}`} variant="secondary">
|
||||
<Film className="h-3 w-3 mr-1" />视频
|
||||
</Badge>
|
||||
)}
|
||||
<Badge className="absolute top-2 right-2" variant="secondary">
|
||||
{getCategoryLabel(work)}
|
||||
</Badge>
|
||||
<div className="pointer-events-none absolute inset-x-3 bottom-3 z-20 flex translate-y-2 justify-center gap-2 opacity-0 transition-all duration-300 ease-out group-hover:translate-y-0 group-hover:opacity-100">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="gallery-work-action-button pointer-events-auto h-9 w-9 p-0"
|
||||
onClick={(e) => toggleLike(work.id, e)}
|
||||
title="点赞"
|
||||
<Card
|
||||
className={`${galleryGlassCard} gallery-work-card w-full overflow-hidden cursor-pointer !rounded-2xl !py-0`}
|
||||
onClick={() => setSelectedWork(work)}
|
||||
>
|
||||
<Heart className={`h-4 w-4 ${likedIds.has(work.id) ? 'fill-current' : ''}`} />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="gallery-work-action-button pointer-events-auto h-9 w-9 p-0"
|
||||
onClick={(e) => handleDownload(work.url, `miaojing-${work.id}.png`, e)}
|
||||
title="下载"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
{isAdmin && apiWorkIds.has(work.id) && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
className="pointer-events-auto h-9 w-9 rounded-full p-0 shadow-[0_12px_28px_rgba(0,0,0,0.34)]"
|
||||
onClick={(e) => handleDeleteGalleryWorks([work.id], e)}
|
||||
title="从画廊删除"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<CardContent className="flex h-[152px] flex-col p-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<div className="flex h-6 w-6 shrink-0 items-center justify-center overflow-hidden rounded-full bg-primary/15 text-xs font-semibold text-primary ring-1 ring-primary/25">
|
||||
{work.publisherAvatarUrl ? (
|
||||
<img
|
||||
src={work.publisherAvatarUrl}
|
||||
alt={work.publisherNickname}
|
||||
className="h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
getAvatarText(work.publisherNickname)
|
||||
)}
|
||||
</div>
|
||||
<span className="truncate text-sm font-medium">
|
||||
{work.publisherNickname}
|
||||
</span>
|
||||
<div className="relative overflow-hidden bg-black/25">
|
||||
{mediaPreviewUrl ? (
|
||||
<img
|
||||
src={mediaPreviewUrl}
|
||||
alt={(work.prompt || '').slice(0, 30)}
|
||||
className="block h-auto w-full object-contain"
|
||||
loading="lazy"
|
||||
onLoad={(e) => handleCardImageLoad(work.id, e)}
|
||||
onDoubleClick={(e) => { e.stopPropagation(); setFullscreenSrc(work.url); }}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex aspect-square w-full flex-col items-center justify-center bg-gradient-to-br from-muted to-muted/50">
|
||||
<Sparkles className="h-8 w-8 text-muted-foreground/20" />
|
||||
</div>
|
||||
)}
|
||||
{isAdmin && apiWorkIds.has(work.id) && (
|
||||
<button
|
||||
className={`absolute left-2 top-2 z-20 flex h-7 w-7 items-center justify-center rounded-lg border text-xs font-semibold backdrop-blur-md transition-colors ${
|
||||
selectedGalleryIds.has(work.id)
|
||||
? 'border-primary/60 bg-primary text-primary-foreground'
|
||||
: 'border-white/20 bg-black/45 text-white hover:bg-black/65'
|
||||
}`}
|
||||
onClick={(e) => toggleSelectGalleryWork(work.id, e)}
|
||||
title={selectedGalleryIds.has(work.id) ? '取消选择' : '选择作品'}
|
||||
>
|
||||
{selectedGalleryIds.has(work.id) ? '✓' : ''}
|
||||
</button>
|
||||
)}
|
||||
{(work.type === 'video' || work.type === 'text2video' || work.type === 'img2video') && (
|
||||
<Badge className={`absolute left-2 ${isAdmin && apiWorkIds.has(work.id) ? 'top-11' : 'top-2'}`} variant="secondary">
|
||||
<Film className="h-3 w-3 mr-1" />视频
|
||||
</Badge>
|
||||
)}
|
||||
<Badge className="absolute top-2 right-2" variant="secondary">
|
||||
{getCategoryLabel(work)}
|
||||
</Badge>
|
||||
<div className="pointer-events-none absolute inset-x-3 bottom-3 z-20 flex translate-y-2 justify-center gap-2 opacity-0 transition-all duration-300 ease-out group-hover:translate-y-0 group-hover:opacity-100">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="gallery-work-action-button pointer-events-auto h-9 w-9 p-0"
|
||||
onClick={(e) => toggleLike(work.id, e)}
|
||||
title="点赞"
|
||||
>
|
||||
<Heart className={`h-4 w-4 ${likedIds.has(work.id) ? 'fill-current' : ''}`} />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="gallery-work-action-button pointer-events-auto h-9 w-9 p-0"
|
||||
onClick={(e) => handleDownload(work.url, `miaojing-${work.id}.png`, e)}
|
||||
title="下载"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
{isAdmin && apiWorkIds.has(work.id) && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
className="pointer-events-auto h-9 w-9 rounded-full p-0 shadow-[0_12px_28px_rgba(0,0,0,0.34)]"
|
||||
onClick={(e) => handleDeleteGalleryWorks([work.id], e)}
|
||||
title="从画廊删除"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<CardContent className="flex h-[152px] flex-col p-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<div className="flex h-6 w-6 shrink-0 items-center justify-center overflow-hidden rounded-full bg-primary/15 text-xs font-semibold text-primary ring-1 ring-primary/25">
|
||||
{work.publisherAvatarUrl ? (
|
||||
<img
|
||||
src={work.publisherAvatarUrl}
|
||||
alt={work.publisherNickname}
|
||||
className="h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
) : (
|
||||
getAvatarText(work.publisherNickname)
|
||||
)}
|
||||
</div>
|
||||
<span className="truncate text-sm font-medium">
|
||||
{work.publisherNickname}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<Heart className={`h-3 w-3 ${likedIds.has(work.id) ? 'fill-rose-500 text-rose-500' : ''}`} />
|
||||
{work.likes + (likedIds.has(work.id) ? 1 : 0)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 h-[100px] overflow-hidden whitespace-pre-wrap break-words text-xs leading-5 text-muted-foreground line-clamp-5">
|
||||
{work.prompt}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<Heart className={`h-3 w-3 ${likedIds.has(work.id) ? 'fill-rose-500 text-rose-500' : ''}`} />
|
||||
{work.likes + (likedIds.has(work.id) ? 1 : 0)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 h-[100px] overflow-hidden whitespace-pre-wrap break-words text-xs leading-5 text-muted-foreground line-clamp-5">
|
||||
{work.prompt}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -1106,5 +1105,3 @@ export default function GalleryPage() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -227,10 +227,10 @@
|
||||
.gallery-work-shell::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -2px;
|
||||
inset: -3px;
|
||||
z-index: -1;
|
||||
padding: 2px;
|
||||
border-radius: 1.12rem;
|
||||
padding: 3px;
|
||||
border-radius: 1.18rem;
|
||||
background:
|
||||
conic-gradient(
|
||||
from 0deg,
|
||||
@@ -260,10 +260,10 @@
|
||||
.gallery-work-shell::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -6px;
|
||||
inset: -7px;
|
||||
z-index: -2;
|
||||
padding: 6px;
|
||||
border-radius: 1.32rem;
|
||||
padding: 7px;
|
||||
border-radius: 1.38rem;
|
||||
background:
|
||||
conic-gradient(
|
||||
from 0deg,
|
||||
@@ -276,7 +276,7 @@
|
||||
transparent 150deg,
|
||||
transparent 360deg
|
||||
);
|
||||
filter: blur(6px) saturate(1.4);
|
||||
filter: blur(7px) saturate(1.45);
|
||||
opacity: 0;
|
||||
transition: opacity 320ms ease;
|
||||
animation: gallery-border-flow 3.6s linear infinite;
|
||||
@@ -291,41 +291,6 @@
|
||||
mask-composite: exclude;
|
||||
}
|
||||
|
||||
/* 外围辉光层 */
|
||||
.gallery-work-shell .gallery-glow-layer {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -14px;
|
||||
z-index: -3;
|
||||
border-radius: 1.6rem;
|
||||
background:
|
||||
conic-gradient(
|
||||
from 0deg,
|
||||
transparent 0deg,
|
||||
transparent 56deg,
|
||||
var(--gallery-accent-1) 66deg,
|
||||
var(--gallery-accent-2) 86deg,
|
||||
var(--gallery-accent-3) 114deg,
|
||||
var(--gallery-accent-1) 142deg,
|
||||
transparent 150deg,
|
||||
transparent 360deg
|
||||
);
|
||||
filter: blur(18px) saturate(1.8);
|
||||
opacity: 0;
|
||||
transition: opacity 420ms ease;
|
||||
animation: gallery-border-flow 3.6s linear infinite;
|
||||
animation-play-state: paused;
|
||||
-webkit-mask:
|
||||
linear-gradient(#000 0 0) content-box,
|
||||
linear-gradient(#000 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask:
|
||||
linear-gradient(#000 0 0) content-box,
|
||||
linear-gradient(#000 0 0);
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.gallery-work-shell:hover {
|
||||
z-index: 10;
|
||||
transform: translateY(-6px) scale(1.02);
|
||||
@@ -338,12 +303,7 @@
|
||||
}
|
||||
|
||||
.gallery-work-shell:hover::after {
|
||||
opacity: 0.55;
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
.gallery-work-shell:hover .gallery-glow-layer {
|
||||
opacity: 0.32;
|
||||
opacity: 0.64;
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user