fix: improve project brief preview layout
This commit is contained in:
@@ -1545,6 +1545,34 @@
|
|||||||
"message": "auto-save 2026-05-20 14:45 (~4)",
|
"message": "auto-save 2026-05-20 14:45 (~4)",
|
||||||
"hash": "527ccfa",
|
"hash": "527ccfa",
|
||||||
"files_changed": 4
|
"files_changed": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ts": "2026-05-20T16:33:38+08:00",
|
||||||
|
"type": "commit",
|
||||||
|
"message": "auto-save 2026-05-20 16:33 (~2)",
|
||||||
|
"hash": "9b1833c",
|
||||||
|
"files_changed": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ts": "2026-05-20T16:39:05+08:00",
|
||||||
|
"type": "commit",
|
||||||
|
"message": "auto-save 2026-05-20 16:39 (~3)",
|
||||||
|
"hash": "e2465d8",
|
||||||
|
"files_changed": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ts": "2026-05-20T16:44:32+08:00",
|
||||||
|
"type": "commit",
|
||||||
|
"message": "auto-save 2026-05-20 16:44 (~2)",
|
||||||
|
"hash": "3b04b72",
|
||||||
|
"files_changed": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ts": "2026-05-20T16:48:16+08:00",
|
||||||
|
"type": "commit",
|
||||||
|
"message": "fix: improve project brief preview layout",
|
||||||
|
"hash": "61e597c",
|
||||||
|
"files_changed": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -479,7 +479,7 @@ input, textarea {
|
|||||||
|
|
||||||
.project-board-grid {
|
.project-board-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(250px, 310px) minmax(0, 1fr) 88px;
|
grid-template-columns: clamp(330px, 22vw, 410px) minmax(0, 1fr) 88px;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -499,18 +499,90 @@ input, textarea {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-primary-card {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 8px;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(255,255,255,0.08), rgba(255,255,255,0.028)),
|
||||||
|
rgba(0,0,0,0.18);
|
||||||
|
box-shadow:
|
||||||
|
0 20px 60px -40px rgba(230,245,120,0.72),
|
||||||
|
inset 0 0 0 1px rgba(255,255,255,0.08);
|
||||||
|
padding: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
.project-primary-preview {
|
.project-primary-preview {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
border-radius: 6px;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(255,255,255,0.98), rgba(238,242,230,0.93));
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
min-height: 260px;
|
||||||
|
max-height: min(42vh, 460px);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.52);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-primary-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
transform: scale(0.98);
|
||||||
|
transition: transform 200ms ease, filter 200ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-primary-card:hover .project-primary-image {
|
||||||
|
filter: contrast(1.02) saturate(1.02);
|
||||||
|
transform: scale(1.015);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-primary-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 9px 2px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-image-popover {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 120;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: min(76vw, 920px);
|
||||||
|
height: min(82vh, 780px);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border: 1px solid rgba(255,255,255,0.16);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: rgba(255,255,255,0.92);
|
background:
|
||||||
aspect-ratio: 4 / 3;
|
linear-gradient(135deg, rgba(255,255,255,0.10), rgba(255,255,255,0.035)),
|
||||||
min-height: 190px;
|
rgba(9, 10, 8, 0.86);
|
||||||
box-shadow:
|
box-shadow: 0 30px 120px -28px rgba(0,0,0,0.94);
|
||||||
0 20px 60px -38px rgba(230,245,120,0.7),
|
opacity: 0;
|
||||||
inset 0 0 0 1px rgba(255,255,255,0.36);
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translate(-50%, -48%) scale(0.96);
|
||||||
|
transition: opacity 180ms ease, transform 180ms ease;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-image-popover--open {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-image-popover img {
|
||||||
|
position: absolute;
|
||||||
|
inset: 18px;
|
||||||
|
width: calc(100% - 36px);
|
||||||
|
height: calc(100% - 36px);
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(255,255,255,0.96);
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-stat {
|
.project-stat {
|
||||||
@@ -534,6 +606,31 @@ input, textarea {
|
|||||||
padding: 13px;
|
padding: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-spec-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 42px minmax(0, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
align-items: start;
|
||||||
|
border-top: 1px solid rgba(255,255,255,0.06);
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-spec-row:first-child {
|
||||||
|
border-top: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-spec-label {
|
||||||
|
color: rgba(255,255,255,0.36);
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-spec-value {
|
||||||
|
color: rgba(255,255,255,0.78);
|
||||||
|
line-height: 1.55;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
.project-production-panel {
|
.project-production-panel {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -642,11 +739,22 @@ input, textarea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.project-board-grid {
|
.project-board-grid {
|
||||||
grid-template-columns: minmax(280px, 340px) minmax(0, 1fr) 96px;
|
grid-template-columns: clamp(370px, 22vw, 440px) minmax(0, 1fr) 96px;
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.project-primary-image,
|
||||||
|
.project-image-popover {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-primary-card:hover .project-primary-image {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 1180px) {
|
@media (max-width: 1180px) {
|
||||||
.project-shell {
|
.project-shell {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@@ -677,6 +785,14 @@ input, textarea {
|
|||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-primary-preview {
|
||||||
|
min-height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-image-popover {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.session-workspace {
|
.session-workspace {
|
||||||
height: auto;
|
height: auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
import PromptPanel from '@/components/PromptPanel';
|
import PromptPanel from '@/components/PromptPanel';
|
||||||
import Sidebar from '@/components/Sidebar';
|
import Sidebar from '@/components/Sidebar';
|
||||||
import PackPanel from '@/components/PackPanel';
|
import PackPanel from '@/components/PackPanel';
|
||||||
@@ -55,6 +56,18 @@ function ProjectStat({ label, value, tone }: { label: string; value: string | nu
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ProjectSpecField({ label, value }: { label: string; value?: string | string[] }) {
|
||||||
|
const text = Array.isArray(value) ? value.filter(Boolean).join('、') : value;
|
||||||
|
if (!text) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="project-spec-row">
|
||||||
|
<span className="project-spec-label">{label}</span>
|
||||||
|
<span className="project-spec-value">{text}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ReferenceStrip({ session }: { session: GenSession }) {
|
function ReferenceStrip({ session }: { session: GenSession }) {
|
||||||
const refs = imageSourcesForSession(session);
|
const refs = imageSourcesForSession(session);
|
||||||
if (!refs.length) return null;
|
if (!refs.length) return null;
|
||||||
@@ -76,6 +89,7 @@ function ProjectBrief({ session }: { session: GenSession }) {
|
|||||||
const primaryImage = selectedImages[0] ?? session.images[0] ?? null;
|
const primaryImage = selectedImages[0] ?? session.images[0] ?? null;
|
||||||
const generatedAssets = (session.packs ?? []).reduce((sum, pack) => sum + pack.assets.length, 0);
|
const generatedAssets = (session.packs ?? []).reduce((sum, pack) => sum + pack.assets.length, 0);
|
||||||
const totalSlots = packSlotTotal();
|
const totalSlots = packSlotTotal();
|
||||||
|
const [previewOpen, setPreviewOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="project-brief-panel">
|
<section className="project-brief-panel">
|
||||||
@@ -93,12 +107,26 @@ function ProjectBrief({ session }: { session: GenSession }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{primaryImage && (
|
{primaryImage && (
|
||||||
<div className="project-primary-preview mt-5">
|
<div
|
||||||
<img src={primaryImage.url} alt="当前主方案" className="h-full w-full object-contain" />
|
className="project-primary-card mt-5"
|
||||||
<div className="absolute inset-x-0 bottom-0 flex items-center justify-between gap-3 bg-gradient-to-t from-black/72 to-transparent p-3">
|
onMouseEnter={() => setPreviewOpen(true)}
|
||||||
|
onMouseLeave={() => setPreviewOpen(false)}
|
||||||
|
onFocus={() => setPreviewOpen(true)}
|
||||||
|
onBlur={() => setPreviewOpen(false)}
|
||||||
|
>
|
||||||
|
<div className="project-primary-preview">
|
||||||
|
<img src={primaryImage.url} alt="当前主方案" className="project-primary-image" />
|
||||||
|
</div>
|
||||||
|
<div className="project-primary-meta">
|
||||||
<span className="text-[10px] font-semibold uppercase tracking-[0.16em] text-white/70">Primary</span>
|
<span className="text-[10px] font-semibold uppercase tracking-[0.16em] text-white/70">Primary</span>
|
||||||
<span className="text-[10px] text-white/50">{primaryImage.status === 'selected' ? '已选中' : '待筛选'}</span>
|
<span className="text-[10px] text-white/50">{primaryImage.status === 'selected' ? '已选中' : '待筛选'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
{previewOpen && typeof document !== 'undefined' && createPortal(
|
||||||
|
<div className="project-image-popover project-image-popover--open" aria-hidden="true">
|
||||||
|
<img src={primaryImage.url} alt="" />
|
||||||
|
</div>,
|
||||||
|
document.body,
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -115,17 +143,14 @@ function ProjectBrief({ session }: { session: GenSession }) {
|
|||||||
<span className="text-[10px] text-[#e6f578]/70">已锁定</span>
|
<span className="text-[10px] text-[#e6f578]/70">已锁定</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 space-y-2 text-[11px]">
|
<div className="mt-3 space-y-2 text-[11px]">
|
||||||
{[
|
<ProjectSpecField label="形态" value={session.characterSpec.speciesShape} />
|
||||||
['形态', session.characterSpec.speciesShape],
|
<ProjectSpecField label="比例" value={session.characterSpec.bodyRatio} />
|
||||||
['比例', session.characterSpec.bodyRatio],
|
<ProjectSpecField label="五官" value={session.characterSpec.faceFeatures} />
|
||||||
['配色', session.characterSpec.colorPalette.join('、')],
|
<ProjectSpecField label="配色" value={session.characterSpec.colorPalette} />
|
||||||
['材料', session.characterSpec.materials.join('、')],
|
<ProjectSpecField label="材料" value={session.characterSpec.materials} />
|
||||||
].map(([label, value]) => (
|
<ProjectSpecField label="配件" value={session.characterSpec.accessories} />
|
||||||
<div key={label} className="grid grid-cols-[42px_minmax(0,1fr)] gap-2">
|
<ProjectSpecField label="识别" value={session.characterSpec.signatureElements} />
|
||||||
<span className="text-white/35">{label}</span>
|
<ProjectSpecField label="打样" value={session.characterSpec.manufacturingNotes} />
|
||||||
<span className="truncate text-white/75">{value}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Reference in New Issue
Block a user