Merge remote-tracking branch 'origin/master' into codex/wooden-fish-template
This commit is contained in:
@@ -22,7 +22,7 @@ const PLACEHOLDER_PUZZLE_IMAGE =
|
||||
<defs>
|
||||
<linearGradient id="sky" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#fef3c7" />
|
||||
<stop offset="0.45" stop-color="#fb7185" />
|
||||
<stop offset="0.45" stop-color="#c7653d" />
|
||||
<stop offset="1" stop-color="#312e81" />
|
||||
</linearGradient>
|
||||
<radialGradient id="glow" cx="42%" cy="34%" r="46%">
|
||||
|
||||
@@ -173,7 +173,7 @@ function ImageFrame({
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`overflow-hidden rounded-2xl border border-[var(--platform-subpanel-border)] bg-[radial-gradient(circle_at_top,rgba(255,255,255,0.22),transparent_42%),linear-gradient(180deg,rgba(255,96,147,0.92),rgba(255,146,109,0.84))] ${tone === 'landscape' ? 'aspect-[16/9]' : 'aspect-square'}`}
|
||||
className={`overflow-hidden rounded-2xl border border-[var(--platform-subpanel-border)] bg-[radial-gradient(circle_at_top,rgba(255,255,255,0.22),transparent_42%),linear-gradient(180deg,rgba(204,117,76,0.9),rgba(223,127,64,0.82))] ${tone === 'landscape' ? 'aspect-[16/9]' : 'aspect-square'}`}
|
||||
>
|
||||
{src ? (
|
||||
<ResolvedAssetImage
|
||||
@@ -238,7 +238,7 @@ function PendingEntityCard({
|
||||
</div>
|
||||
<div className="platform-progress-track mt-3 h-2.5 overflow-hidden rounded-full">
|
||||
<div
|
||||
className="h-full bg-[linear-gradient(90deg,#ff4f8b_0%,#ff8a73_100%)] transition-[width] duration-300"
|
||||
className="h-full bg-[var(--platform-button-primary-solid)] transition-[width] duration-300"
|
||||
style={{ width: `${Math.max(6, Math.min(100, progress))}%` }}
|
||||
/>
|
||||
</div>
|
||||
@@ -313,7 +313,7 @@ function OpeningCgPreview({
|
||||
</div>
|
||||
{isGenerating ? (
|
||||
<div className="platform-progress-track h-2 overflow-hidden rounded-full">
|
||||
<div className="h-full w-2/3 animate-pulse bg-[linear-gradient(90deg,#ff4f8b_0%,#ff8a73_52%,#ffd2a6_100%)]" />
|
||||
<div className="h-full w-2/3 animate-pulse bg-[linear-gradient(90deg,#df7f40_0%,#cc754c_52%,#eaccb3_100%)]" />
|
||||
</div>
|
||||
) : null}
|
||||
{openingCg?.status === 'failed' && openingCg.errorMessage ? (
|
||||
@@ -437,7 +437,7 @@ function CatalogCard({
|
||||
<div
|
||||
className={`shrink-0 rounded-full border px-2.5 py-1 text-[10px] ${
|
||||
isSelected
|
||||
? 'border-rose-300/25 bg-rose-500/14 text-rose-50'
|
||||
? 'border-[var(--platform-button-danger-border)] bg-[var(--platform-button-danger-fill)] text-[var(--platform-button-danger-text)]'
|
||||
: 'platform-subpanel text-[var(--platform-text-soft)]'
|
||||
}`}
|
||||
>
|
||||
@@ -453,7 +453,7 @@ function CatalogCard({
|
||||
onClick={disabled ? undefined : onClick}
|
||||
aria-disabled={disabled}
|
||||
className={`w-full rounded-[1.3rem] border p-2.5 text-left transition-colors xl:p-3 ${
|
||||
isSelected ? 'border-rose-300/35 bg-rose-500/10' : 'platform-subpanel'
|
||||
isSelected ? 'border-[var(--platform-button-danger-border)] bg-[var(--platform-button-danger-fill)]' : 'platform-subpanel'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-3 xl:gap-3.5">
|
||||
@@ -491,7 +491,7 @@ function CatalogCard({
|
||||
onClick={disabled ? undefined : onClick}
|
||||
aria-disabled={disabled}
|
||||
className={`w-full rounded-[1.4rem] border p-3 text-left transition-colors ${
|
||||
isSelected ? 'border-rose-300/35 bg-rose-500/10' : 'platform-subpanel'
|
||||
isSelected ? 'border-[var(--platform-button-danger-border)] bg-[var(--platform-button-danger-fill)]' : 'platform-subpanel'
|
||||
}`}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
@@ -899,7 +899,7 @@ export function CustomWorldEntityCatalog({
|
||||
ref={scrollContainerRef}
|
||||
className="h-full min-h-0 space-y-3 overflow-y-auto overscroll-contain pr-1 scrollbar-hide xl:space-y-4 xl:pr-2 2xl:space-y-5 2xl:pr-3"
|
||||
>
|
||||
<div className="px-1 pb-1 text-center xl:flex xl:items-end xl:justify-between xl:gap-6 xl:rounded-[2rem] xl:border xl:border-[var(--platform-subpanel-border)] xl:bg-white/55 xl:px-6 xl:py-3 xl:text-left xl:shadow-[0_18px_70px_rgba(255,79,139,0.08)] xl:backdrop-blur-sm 2xl:px-7">
|
||||
<div className="px-1 pb-1 text-center xl:flex xl:items-end xl:justify-between xl:gap-6 xl:rounded-[2rem] xl:border xl:border-[var(--platform-subpanel-border)] xl:bg-white/55 xl:px-6 xl:py-3 xl:text-left xl:shadow-[0_18px_70px_rgba(112,57,30,0.08)] xl:backdrop-blur-sm 2xl:px-7">
|
||||
<div className="text-[11px] font-bold tracking-[0.28em] text-zinc-500">
|
||||
世界档案
|
||||
</div>
|
||||
@@ -913,7 +913,7 @@ export function CustomWorldEntityCatalog({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="platform-sticky-fade sticky top-0 z-10 -mx-1 space-y-3 px-1 pb-3 pt-1 backdrop-blur-sm xl:rounded-[1.75rem] xl:border xl:border-[var(--platform-subpanel-border)] xl:bg-white/70 xl:px-4 xl:py-3 xl:shadow-[0_16px_48px_rgba(255,79,139,0.08)]">
|
||||
<div className="platform-sticky-fade sticky top-0 z-10 -mx-1 space-y-3 px-1 pb-3 pt-1 backdrop-blur-sm xl:rounded-[1.75rem] xl:border xl:border-[var(--platform-subpanel-border)] xl:bg-white/70 xl:px-4 xl:py-3 xl:shadow-[0_16px_48px_rgba(112,57,30,0.08)]">
|
||||
<div className="flex gap-2 overflow-x-auto pb-1 scrollbar-hide xl:pb-0">
|
||||
{RESULT_TABS.map((tab) => (
|
||||
<div key={tab.id}>
|
||||
|
||||
@@ -225,7 +225,7 @@ export function CustomWorldGenerationView({
|
||||
|
||||
<div className="platform-progress-track mt-4 h-4 overflow-hidden rounded-full xl:mt-5 xl:h-5">
|
||||
<motion.div
|
||||
className="h-full bg-[linear-gradient(90deg,#ff4f8b_0%,#ff8a73_52%,#ffd2a6_100%)]"
|
||||
className="h-full bg-[linear-gradient(90deg,#df7f40_0%,#cc754c_52%,#eaccb3_100%)]"
|
||||
animate={{ width: `${progressValue}%` }}
|
||||
transition={{ duration: 0.35, ease: 'easeOut' }}
|
||||
/>
|
||||
@@ -283,9 +283,9 @@ export function CustomWorldGenerationView({
|
||||
)}
|
||||
className={`rounded-2xl border px-4 py-3 transition-colors ${
|
||||
step.status === 'completed'
|
||||
? 'border-emerald-400/16 bg-emerald-500/8'
|
||||
? 'border-[var(--platform-success-border)] bg-[var(--platform-success-bg)]'
|
||||
: step.status === 'active'
|
||||
? 'border-sky-300/22 bg-sky-500/10'
|
||||
? 'border-[var(--platform-cool-border)] bg-[var(--platform-cool-bg)]'
|
||||
: 'platform-subpanel'
|
||||
} custom-world-generation-step`}
|
||||
initial={
|
||||
@@ -317,9 +317,9 @@ export function CustomWorldGenerationView({
|
||||
<motion.div
|
||||
className={`h-full rounded-full ${
|
||||
step.status === 'completed'
|
||||
? 'bg-emerald-300'
|
||||
? 'bg-[var(--platform-success-text)]'
|
||||
: step.status === 'active'
|
||||
? 'bg-[linear-gradient(90deg,#7dd3fc_0%,#fcd34d_100%)]'
|
||||
? 'bg-[linear-gradient(90deg,#df7f40_0%,#cc754c_56%,#eaccb3_100%)]'
|
||||
: 'bg-white/18'
|
||||
}`}
|
||||
animate={{ width: `${stepProgress}%` }}
|
||||
@@ -335,7 +335,7 @@ export function CustomWorldGenerationView({
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<div className="mt-4 rounded-2xl border border-rose-400/18 bg-rose-500/10 px-4 py-3 text-sm leading-6 text-rose-100">
|
||||
<div className="mt-4 rounded-2xl border border-[var(--platform-button-danger-border)] bg-[var(--platform-button-danger-fill)] px-4 py-3 text-sm leading-6 text-[var(--platform-button-danger-text)]">
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -364,7 +364,7 @@ export function CustomWorldGenerationView({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onInterrupt}
|
||||
className="rounded-full border border-rose-300/18 bg-rose-500/10 px-4 py-2 text-sm text-rose-100 transition-colors hover:text-white"
|
||||
className="rounded-full border border-[var(--platform-button-danger-border)] bg-[var(--platform-button-danger-fill)] px-4 py-2 text-sm text-[var(--platform-button-danger-text)] transition-colors hover:text-[var(--platform-text-strong)]"
|
||||
>
|
||||
{interruptLabel}
|
||||
</button>
|
||||
|
||||
@@ -260,7 +260,7 @@ function ThemeOptionCard({
|
||||
onClick={onClick}
|
||||
className={`platform-subpanel w-full rounded-[1.5rem] p-4 text-left transition ${
|
||||
active
|
||||
? 'border-[var(--platform-surface-hover-border)] shadow-[0_18px_44px_rgba(255,91,132,0.14)]'
|
||||
? 'border-[var(--platform-surface-hover-border)] shadow-[0_18px_44px_rgba(112,57,30,0.14)]'
|
||||
: 'hover:border-[var(--platform-surface-hover-border)]'
|
||||
}`}
|
||||
>
|
||||
@@ -534,8 +534,8 @@ export function AccountModal({
|
||||
<ThemeOptionCard
|
||||
active={platformTheme === 'light'}
|
||||
title="亮色主题"
|
||||
detail="暖白底面板,粉橘强调。"
|
||||
previewClassName="bg-[radial-gradient(circle_at_top,rgba(255,255,255,0.28),transparent_30%),linear-gradient(135deg,#fff8fb_0%,#ffe9ee_52%,#ffd8cb_100%)] border border-white/70"
|
||||
detail="暖白底面板,陶土橙强调。"
|
||||
previewClassName="bg-[radial-gradient(circle_at_top,rgba(255,255,255,0.32),transparent_30%),linear-gradient(135deg,#fffdf9_0%,#f4e5d7_52%,#eaccb3_100%)] border border-white/70"
|
||||
onClick={() => onPlatformThemeChange('light')}
|
||||
/>
|
||||
<ThemeOptionCard
|
||||
|
||||
@@ -60,7 +60,7 @@ export function BindPhoneScreen({
|
||||
<div className={`platform-theme platform-theme--${platformTheme} min-h-screen bg-[var(--platform-body-fill)] px-4 py-6 text-[var(--platform-text-strong)] sm:py-8`}>
|
||||
<div className="mx-auto flex min-h-[calc(100vh-3rem)] w-full max-w-5xl items-center justify-center sm:min-h-[calc(100vh-4rem)]">
|
||||
<div className="platform-auth-card grid w-full max-w-4xl overflow-hidden rounded-[28px] md:grid-cols-[1.05fr_0.95fr]">
|
||||
<div className="border-b border-[var(--platform-subpanel-border)] bg-[linear-gradient(135deg,rgba(255,79,139,0.18),rgba(255,155,120,0.14))] px-6 py-8 md:border-b-0 md:border-r md:px-10 md:py-12">
|
||||
<div className="border-b border-[var(--platform-subpanel-border)] bg-[linear-gradient(135deg,rgba(204,117,76,0.18),rgba(240,203,169,0.16))] px-6 py-8 md:border-b-0 md:border-r md:px-10 md:py-12">
|
||||
<div className="selection-hero-brand selection-hero-brand--left">
|
||||
<div className="selection-hero-brand__title">陶泥儿</div>
|
||||
<div className="selection-hero-brand__subtitle">视觉叙事 RPG</div>
|
||||
|
||||
@@ -127,7 +127,7 @@ export function BarkBattleConfigEditor({
|
||||
value={title}
|
||||
disabled={isBusy}
|
||||
onChange={(event) => setTitle(event.target.value)}
|
||||
className="h-11 w-full rounded-[1.05rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 text-base font-semibold text-[var(--platform-text-strong)] outline-none transition focus:border-rose-200 focus:bg-white focus:ring-2 focus:ring-rose-100"
|
||||
className="h-11 w-full rounded-[1.05rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 text-base font-semibold text-[var(--platform-text-strong)] outline-none transition focus:border-[var(--platform-surface-hover-border)] focus:bg-white focus:ring-2 focus:ring-[var(--platform-warm-border)]"
|
||||
maxLength={40}
|
||||
aria-label="作品标题"
|
||||
/>
|
||||
@@ -141,7 +141,7 @@ export function BarkBattleConfigEditor({
|
||||
value={description}
|
||||
disabled={isBusy}
|
||||
onChange={(event) => setDescription(event.target.value)}
|
||||
className="h-[5.5rem] min-h-[5.5rem] w-full resize-none rounded-[1.05rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 py-3 text-base leading-6 text-[var(--platform-text-strong)] outline-none transition focus:border-rose-200 focus:bg-white focus:ring-2 focus:ring-rose-100"
|
||||
className="h-[5.5rem] min-h-[5.5rem] w-full resize-none rounded-[1.05rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 py-3 text-base leading-6 text-[var(--platform-text-strong)] outline-none transition focus:border-[var(--platform-surface-hover-border)] focus:bg-white focus:ring-2 focus:ring-[var(--platform-warm-border)]"
|
||||
maxLength={160}
|
||||
placeholder=""
|
||||
aria-label="简介"
|
||||
@@ -157,7 +157,7 @@ export function BarkBattleConfigEditor({
|
||||
value={themePreset}
|
||||
disabled={isBusy}
|
||||
onChange={(event) => setThemePreset(event.target.value)}
|
||||
className="h-11 w-full rounded-[1.05rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 text-sm font-black text-[var(--platform-text-strong)] outline-none transition focus:border-rose-200 focus:bg-white focus:ring-2 focus:ring-rose-100"
|
||||
className="h-11 w-full rounded-[1.05rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 text-sm font-black text-[var(--platform-text-strong)] outline-none transition focus:border-[var(--platform-surface-hover-border)] focus:bg-white focus:ring-2 focus:ring-[var(--platform-warm-border)]"
|
||||
aria-label="主题背景"
|
||||
>
|
||||
{THEME_OPTIONS.map((option) => (
|
||||
@@ -180,7 +180,7 @@ export function BarkBattleConfigEditor({
|
||||
event.target.value as BarkBattleDifficultyPreset,
|
||||
)
|
||||
}
|
||||
className="h-11 w-full rounded-[1.05rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 text-sm font-black text-[var(--platform-text-strong)] outline-none transition focus:border-rose-200 focus:bg-white focus:ring-2 focus:ring-rose-100"
|
||||
className="h-11 w-full rounded-[1.05rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 text-sm font-black text-[var(--platform-text-strong)] outline-none transition focus:border-[var(--platform-surface-hover-border)] focus:bg-white focus:ring-2 focus:ring-[var(--platform-warm-border)]"
|
||||
aria-label="难度预设"
|
||||
>
|
||||
{DIFFICULTY_OPTIONS.map((option) => (
|
||||
@@ -201,7 +201,7 @@ export function BarkBattleConfigEditor({
|
||||
onChange={(event) =>
|
||||
setPlayerDogSkinPreset(event.target.value)
|
||||
}
|
||||
className="h-11 w-full rounded-[1.05rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 text-sm font-black text-[var(--platform-text-strong)] outline-none transition focus:border-rose-200 focus:bg-white focus:ring-2 focus:ring-rose-100"
|
||||
className="h-11 w-full rounded-[1.05rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 text-sm font-black text-[var(--platform-text-strong)] outline-none transition focus:border-[var(--platform-surface-hover-border)] focus:bg-white focus:ring-2 focus:ring-[var(--platform-warm-border)]"
|
||||
aria-label="玩家狗狗"
|
||||
>
|
||||
{DOG_SKIN_OPTIONS.map((option) => (
|
||||
@@ -222,7 +222,7 @@ export function BarkBattleConfigEditor({
|
||||
onChange={(event) =>
|
||||
setOpponentDogSkinPreset(event.target.value)
|
||||
}
|
||||
className="h-11 w-full rounded-[1.05rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 text-sm font-black text-[var(--platform-text-strong)] outline-none transition focus:border-rose-200 focus:bg-white focus:ring-2 focus:ring-rose-100"
|
||||
className="h-11 w-full rounded-[1.05rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 text-sm font-black text-[var(--platform-text-strong)] outline-none transition focus:border-[var(--platform-surface-hover-border)] focus:bg-white focus:ring-2 focus:ring-[var(--platform-warm-border)]"
|
||||
aria-label="对手狗狗"
|
||||
>
|
||||
{DOG_SKIN_OPTIONS.map((option) => (
|
||||
|
||||
@@ -14,6 +14,7 @@ export type CreativeImageInputReferenceImage = {
|
||||
id: string;
|
||||
label: string;
|
||||
imageSrc: string;
|
||||
assetObjectId?: string | null;
|
||||
};
|
||||
|
||||
export type CreativeImageInputPanelLabels = {
|
||||
@@ -207,7 +208,7 @@ export function CreativeImageInputPanel({
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={onHistoryClick}
|
||||
className={`absolute right-3 top-3 z-10 inline-flex items-center gap-1.5 rounded-full border border-white/80 bg-white/94 px-3 py-2 text-[11px] font-black text-[var(--platform-text-strong)] shadow-sm backdrop-blur transition hover:text-[#ff4056] ${
|
||||
className={`absolute right-3 top-3 z-10 inline-flex items-center gap-1.5 rounded-full border border-white/80 bg-white/94 px-3 py-2 text-[11px] font-black text-[var(--platform-text-strong)] shadow-sm backdrop-blur transition hover:text-[var(--platform-accent)] ${
|
||||
disabled ? 'cursor-not-allowed opacity-55' : ''
|
||||
}`}
|
||||
aria-label={labels.history ?? '选择历史图片'}
|
||||
@@ -232,7 +233,7 @@ export function CreativeImageInputPanel({
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`relative h-5 w-9 rounded-full transition ${
|
||||
aiRedraw ? 'bg-[#ff4056]' : 'bg-zinc-300'
|
||||
aiRedraw ? 'bg-[var(--platform-accent)]' : 'bg-zinc-300'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
@@ -248,7 +249,7 @@ export function CreativeImageInputPanel({
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => setIsRemoveImageConfirmOpen(true)}
|
||||
className="absolute left-3 top-3 z-10 inline-flex h-10 w-10 items-center justify-center rounded-full border border-white/80 bg-white/94 text-[var(--platform-text-strong)] shadow-sm backdrop-blur transition hover:text-[#ff4056] disabled:cursor-not-allowed disabled:opacity-55"
|
||||
className="absolute left-3 top-3 z-10 inline-flex h-10 w-10 items-center justify-center rounded-full border border-white/80 bg-white/94 text-[var(--platform-text-strong)] shadow-sm backdrop-blur transition hover:text-[var(--platform-accent)] disabled:cursor-not-allowed disabled:opacity-55"
|
||||
aria-label={labels.removeImage}
|
||||
title={labels.removeImage}
|
||||
>
|
||||
@@ -257,7 +258,7 @@ export function CreativeImageInputPanel({
|
||||
) : canEditMainImage && !uploadedImageSrc ? (
|
||||
<label
|
||||
htmlFor={mainImageInputId}
|
||||
className={`absolute bottom-9 left-1/2 z-10 -translate-x-1/2 whitespace-nowrap text-center text-sm font-black text-[var(--platform-text-strong)] drop-shadow-[0_1px_0_rgba(255,255,255,0.82)] transition hover:text-[#ff4056] sm:bottom-10 ${
|
||||
className={`absolute bottom-9 left-1/2 z-10 -translate-x-1/2 whitespace-nowrap text-center text-sm font-black text-[var(--platform-text-strong)] drop-shadow-[0_1px_0_rgba(255,255,255,0.82)] transition hover:text-[var(--platform-accent)] sm:bottom-10 ${
|
||||
disabled
|
||||
? 'cursor-not-allowed opacity-55'
|
||||
: 'cursor-pointer'
|
||||
@@ -293,7 +294,7 @@ export function CreativeImageInputPanel({
|
||||
{imageModelPicker}
|
||||
{!uploadedImageSrc && onPromptReferenceFilesSelect ? (
|
||||
<label
|
||||
className={`absolute bottom-3 right-3 z-10 inline-flex h-8 w-8 items-center justify-center rounded-full border border-[var(--platform-subpanel-border)] bg-white/96 text-[var(--platform-text-strong)] shadow-sm transition hover:bg-[var(--platform-subpanel-fill)] hover:text-[#ff4056] ${
|
||||
className={`absolute bottom-3 right-3 z-10 inline-flex h-8 w-8 items-center justify-center rounded-full border border-[var(--platform-subpanel-border)] bg-white/96 text-[var(--platform-text-strong)] shadow-sm transition hover:bg-[var(--platform-subpanel-fill)] hover:text-[var(--platform-accent)] ${
|
||||
promptReferenceUploadDisabled
|
||||
? 'cursor-not-allowed opacity-55'
|
||||
: 'cursor-pointer'
|
||||
@@ -347,7 +348,7 @@ export function CreativeImageInputPanel({
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => onPromptReferenceRemove(reference.id)}
|
||||
className="absolute right-0.5 top-0.5 inline-flex h-5 w-5 items-center justify-center rounded-full bg-white/94 text-[var(--platform-text-strong)] shadow-sm transition hover:text-[#ff4056] disabled:opacity-55"
|
||||
className="absolute right-0.5 top-0.5 inline-flex h-5 w-5 items-center justify-center rounded-full bg-white/94 text-[var(--platform-text-strong)] shadow-sm transition hover:text-[var(--platform-accent)] disabled:opacity-55"
|
||||
aria-label={`移除参考图 ${reference.label}`}
|
||||
title="移除参考图"
|
||||
>
|
||||
|
||||
@@ -377,7 +377,7 @@ export function SquareImageCropModal({
|
||||
className="h-full w-full object-fill"
|
||||
/>
|
||||
<div
|
||||
className={`absolute border-2 border-sky-200/95 shadow-[0_0_0_9999px_rgba(0,0,0,0.38)] outline outline-1 outline-black/35 ${
|
||||
className={`absolute border-2 border-[var(--platform-accent)] shadow-[0_0_0_9999px_rgba(61,31,16,0.34)] outline outline-1 outline-[rgba(74,34,15,0.35)] ${
|
||||
activeDragHandle === 'move' ? 'cursor-grabbing' : 'cursor-grab'
|
||||
}`}
|
||||
style={previewStyle}
|
||||
@@ -410,13 +410,13 @@ export function SquareImageCropModal({
|
||||
onPointerUp={stopCropDrag}
|
||||
onPointerCancel={stopCropDrag}
|
||||
>
|
||||
<span className="absolute left-1/2 top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full border border-white bg-sky-300 shadow-[0_0_0_3px_rgba(2,132,199,0.32)]" />
|
||||
<span className="absolute left-1/2 top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full border border-white bg-[var(--platform-accent)] shadow-[0_0_0_3px_rgba(204,117,76,0.32)]" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{error ? (
|
||||
<div className="mt-4 rounded-2xl border border-rose-400/25 bg-rose-500/10 px-3 py-2 text-sm text-rose-600">
|
||||
<div className="mt-4 rounded-2xl border border-[var(--platform-button-danger-border)] bg-[var(--platform-button-danger-fill)] px-3 py-2 text-sm text-[var(--platform-button-danger-text)]">
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -136,7 +136,7 @@ function CreationAgentOperationBanner({
|
||||
: 'platform-banner--success';
|
||||
const progress = normalizeCreationAgentProgress(visibleOperation.progress);
|
||||
const progressFillStyle = isFailed
|
||||
? { background: 'linear-gradient(90deg, #fb7185 0%, #f43f5e 100%)' }
|
||||
? { background: 'linear-gradient(90deg, #c7653d 0%, #a6402f 100%)' }
|
||||
: isRunning
|
||||
? { background: 'var(--platform-button-primary-fill)' }
|
||||
: { background: 'linear-gradient(90deg, #86efac 0%, #34d399 100%)' };
|
||||
|
||||
@@ -171,7 +171,7 @@ export function CreativeAgentWorkspace({
|
||||
{targetBinding ? (
|
||||
<section className="platform-subpanel flex flex-col gap-3 rounded-[1.35rem] p-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="inline-flex h-10 w-10 items-center justify-center rounded-full bg-emerald-100 text-emerald-700">
|
||||
<span className="inline-flex h-10 w-10 items-center justify-center rounded-full bg-[var(--platform-success-bg)] text-[var(--platform-success-text)]">
|
||||
<CheckCircle2 className="h-5 w-5" />
|
||||
</span>
|
||||
<div>
|
||||
@@ -203,7 +203,7 @@ export function CreativeAgentWorkspace({
|
||||
key={message.id}
|
||||
className={`max-w-[86%] rounded-[1.15rem] px-4 py-3 text-sm leading-6 ${
|
||||
message.role === 'user'
|
||||
? 'ml-auto bg-[var(--platform-button-primary-fill)] text-[var(--platform-button-primary-text)]'
|
||||
? 'ml-auto bg-[var(--platform-button-primary-solid)] text-[var(--platform-button-primary-text)]'
|
||||
: 'platform-subpanel text-[var(--platform-text-base)]'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -60,13 +60,13 @@ const difficultyToneByValue: Record<
|
||||
{ accent: string; soft: string; label: string }
|
||||
> = {
|
||||
advanced: {
|
||||
accent: '#f97316',
|
||||
accent: '#df7f40',
|
||||
soft: 'rgba(249, 115, 22, 0.16)',
|
||||
label: '进阶',
|
||||
},
|
||||
challenge: {
|
||||
accent: '#e11d48',
|
||||
soft: 'rgba(225, 29, 72, 0.16)',
|
||||
accent: '#b64a35',
|
||||
soft: 'rgba(182, 98, 63, 0.16)',
|
||||
label: '挑战',
|
||||
},
|
||||
easy: {
|
||||
|
||||
@@ -290,9 +290,9 @@ export function JumpHopRuntimeShell({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="platform-remap-surface jump-hop-runtime relative flex h-full min-h-0 w-full flex-col overflow-hidden bg-[#eef8ff] text-slate-950">
|
||||
<div className="platform-remap-surface jump-hop-runtime relative flex h-full min-h-0 w-full flex-col overflow-hidden bg-[#fffdf9] text-slate-950">
|
||||
<div className="jump-hop-runtime__sky" />
|
||||
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_18%,rgba(255,255,255,0.82),transparent_30%),linear-gradient(180deg,rgba(255,255,255,0.18),rgba(148,210,255,0.2))]" />
|
||||
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_18%,rgba(255,255,255,0.82),transparent_30%),linear-gradient(180deg,rgba(255,255,255,0.18),rgba(234,204,179,0.24))]" />
|
||||
|
||||
<header className="relative z-20 flex items-center justify-between gap-2 px-3 pb-2 pt-[max(0.75rem,env(safe-area-inset-top))] sm:px-4">
|
||||
<button
|
||||
@@ -456,7 +456,9 @@ export function JumpHopRuntimeShell({
|
||||
<footer className="relative z-20 mt-3 flex items-center justify-between gap-3">
|
||||
<div className="min-w-0 text-sm font-bold text-slate-700">
|
||||
{error ? (
|
||||
<span className="text-rose-600">{error}</span>
|
||||
<span className="text-[var(--platform-button-danger-text)]">
|
||||
{error}
|
||||
</span>
|
||||
) : (
|
||||
<span>{getStatusLabel(activeRun?.status)}</span>
|
||||
)}
|
||||
@@ -467,7 +469,7 @@ export function JumpHopRuntimeShell({
|
||||
onPointerDown={beginCharge}
|
||||
onPointerUp={() => void finishCharge()}
|
||||
onPointerCancel={cancelCharge}
|
||||
className="platform-button platform-button--primary min-h-12 shrink-0 gap-2 rounded-full px-5 py-3 text-sm shadow-[0_12px_28px_rgba(14,165,233,0.28)]"
|
||||
className="platform-button platform-button--primary min-h-12 shrink-0 gap-2 rounded-full px-5 py-3 text-sm shadow-[0_12px_28px_rgba(182,98,63,0.22)]"
|
||||
>
|
||||
{isBusy ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
@@ -485,8 +487,8 @@ export function JumpHopRuntimeShell({
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(circle at 18% 18%, rgba(253, 230, 138, 0.36), transparent 24%),
|
||||
radial-gradient(circle at 82% 22%, rgba(125, 211, 252, 0.34), transparent 28%),
|
||||
linear-gradient(180deg, #f7fdff 0%, #e8f6ff 52%, #d9eefc 100%);
|
||||
radial-gradient(circle at 82% 22%, rgba(226, 171, 134, 0.34), transparent 28%),
|
||||
linear-gradient(180deg, #fffdf9 0%, #f8efe7 52%, #f4e5d7 100%);
|
||||
}
|
||||
|
||||
.jump-hop-runtime__stage {
|
||||
@@ -645,7 +647,7 @@ export function JumpHopRuntimeShell({
|
||||
border-radius: 999px 999px 0.9rem 0.9rem;
|
||||
background:
|
||||
radial-gradient(circle at 50% 22%, #fff 0 17%, transparent 18%),
|
||||
linear-gradient(180deg, #fb7185 0%, #f97316 100%);
|
||||
linear-gradient(180deg, #c7653d 0%, #df7f40 100%);
|
||||
box-shadow: inset 0 0 0 2px rgba(255,255,255,0.72), 0 12px 18px rgba(190, 80, 40, 0.24);
|
||||
}
|
||||
|
||||
@@ -683,7 +685,7 @@ export function JumpHopRuntimeShell({
|
||||
height: 100%;
|
||||
transform-origin: left center;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(90deg, #38bdf8, #facc15, #fb7185);
|
||||
background: linear-gradient(90deg, #6e8d42, #f0cba9, #c7653d);
|
||||
}
|
||||
|
||||
@keyframes jump-hop-feedback {
|
||||
|
||||
@@ -330,7 +330,7 @@ export function Match3DAgentWorkspace({
|
||||
<h1 className="m-0 text-3xl font-black leading-none tracking-normal text-[var(--platform-text-strong)] sm:text-7xl">
|
||||
{title}
|
||||
</h1>
|
||||
<span className="rounded-full border border-emerald-200 bg-emerald-50 px-3 py-1 text-[11px] font-black text-emerald-700">
|
||||
<span className="rounded-full border border-[var(--platform-warm-border)] bg-[var(--platform-warm-bg)] px-3 py-1 text-[11px] font-black text-[var(--platform-warm-text)]">
|
||||
BETA
|
||||
</span>
|
||||
</div>
|
||||
@@ -356,7 +356,7 @@ export function Match3DAgentWorkspace({
|
||||
themeText: event.target.value,
|
||||
}))
|
||||
}
|
||||
className="h-full min-h-[7.75rem] w-full resize-none rounded-[1.15rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 py-3 text-base leading-6 text-[var(--platform-text-strong)] outline-none transition focus:border-rose-200 focus:bg-white focus:ring-2 focus:ring-rose-100 sm:min-h-[9rem] lg:min-h-[14rem]"
|
||||
className="h-full min-h-[7.75rem] w-full resize-none rounded-[1.15rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 py-3 text-base leading-6 text-[var(--platform-text-strong)] outline-none transition focus:border-[var(--platform-surface-hover-border)] focus:bg-white focus:ring-2 focus:ring-[var(--platform-warm-border)] sm:min-h-[9rem] lg:min-h-[14rem]"
|
||||
aria-label="想做一个什么题材的抓大鹅?"
|
||||
/>
|
||||
</label>
|
||||
@@ -388,10 +388,10 @@ export function Match3DAgentWorkspace({
|
||||
assetStyleId: option.id,
|
||||
}));
|
||||
}}
|
||||
className={`group relative h-[4.9rem] w-[5.85rem] shrink-0 snap-start overflow-hidden rounded-[0.95rem] border p-0 text-left transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-200 sm:h-[5.45rem] sm:w-[6.4rem] ${
|
||||
className={`group relative h-[4.9rem] w-[5.85rem] shrink-0 snap-start overflow-hidden rounded-[0.95rem] border p-0 text-left transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--platform-warm-border)] sm:h-[5.45rem] sm:w-[6.4rem] ${
|
||||
selected
|
||||
? 'border-rose-300 bg-white shadow-[0_8px_18px_rgba(190,18,60,0.10)] ring-2 ring-rose-100'
|
||||
: 'border-[var(--platform-subpanel-border)] bg-white/70 hover:border-rose-200 hover:bg-white/95'
|
||||
? 'border-[var(--platform-surface-hover-border)] bg-white shadow-[0_8px_18px_rgba(112,57,30,0.10)] ring-2 ring-[var(--platform-warm-border)]'
|
||||
: 'border-[var(--platform-subpanel-border)] bg-white/70 hover:border-[var(--platform-surface-hover-border)] hover:bg-white/95'
|
||||
} ${isBusy ? 'cursor-not-allowed opacity-55' : ''}`}
|
||||
aria-pressed={selected}
|
||||
aria-label={option.label}
|
||||
@@ -408,11 +408,11 @@ export function Match3DAgentWorkspace({
|
||||
)}
|
||||
<span className="absolute inset-0 bg-[linear-gradient(180deg,rgba(255,255,255,0.02)_0%,rgba(255,255,255,0.18)_44%,rgba(255,255,255,0.82)_100%)]" />
|
||||
{selected ? (
|
||||
<span className="absolute right-1.5 top-1.5 h-2.5 w-2.5 rounded-full bg-rose-400 shadow-[0_0_0_3px_rgba(255,255,255,0.84)]" />
|
||||
<span className="absolute right-1.5 top-1.5 h-2.5 w-2.5 rounded-full bg-[var(--platform-accent)] shadow-[0_0_0_3px_rgba(255,255,255,0.84)]" />
|
||||
) : null}
|
||||
{isCustom ? (
|
||||
<span className="absolute inset-0 flex items-center justify-center text-rose-500">
|
||||
<span className="grid h-8 w-8 place-items-center rounded-full bg-white/82 shadow-[0_6px_18px_rgba(190,18,60,0.12)]">
|
||||
<span className="absolute inset-0 flex items-center justify-center text-[var(--platform-accent)]">
|
||||
<span className="grid h-8 w-8 place-items-center rounded-full bg-white/82 shadow-[0_6px_18px_rgba(112,57,30,0.12)]">
|
||||
<Plus className="h-5 w-5" />
|
||||
</span>
|
||||
</span>
|
||||
@@ -420,7 +420,7 @@ export function Match3DAgentWorkspace({
|
||||
<span
|
||||
className={`absolute inset-x-2 bottom-1.5 truncate rounded-full px-1.5 py-0.5 text-center text-[11px] font-black shadow-[0_3px_10px_rgba(15,23,42,0.10)] ${
|
||||
selected
|
||||
? 'bg-rose-50/95 text-rose-700'
|
||||
? 'bg-[var(--platform-warm-bg)] text-[var(--platform-warm-text)]'
|
||||
: 'bg-white/88 text-[var(--platform-text-strong)]'
|
||||
}`}
|
||||
>
|
||||
@@ -450,10 +450,10 @@ export function Match3DAgentWorkspace({
|
||||
difficultyOptionId: option.id,
|
||||
}))
|
||||
}
|
||||
className={`min-h-10 rounded-[0.85rem] border px-2 text-sm font-black transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-200 sm:min-h-11 ${
|
||||
className={`min-h-10 rounded-[0.85rem] border px-2 text-sm font-black transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--platform-warm-border)] sm:min-h-11 ${
|
||||
selected
|
||||
? 'border-[#ff7890] bg-[linear-gradient(180deg,#ff7890_0%,#ff4f6a_100%)] text-white shadow-[0_8px_18px_rgba(244,63,94,0.16)]'
|
||||
: 'border-[var(--platform-subpanel-border)] bg-white/76 text-[var(--platform-text-strong)] hover:border-rose-200 hover:bg-white'
|
||||
: 'border-[var(--platform-subpanel-border)] bg-white/76 text-[var(--platform-text-strong)] hover:border-[var(--platform-surface-hover-border)] hover:bg-white'
|
||||
} ${isBusy ? 'cursor-not-allowed opacity-55' : ''}`}
|
||||
aria-pressed={selected}
|
||||
>
|
||||
@@ -528,7 +528,7 @@ export function Match3DAgentWorkspace({
|
||||
setDraftCustomStylePrompt(event.target.value)
|
||||
}
|
||||
rows={4}
|
||||
className="mt-4 h-[7.5rem] w-full resize-none rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 py-3 text-base leading-6 text-[var(--platform-text-strong)] outline-none transition focus:border-rose-200 focus:bg-white focus:ring-2 focus:ring-rose-100"
|
||||
className="mt-4 h-[7.5rem] w-full resize-none rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 py-3 text-base leading-6 text-[var(--platform-text-strong)] outline-none transition focus:border-[var(--platform-surface-hover-border)] focus:bg-white focus:ring-2 focus:ring-[var(--platform-warm-border)]"
|
||||
aria-label="自定义2D素材风格描述"
|
||||
/>
|
||||
<div className="mt-5 grid grid-cols-2 gap-3">
|
||||
|
||||
@@ -1003,7 +1003,7 @@ function Match3DBatchGenerationProgress({
|
||||
</div>
|
||||
) : null}
|
||||
{generationState.error ? (
|
||||
<div className="mt-2 text-sm font-semibold text-rose-600">
|
||||
<div className="mt-2 text-sm font-semibold text-[var(--platform-button-danger-text)]">
|
||||
{generationState.error}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -1478,14 +1478,14 @@ function Match3DWorkInfoTab({
|
||||
{tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="inline-flex items-center gap-1.5 rounded-full border border-emerald-300/35 bg-emerald-100/68 px-3 py-1.5 text-xs font-semibold text-emerald-700"
|
||||
className="inline-flex items-center gap-1.5 rounded-full border border-[var(--platform-warm-border)] bg-[var(--platform-warm-bg)] px-3 py-1.5 text-xs font-semibold text-[var(--platform-warm-text)]"
|
||||
>
|
||||
{tag}
|
||||
<button
|
||||
type="button"
|
||||
disabled={isBusy}
|
||||
onClick={() => updateTags(tags.filter((item) => item !== tag))}
|
||||
className="rounded-full text-emerald-800/70 transition hover:text-emerald-950 disabled:opacity-45"
|
||||
className="rounded-full text-[var(--platform-warm-text)] opacity-70 transition hover:text-[var(--platform-text-strong)] hover:opacity-100 disabled:opacity-45"
|
||||
aria-label={`删除标签 ${tag}`}
|
||||
title="删除标签"
|
||||
>
|
||||
@@ -1691,7 +1691,7 @@ function Match3DCoverImageEditor({
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`relative h-5 w-9 rounded-full transition ${
|
||||
aiRedraw ? 'bg-[#ff4056]' : 'bg-zinc-300'
|
||||
aiRedraw ? 'bg-[var(--platform-accent)]' : 'bg-zinc-300'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
@@ -1705,7 +1705,7 @@ function Match3DCoverImageEditor({
|
||||
type="button"
|
||||
disabled={isGenerating}
|
||||
onClick={onUploadedImageRemove}
|
||||
className="absolute left-3 top-3 z-10 inline-flex h-10 w-10 items-center justify-center rounded-full border border-white/80 bg-white/94 text-[var(--platform-text-strong)] shadow-sm backdrop-blur transition hover:text-[#ff4056] disabled:cursor-not-allowed disabled:opacity-55"
|
||||
className="absolute left-3 top-3 z-10 inline-flex h-10 w-10 items-center justify-center rounded-full border border-white/80 bg-white/94 text-[var(--platform-text-strong)] shadow-sm backdrop-blur transition hover:text-[var(--platform-accent)] disabled:cursor-not-allowed disabled:opacity-55"
|
||||
aria-label="移除封面图"
|
||||
title="移除封面图"
|
||||
>
|
||||
@@ -1715,7 +1715,7 @@ function Match3DCoverImageEditor({
|
||||
) : (
|
||||
<label
|
||||
htmlFor="match3d-cover-upload-input"
|
||||
className={`absolute bottom-9 left-1/2 z-10 -translate-x-1/2 whitespace-nowrap text-center text-sm font-black text-[var(--platform-text-strong)] drop-shadow-[0_1px_0_rgba(255,255,255,0.82)] transition hover:text-[#ff4056] ${isGenerating ? 'cursor-not-allowed opacity-55' : 'cursor-pointer'}`}
|
||||
className={`absolute bottom-9 left-1/2 z-10 -translate-x-1/2 whitespace-nowrap text-center text-sm font-black text-[var(--platform-text-strong)] drop-shadow-[0_1px_0_rgba(255,255,255,0.82)] transition hover:text-[var(--platform-accent)] ${isGenerating ? 'cursor-not-allowed opacity-55' : 'cursor-pointer'}`}
|
||||
>
|
||||
上传图片/填写封面描述
|
||||
</label>
|
||||
@@ -1764,7 +1764,7 @@ function Match3DCoverImageEditor({
|
||||
{referenceImages.map((reference) => (
|
||||
<div
|
||||
key={reference.id}
|
||||
className="relative overflow-hidden rounded-[1rem] border border-emerald-300 bg-white/74"
|
||||
className="relative overflow-hidden rounded-[1rem] border border-[var(--platform-warm-border)] bg-white/74"
|
||||
>
|
||||
<div className="aspect-square overflow-hidden">
|
||||
<ResolvedAssetImage
|
||||
@@ -1781,7 +1781,7 @@ function Match3DCoverImageEditor({
|
||||
type="button"
|
||||
disabled={isGenerating}
|
||||
onClick={() => onReferenceRemove(reference.id)}
|
||||
className="absolute bottom-1.5 right-1.5 inline-flex h-6 w-6 items-center justify-center rounded-full bg-white/92 text-[var(--platform-text-strong)] shadow-sm transition hover:text-[#ff4056] disabled:opacity-55"
|
||||
className="absolute bottom-1.5 right-1.5 inline-flex h-6 w-6 items-center justify-center rounded-full bg-white/92 text-[var(--platform-text-strong)] shadow-sm transition hover:text-[var(--platform-accent)] disabled:opacity-55"
|
||||
aria-label={`移除参考图 ${reference.label}`}
|
||||
title="移除参考图"
|
||||
>
|
||||
@@ -1803,8 +1803,8 @@ function Match3DCoverImageEditor({
|
||||
referenceImages.some(
|
||||
(reference) => reference.imageSrc === asset.imageSrc,
|
||||
)
|
||||
? 'border-emerald-300 ring-2 ring-emerald-100'
|
||||
: 'border-[var(--platform-subpanel-border)] hover:border-emerald-200'
|
||||
? 'border-[var(--platform-warm-border)] ring-2 ring-[var(--platform-warm-bg)]'
|
||||
: 'border-[var(--platform-subpanel-border)] hover:border-[var(--platform-surface-hover-border)]'
|
||||
}`}
|
||||
aria-label={`引用${asset.label}`}
|
||||
>
|
||||
@@ -2058,9 +2058,9 @@ function Match3DConfigTab({
|
||||
<section className="platform-subpanel rounded-[1.35rem] p-4 sm:p-5">
|
||||
<div className="relative px-1 pb-1 pt-2">
|
||||
<div className="relative mx-[1.35rem] h-10">
|
||||
<div className="absolute left-0 right-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-white/75 shadow-[inset_0_0_0_1px_rgba(244,114,182,0.16)]" />
|
||||
<div className="absolute left-0 right-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-white/75 shadow-[inset_0_0_0_1px_rgba(204,117,76,0.16)]" />
|
||||
<div
|
||||
className="absolute left-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-[linear-gradient(90deg,#ff8aac_0%,#ff5f7e_54%,#ff9b88_100%)] transition-[width] duration-200"
|
||||
className="absolute left-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-[linear-gradient(90deg,#df7f40_0%,#c7653d_54%,#e2ab86_100%)] transition-[width] duration-200"
|
||||
style={{ width: `${trackProgress * 100}%` }}
|
||||
/>
|
||||
{MATCH3D_DIFFICULTY_OPTIONS.map((option, index) => {
|
||||
@@ -2069,10 +2069,10 @@ function Match3DConfigTab({
|
||||
<div
|
||||
key={option.id}
|
||||
aria-hidden="true"
|
||||
className={`absolute top-1/2 flex h-8 w-8 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-200 ${
|
||||
className={`absolute top-1/2 flex h-8 w-8 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--platform-warm-border)] ${
|
||||
selected
|
||||
? 'border-[#ff5f7e] bg-white shadow-[0_8px_18px_rgba(244,63,94,0.2)]'
|
||||
: 'border-rose-100 bg-white/90 hover:border-rose-200'
|
||||
? 'border-[var(--platform-surface-hover-border)] bg-white shadow-[0_8px_18px_rgba(112,57,30,0.16)]'
|
||||
: 'border-[var(--platform-subpanel-border)] bg-white/90 hover:border-[var(--platform-surface-hover-border)]'
|
||||
} ${isBusy ? 'cursor-not-allowed opacity-55' : ''}`}
|
||||
style={{
|
||||
left: `${(index / (MATCH3D_DIFFICULTY_OPTIONS.length - 1)) * 100}%`,
|
||||
@@ -2080,7 +2080,7 @@ function Match3DConfigTab({
|
||||
>
|
||||
<span
|
||||
className={`h-3.5 w-3.5 rounded-full ${
|
||||
selected ? 'bg-[var(--platform-accent)]' : 'bg-rose-100'
|
||||
selected ? 'bg-[var(--platform-accent)]' : 'bg-[var(--platform-warm-bg)]'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
@@ -2108,9 +2108,9 @@ function Match3DConfigTab({
|
||||
type="button"
|
||||
disabled={isBusy}
|
||||
onClick={() => applyDifficultyOption(option)}
|
||||
className={`rounded-[0.9rem] px-1.5 py-2 text-center transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-200 ${
|
||||
className={`rounded-[0.9rem] px-1.5 py-2 text-center transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--platform-warm-border)] ${
|
||||
selected
|
||||
? 'bg-[#fff1f5] text-[var(--platform-text-strong)] shadow-[inset_0_0_0_1px_rgba(244,63,94,0.18)]'
|
||||
? 'bg-[var(--platform-warm-bg)] text-[var(--platform-text-strong)] shadow-[inset_0_0_0_1px_rgba(204,117,76,0.18)]'
|
||||
: 'text-[var(--platform-text-base)] hover:bg-white/58'
|
||||
} ${isBusy ? 'cursor-not-allowed opacity-55' : ''}`}
|
||||
aria-pressed={selected}
|
||||
@@ -2124,7 +2124,7 @@ function Match3DConfigTab({
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 rounded-[1rem] border border-rose-100/80 bg-white/62 px-3 py-3">
|
||||
<div className="mt-3 rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/62 px-3 py-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-lg font-black text-[var(--platform-text-strong)]">
|
||||
@@ -2135,7 +2135,7 @@ function Match3DConfigTab({
|
||||
种
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-full bg-[var(--platform-accent)] px-3 py-1 text-xs font-black text-white shadow-[0_8px_18px_rgba(244,63,94,0.16)]">
|
||||
<div className="rounded-full bg-[var(--platform-accent)] px-3 py-1 text-xs font-black text-white shadow-[0_8px_18px_rgba(112,57,30,0.16)]">
|
||||
难度 {selectedOption.difficulty}
|
||||
</div>
|
||||
</div>
|
||||
@@ -2189,15 +2189,15 @@ function Match3DItemAssetListCard({
|
||||
<div
|
||||
className={`group min-w-0 rounded-[1.15rem] border p-2 text-left transition-colors ${
|
||||
active
|
||||
? 'border-rose-300/70 bg-rose-50/80'
|
||||
: 'border-[var(--platform-subpanel-border)] bg-white/76 hover:border-rose-200 hover:bg-white'
|
||||
? 'border-[var(--platform-surface-hover-border)] bg-[var(--platform-warm-bg)]'
|
||||
: 'border-[var(--platform-subpanel-border)] bg-white/76 hover:border-[var(--platform-surface-hover-border)] hover:bg-white'
|
||||
}`}
|
||||
>
|
||||
<div className="grid min-h-full grid-rows-[minmax(0,1fr)_auto] gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="grid min-h-0 gap-2 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-200"
|
||||
className="grid min-h-0 gap-2 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--platform-warm-border)]"
|
||||
aria-label={`打开${asset.name}物品素材`}
|
||||
>
|
||||
<div className="grid aspect-square min-h-0 place-items-center overflow-hidden rounded-[0.95rem] border border-[var(--platform-subpanel-border)] bg-white/82">
|
||||
@@ -2220,7 +2220,7 @@ function Match3DItemAssetListCard({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onDelete}
|
||||
className="platform-icon-button h-8 w-8 shrink-0 text-rose-500"
|
||||
className="platform-icon-button h-8 w-8 shrink-0 text-[var(--platform-button-danger-text)]"
|
||||
aria-label="删除物品素材"
|
||||
title="删除"
|
||||
>
|
||||
@@ -2307,9 +2307,9 @@ function Match3DItemAssetDetail({
|
||||
aria-pressed={isActive}
|
||||
className={`grid aspect-square place-items-center overflow-hidden rounded-[0.65rem] border bg-white/82 transition ${
|
||||
isActive
|
||||
? 'border-rose-300 ring-2 ring-rose-200'
|
||||
? 'border-[var(--platform-surface-hover-border)] ring-2 ring-[var(--platform-warm-border)]'
|
||||
: 'border-[var(--platform-subpanel-border)]'
|
||||
} ${!hasSource || busy ? 'cursor-not-allowed opacity-58' : 'hover:border-rose-200'}`}
|
||||
} ${!hasSource || busy ? 'cursor-not-allowed opacity-58' : 'hover:border-[var(--platform-surface-hover-border)]'}`}
|
||||
>
|
||||
{source ? (
|
||||
<ResolvedAssetImage
|
||||
|
||||
@@ -55,7 +55,7 @@ const MATCH3D_GEOMETRY_ASSETS: Record<string, Match3DGeometryAsset> = {
|
||||
};
|
||||
|
||||
const MATCH3D_UNKNOWN_GEOMETRY_ASSETS: Match3DGeometryAsset[] = [
|
||||
blockAsset('brick', '#e11d48', '#9f1239', 2, 2, 0.68),
|
||||
blockAsset('brick', '#b64a35', '#7a2f22', 2, 2, 0.68),
|
||||
blockAsset('tile', '#f59e0b', '#92400e', 3, 1, 0.28),
|
||||
blockAsset('slope', '#8b5cf6', '#5b21b6', 2, 1, 0.86),
|
||||
blockAsset('cylinder', '#10b981', '#065f46', 1, 1, 1.0),
|
||||
|
||||
@@ -1991,6 +1991,8 @@ function buildPuzzleCompileActionFromFormPayload(
|
||||
...(pictureDescription ? { pictureDescription } : {}),
|
||||
referenceImageSrc: payload?.referenceImageSrc || null,
|
||||
referenceImageSrcs: payload?.referenceImageSrcs ?? [],
|
||||
referenceImageAssetObjectId: payload?.referenceImageAssetObjectId ?? null,
|
||||
referenceImageAssetObjectIds: payload?.referenceImageAssetObjectIds ?? [],
|
||||
imageModel: payload?.imageModel ?? null,
|
||||
aiRedraw: payload?.aiRedraw ?? true,
|
||||
candidateCount: 1,
|
||||
@@ -2013,6 +2015,8 @@ function buildPuzzleFormPayloadFromSession(
|
||||
pictureDescription,
|
||||
referenceImageSrc: null,
|
||||
referenceImageSrcs: [],
|
||||
referenceImageAssetObjectId: null,
|
||||
referenceImageAssetObjectIds: [],
|
||||
imageModel: null,
|
||||
aiRedraw: true,
|
||||
};
|
||||
@@ -2043,6 +2047,8 @@ function buildPuzzleFormPayloadFromAction(
|
||||
? (payload.referenceImageSrc ?? null)
|
||||
: (payload.referenceImageSrc ?? null),
|
||||
referenceImageSrcs: payload.referenceImageSrcs ?? [],
|
||||
referenceImageAssetObjectId: payload.referenceImageAssetObjectId ?? null,
|
||||
referenceImageAssetObjectIds: payload.referenceImageAssetObjectIds ?? [],
|
||||
imageModel:
|
||||
payload.action === 'compile_puzzle_draft'
|
||||
? (payload.imageModel ?? null)
|
||||
@@ -5634,6 +5640,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
pictureDescription: payload.pictureDescription ?? '',
|
||||
referenceImageSrc: payload.referenceImageSrc ?? null,
|
||||
referenceImageSrcs: payload.referenceImageSrcs ?? [],
|
||||
referenceImageAssetObjectId:
|
||||
payload.referenceImageAssetObjectId ?? null,
|
||||
referenceImageAssetObjectIds:
|
||||
payload.referenceImageAssetObjectIds ?? [],
|
||||
imageModel: payload.imageModel ?? null,
|
||||
aiRedraw: payload.aiRedraw ?? true,
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ vi.mock('../ResolvedAssetImage', () => ({
|
||||
vi.mock('../../services/puzzle-works/puzzleAssetClient', () => ({
|
||||
puzzleAssetClient: {
|
||||
listHistoryAssets: vi.fn(),
|
||||
uploadReferenceImage: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -90,6 +91,20 @@ beforeEach(() => {
|
||||
if (!Element.prototype.scrollIntoView) {
|
||||
Element.prototype.scrollIntoView = () => {};
|
||||
}
|
||||
vi.mocked(puzzleAssetClient.uploadReferenceImage).mockImplementation(
|
||||
async ({ file }) => ({
|
||||
assetObjectId: `asset-reference-${file.name}`,
|
||||
assetKind: 'puzzle_cover_image',
|
||||
objectKey: `generated-puzzle-assets/reference/${file.name}`,
|
||||
imageSrc: `/generated-puzzle-assets/reference/${file.name}`,
|
||||
ownerUserId: 'user-1',
|
||||
ownerLabel: '账号 user-1',
|
||||
profileId: null,
|
||||
entityId: null,
|
||||
createdAt: '1713686400.000000Z',
|
||||
updatedAt: '1713686400.000000Z',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -190,6 +205,8 @@ test('puzzle workspace submits the work form instead of agent chat', () => {
|
||||
pictureDescription: '一只猫在雨夜灯牌下回头。',
|
||||
referenceImageSrc: null,
|
||||
referenceImageSrcs: [],
|
||||
referenceImageAssetObjectId: null,
|
||||
referenceImageAssetObjectIds: [],
|
||||
imageModel: 'gpt-image-2',
|
||||
aiRedraw: true,
|
||||
});
|
||||
@@ -325,8 +342,10 @@ test('puzzle workspace selects a history image from the upload card', async () =
|
||||
expect(onCreateFromForm).toHaveBeenCalledWith({
|
||||
seedText: '保留历史图里的主体,改成晴天花园。',
|
||||
pictureDescription: '保留历史图里的主体,改成晴天花园。',
|
||||
referenceImageSrc: '/generated-puzzle-assets/history/image.png',
|
||||
referenceImageSrc: null,
|
||||
referenceImageSrcs: [],
|
||||
referenceImageAssetObjectId: 'asset-history-1',
|
||||
referenceImageAssetObjectIds: [],
|
||||
imageModel: 'gpt-image-2',
|
||||
aiRedraw: true,
|
||||
});
|
||||
@@ -384,6 +403,8 @@ test('puzzle workspace falls back to compile action for restored sessions', () =
|
||||
promptText: '潮雾中的灯塔与断桥',
|
||||
referenceImageSrc: null,
|
||||
referenceImageSrcs: [],
|
||||
referenceImageAssetObjectId: null,
|
||||
referenceImageAssetObjectIds: [],
|
||||
imageModel: 'gpt-image-2',
|
||||
aiRedraw: true,
|
||||
candidateCount: 1,
|
||||
@@ -484,6 +505,8 @@ test('puzzle workspace restores form draft fields and autosaves edits', () => {
|
||||
pictureDescription: '旧街灯牌下的猫和发光雨伞。',
|
||||
referenceImageSrc: null,
|
||||
referenceImageSrcs: [],
|
||||
referenceImageAssetObjectId: null,
|
||||
referenceImageAssetObjectIds: [],
|
||||
imageModel: 'gpt-image-2',
|
||||
aiRedraw: true,
|
||||
});
|
||||
@@ -528,8 +551,10 @@ test('puzzle workspace hides prompt and cost when AI redraw is off', async () =>
|
||||
expect(onCreateFromForm).toHaveBeenCalledWith({
|
||||
seedText: 'first-level.png',
|
||||
pictureDescription: 'first-level.png',
|
||||
referenceImageSrc: uploadedDataUrl,
|
||||
referenceImageSrc: '/generated-puzzle-assets/reference/first-level.png',
|
||||
referenceImageSrcs: [],
|
||||
referenceImageAssetObjectId: 'asset-reference-first-level.png',
|
||||
referenceImageAssetObjectIds: [],
|
||||
imageModel: 'gpt-image-2',
|
||||
aiRedraw: false,
|
||||
});
|
||||
@@ -584,6 +609,8 @@ test('puzzle workspace submits history image when AI redraw is off', async () =>
|
||||
pictureDescription: '历史素材 · image.png',
|
||||
referenceImageSrc: '/generated-puzzle-assets/history/image.png',
|
||||
referenceImageSrcs: [],
|
||||
referenceImageAssetObjectId: 'asset-history-1',
|
||||
referenceImageAssetObjectIds: [],
|
||||
imageModel: 'gpt-image-2',
|
||||
aiRedraw: false,
|
||||
});
|
||||
@@ -593,6 +620,18 @@ test('puzzle workspace submits uploaded reference image when AI redraw is on', a
|
||||
const onCreateFromForm = vi.fn();
|
||||
const uploadedDataUrl = 'data:image/png;base64,uploaded-square';
|
||||
stubReferenceImageUpload(uploadedDataUrl);
|
||||
vi.mocked(puzzleAssetClient.uploadReferenceImage).mockResolvedValue({
|
||||
assetObjectId: 'asset-reference-main-1',
|
||||
assetKind: 'puzzle_cover_image',
|
||||
objectKey: 'generated-puzzle-assets/reference/main-1.png',
|
||||
imageSrc: '/generated-puzzle-assets/reference/main-1.png',
|
||||
ownerUserId: 'user-1',
|
||||
ownerLabel: '账号 user-1',
|
||||
profileId: null,
|
||||
entityId: null,
|
||||
createdAt: '1713686400.000000Z',
|
||||
updatedAt: '1713686400.000000Z',
|
||||
});
|
||||
|
||||
render(
|
||||
<PuzzleAgentWorkspace
|
||||
@@ -612,6 +651,9 @@ test('puzzle workspace submits uploaded reference image when AI redraw is on', a
|
||||
await waitFor(() => {
|
||||
expect(screen.getByAltText('拼图图片')).toBeTruthy();
|
||||
});
|
||||
expect(puzzleAssetClient.uploadReferenceImage).toHaveBeenCalledWith({
|
||||
file: expect.any(File),
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText('画面AI重绘要求(提示词)'), {
|
||||
target: { value: '保留上传画面的主体和构图,改成雨夜灯街。' },
|
||||
});
|
||||
@@ -621,8 +663,103 @@ test('puzzle workspace submits uploaded reference image when AI redraw is on', a
|
||||
expect(onCreateFromForm).toHaveBeenCalledWith({
|
||||
seedText: '保留上传画面的主体和构图,改成雨夜灯街。',
|
||||
pictureDescription: '保留上传画面的主体和构图,改成雨夜灯街。',
|
||||
referenceImageSrc: uploadedDataUrl,
|
||||
referenceImageSrc: null,
|
||||
referenceImageSrcs: [],
|
||||
referenceImageAssetObjectId: 'asset-reference-main-1',
|
||||
referenceImageAssetObjectIds: [],
|
||||
imageModel: 'gpt-image-2',
|
||||
aiRedraw: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('puzzle workspace uploads prompt references as asset object ids', async () => {
|
||||
const onCreateFromForm = vi.fn();
|
||||
const uploadedSources = [
|
||||
'data:image/png;base64,reference-1',
|
||||
'data:image/png;base64,reference-2',
|
||||
];
|
||||
let readIndex = 0;
|
||||
stubReferenceImageUpload(uploadedSources[0] ?? 'data:image/png;base64,reference-1');
|
||||
class MockFileReader {
|
||||
result: string | null = null;
|
||||
onload: null | (() => void) = null;
|
||||
onerror: null | (() => void) = null;
|
||||
|
||||
readAsDataURL() {
|
||||
this.result = uploadedSources[readIndex] ?? uploadedSources[0] ?? '';
|
||||
readIndex += 1;
|
||||
this.onload?.();
|
||||
}
|
||||
}
|
||||
vi.stubGlobal('FileReader', MockFileReader as unknown as typeof FileReader);
|
||||
vi.mocked(puzzleAssetClient.uploadReferenceImage)
|
||||
.mockResolvedValueOnce({
|
||||
assetObjectId: 'asset-reference-prompt-1',
|
||||
assetKind: 'puzzle_cover_image',
|
||||
objectKey: 'generated-puzzle-assets/reference/prompt-1.png',
|
||||
imageSrc: '/generated-puzzle-assets/reference/prompt-1.png',
|
||||
ownerUserId: 'user-1',
|
||||
ownerLabel: '账号 user-1',
|
||||
profileId: null,
|
||||
entityId: null,
|
||||
createdAt: '1713686400.000000Z',
|
||||
updatedAt: '1713686400.000000Z',
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
assetObjectId: 'asset-reference-prompt-2',
|
||||
assetKind: 'puzzle_cover_image',
|
||||
objectKey: 'generated-puzzle-assets/reference/prompt-2.png',
|
||||
imageSrc: '/generated-puzzle-assets/reference/prompt-2.png',
|
||||
ownerUserId: 'user-1',
|
||||
ownerLabel: '账号 user-1',
|
||||
profileId: null,
|
||||
entityId: null,
|
||||
createdAt: '1713686400.000000Z',
|
||||
updatedAt: '1713686400.000000Z',
|
||||
});
|
||||
|
||||
render(
|
||||
<PuzzleAgentWorkspace
|
||||
session={null}
|
||||
onBack={() => {}}
|
||||
onSubmitMessage={() => {}}
|
||||
onExecuteAction={() => {}}
|
||||
onCreateFromForm={onCreateFromForm}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByLabelText('画面描述'), {
|
||||
target: { value: '一只猫在雨夜灯牌下回头。' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText('上传参考图', { selector: 'input' }), {
|
||||
target: {
|
||||
files: uploadedSources.map(
|
||||
(_source, index) =>
|
||||
new File(['x'], `reference-${index + 1}.png`, {
|
||||
type: 'image/png',
|
||||
}),
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByRole('button', { name: /预览参考图/u })).toHaveLength(
|
||||
2,
|
||||
);
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: /生成拼图游戏草稿/u }));
|
||||
confirmPuzzlePointCost();
|
||||
|
||||
expect(onCreateFromForm).toHaveBeenCalledWith({
|
||||
seedText: '一只猫在雨夜灯牌下回头。',
|
||||
pictureDescription: '一只猫在雨夜灯牌下回头。',
|
||||
referenceImageSrc: null,
|
||||
referenceImageSrcs: [],
|
||||
referenceImageAssetObjectId: null,
|
||||
referenceImageAssetObjectIds: [
|
||||
'asset-reference-prompt-1',
|
||||
'asset-reference-prompt-2',
|
||||
],
|
||||
imageModel: 'gpt-image-2',
|
||||
aiRedraw: true,
|
||||
});
|
||||
@@ -705,7 +842,15 @@ test('puzzle workspace uploads prompt reference images from the description box'
|
||||
seedText: '一只猫在雨夜灯牌下回头。',
|
||||
pictureDescription: '一只猫在雨夜灯牌下回头。',
|
||||
referenceImageSrc: null,
|
||||
referenceImageSrcs: uploadedSources.slice(0, 5),
|
||||
referenceImageSrcs: [],
|
||||
referenceImageAssetObjectId: null,
|
||||
referenceImageAssetObjectIds: [
|
||||
'asset-reference-reference-1.png',
|
||||
'asset-reference-reference-2.png',
|
||||
'asset-reference-reference-3.png',
|
||||
'asset-reference-reference-4.png',
|
||||
'asset-reference-reference-5.png',
|
||||
],
|
||||
imageModel: 'gpt-image-2',
|
||||
aiRedraw: true,
|
||||
});
|
||||
@@ -790,7 +935,7 @@ test('puzzle workspace confirms before removing uploaded image', async () => {
|
||||
|
||||
test('puzzle workspace opens crop tool for non-square uploads', async () => {
|
||||
const sourceDataUrl = 'data:image/png;base64,wide-source';
|
||||
const croppedDataUrl = 'data:image/jpeg;base64,cropped-square';
|
||||
const croppedDataUrl = 'data:image/jpeg;base64,Y3JvcHBlZC1zcXVhcmU=';
|
||||
stubReferenceImageUpload(sourceDataUrl, 800, 600);
|
||||
const drawImage = stubCanvas(croppedDataUrl);
|
||||
|
||||
|
||||
@@ -16,9 +16,11 @@ import { getPuzzleHistoryAssetReferenceLabel } from '../../services/puzzle-works
|
||||
import {
|
||||
cropPuzzleReferenceImageDataUrl,
|
||||
isPuzzleReferenceImageSquare,
|
||||
puzzleReferenceImageDataUrlToFile,
|
||||
readPuzzleReferenceImageAsDataUrl,
|
||||
readPuzzleReferenceImageForUpload,
|
||||
} from '../../services/puzzleReferenceImage';
|
||||
import { puzzleAssetClient } from '../../services/puzzle-works/puzzleAssetClient';
|
||||
import {
|
||||
CreativeImageInputPanel,
|
||||
type CreativeImageInputReferenceImage,
|
||||
@@ -54,6 +56,7 @@ type PuzzleAgentWorkspaceProps = {
|
||||
type PuzzleFormState = {
|
||||
pictureDescription: string;
|
||||
referenceImageSrc: string;
|
||||
referenceImageAssetObjectId: string;
|
||||
referenceImageLabel: string;
|
||||
referenceImageSrcs: CreativeImageInputReferenceImage[];
|
||||
imageModel: PuzzleImageModelId;
|
||||
@@ -63,6 +66,7 @@ type PuzzleFormState = {
|
||||
const EMPTY_FORM_STATE: PuzzleFormState = {
|
||||
pictureDescription: '',
|
||||
referenceImageSrc: '',
|
||||
referenceImageAssetObjectId: '',
|
||||
referenceImageLabel: '',
|
||||
referenceImageSrcs: [],
|
||||
imageModel: PUZZLE_IMAGE_MODEL_GPT_IMAGE_2,
|
||||
@@ -74,6 +78,7 @@ const PUZZLE_PROMPT_REFERENCE_IMAGE_LIMIT = 5;
|
||||
type PuzzleImageCropState = {
|
||||
source: string;
|
||||
label: string;
|
||||
fileName: string;
|
||||
imageSize: { width: number; height: number };
|
||||
cropRect: SquareImageCropRect;
|
||||
error: string | null;
|
||||
@@ -97,11 +102,14 @@ function resolveInitialFormState(
|
||||
return {
|
||||
pictureDescription: formDraft.pictureDescription ?? '',
|
||||
referenceImageSrc: initialFormPayload?.referenceImageSrc ?? '',
|
||||
referenceImageAssetObjectId:
|
||||
initialFormPayload?.referenceImageAssetObjectId ?? '',
|
||||
referenceImageLabel: initialFormPayload?.referenceImageSrc
|
||||
? '已选择拼图图片'
|
||||
: '',
|
||||
referenceImageSrcs: createPuzzlePromptReferenceImagesFromSources(
|
||||
initialFormPayload?.referenceImageSrcs,
|
||||
initialFormPayload?.referenceImageAssetObjectIds,
|
||||
),
|
||||
imageModel: normalizePuzzleImageModel(initialFormPayload?.imageModel),
|
||||
aiRedraw: initialFormPayload?.aiRedraw ?? true,
|
||||
@@ -115,11 +123,14 @@ function resolveInitialFormState(
|
||||
initialFormPayload.seedText ??
|
||||
'',
|
||||
referenceImageSrc: initialFormPayload.referenceImageSrc ?? '',
|
||||
referenceImageAssetObjectId:
|
||||
initialFormPayload.referenceImageAssetObjectId ?? '',
|
||||
referenceImageLabel: initialFormPayload.referenceImageSrc
|
||||
? '已选择拼图图片'
|
||||
: '',
|
||||
referenceImageSrcs: createPuzzlePromptReferenceImagesFromSources(
|
||||
initialFormPayload.referenceImageSrcs,
|
||||
initialFormPayload.referenceImageAssetObjectIds,
|
||||
),
|
||||
imageModel: normalizePuzzleImageModel(initialFormPayload.imageModel),
|
||||
aiRedraw: initialFormPayload.aiRedraw ?? true,
|
||||
@@ -138,6 +149,7 @@ function resolveInitialFormState(
|
||||
session.seedText ||
|
||||
'',
|
||||
referenceImageSrc: '',
|
||||
referenceImageAssetObjectId: '',
|
||||
referenceImageLabel: '',
|
||||
referenceImageSrcs: [],
|
||||
imageModel: PUZZLE_IMAGE_MODEL_GPT_IMAGE_2,
|
||||
@@ -166,14 +178,46 @@ function normalizePuzzlePromptReferenceSources(
|
||||
|
||||
function createPuzzlePromptReferenceImagesFromSources(
|
||||
sources: readonly string[] | null | undefined,
|
||||
assetObjectIds: readonly string[] | null | undefined = [],
|
||||
): CreativeImageInputReferenceImage[] {
|
||||
return normalizePuzzlePromptReferenceSources(sources).map(
|
||||
const assetIds = normalizePuzzleAssetObjectIds(assetObjectIds);
|
||||
const sourceImages = normalizePuzzlePromptReferenceSources(sources).map(
|
||||
(imageSrc, index) => ({
|
||||
id: `restored:${index}:${imageSrc}`,
|
||||
label: `参考图 ${index + 1}`,
|
||||
imageSrc,
|
||||
assetObjectId: assetIds[index] ?? null,
|
||||
}),
|
||||
);
|
||||
if (sourceImages.length > 0) {
|
||||
return sourceImages;
|
||||
}
|
||||
|
||||
return assetIds.map((assetObjectId, index) => ({
|
||||
id: `restored-asset:${index}:${assetObjectId}`,
|
||||
label: `参考图 ${index + 1}`,
|
||||
imageSrc: '',
|
||||
assetObjectId,
|
||||
}));
|
||||
}
|
||||
|
||||
function normalizePuzzleAssetObjectIds(
|
||||
assetObjectIds: readonly (string | null | undefined)[] | null | undefined,
|
||||
) {
|
||||
const normalizedIds: string[] = [];
|
||||
for (const assetObjectId of assetObjectIds ?? []) {
|
||||
const normalized = assetObjectId?.trim() ?? '';
|
||||
if (
|
||||
normalized &&
|
||||
!normalizedIds.some((current) => current === normalized)
|
||||
) {
|
||||
normalizedIds.push(normalized);
|
||||
}
|
||||
if (normalizedIds.length >= PUZZLE_PROMPT_REFERENCE_IMAGE_LIMIT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return normalizedIds;
|
||||
}
|
||||
|
||||
function addPuzzlePromptReferenceImage(
|
||||
@@ -256,6 +300,21 @@ export function PuzzleAgentWorkspace({
|
||||
),
|
||||
[formState.referenceImageSrc, formState.referenceImageSrcs],
|
||||
);
|
||||
const promptReferenceAssetObjectIds = useMemo(
|
||||
() =>
|
||||
formState.referenceImageSrc
|
||||
? []
|
||||
: normalizePuzzleAssetObjectIds(
|
||||
formState.referenceImageSrcs.map((image) => image.assetObjectId),
|
||||
),
|
||||
[formState.referenceImageSrc, formState.referenceImageSrcs],
|
||||
);
|
||||
const mainReferenceImageSrcForPayload =
|
||||
formState.referenceImageAssetObjectId && formState.aiRedraw
|
||||
? null
|
||||
: formState.referenceImageSrc || null;
|
||||
const promptReferenceImageSrcsForPayload =
|
||||
promptReferenceAssetObjectIds.length > 0 ? [] : promptReferenceImageSrcs;
|
||||
const canSubmit = formState.aiRedraw
|
||||
? Boolean(pictureDescription) && !isBusy
|
||||
: Boolean(formState.referenceImageSrc) && !isBusy;
|
||||
@@ -263,16 +322,21 @@ export function PuzzleAgentWorkspace({
|
||||
() => ({
|
||||
seedText: pictureDescription,
|
||||
pictureDescription,
|
||||
referenceImageSrc: formState.referenceImageSrc || null,
|
||||
referenceImageSrcs: promptReferenceImageSrcs,
|
||||
referenceImageSrc: mainReferenceImageSrcForPayload,
|
||||
referenceImageSrcs: promptReferenceImageSrcsForPayload,
|
||||
referenceImageAssetObjectId:
|
||||
formState.referenceImageAssetObjectId || null,
|
||||
referenceImageAssetObjectIds: promptReferenceAssetObjectIds,
|
||||
imageModel: formState.imageModel,
|
||||
aiRedraw: formState.aiRedraw,
|
||||
}),
|
||||
[
|
||||
formState.aiRedraw,
|
||||
formState.referenceImageSrc,
|
||||
formState.referenceImageAssetObjectId,
|
||||
formState.imageModel,
|
||||
promptReferenceImageSrcs,
|
||||
mainReferenceImageSrcForPayload,
|
||||
promptReferenceAssetObjectIds,
|
||||
promptReferenceImageSrcsForPayload,
|
||||
pictureDescription,
|
||||
],
|
||||
);
|
||||
@@ -280,6 +344,8 @@ export function PuzzleAgentWorkspace({
|
||||
autosavePayload.pictureDescription,
|
||||
autosavePayload.referenceImageSrc,
|
||||
autosavePayload.referenceImageSrcs,
|
||||
autosavePayload.referenceImageAssetObjectId,
|
||||
autosavePayload.referenceImageAssetObjectIds,
|
||||
autosavePayload.aiRedraw,
|
||||
autosavePayload.imageModel,
|
||||
]);
|
||||
@@ -333,6 +399,7 @@ export function PuzzleAgentWorkspace({
|
||||
setCropState({
|
||||
source: uploadImage.dataUrl,
|
||||
label: file.name.trim() || '本地拼图图片',
|
||||
fileName: file.name.trim() || 'puzzle-reference.jpg',
|
||||
imageSize,
|
||||
cropRect: buildCenteredSquareImageCropRect(imageSize),
|
||||
error: null,
|
||||
@@ -342,9 +409,11 @@ export function PuzzleAgentWorkspace({
|
||||
return;
|
||||
}
|
||||
|
||||
const asset = await puzzleAssetClient.uploadReferenceImage({ file });
|
||||
setFormState((current) => ({
|
||||
...current,
|
||||
referenceImageSrc: uploadImage.dataUrl,
|
||||
referenceImageSrc: asset.imageSrc || uploadImage.dataUrl,
|
||||
referenceImageAssetObjectId: asset.assetObjectId,
|
||||
referenceImageLabel: file.name.trim() || '本地拼图图片',
|
||||
}));
|
||||
setReferenceImageError(null);
|
||||
@@ -372,11 +441,18 @@ export function PuzzleAgentWorkspace({
|
||||
|
||||
try {
|
||||
const images = await Promise.all(
|
||||
files.slice(0, remainingSlots).map(async (file, index) => ({
|
||||
id: `prompt-upload:${Date.now()}:${index}:${file.name}`,
|
||||
label: file.name.trim() || `参考图 ${index + 1}`,
|
||||
imageSrc: await readPuzzleReferenceImageAsDataUrl(file),
|
||||
})),
|
||||
files.slice(0, remainingSlots).map(async (file, index) => {
|
||||
const [imageSrc, asset] = await Promise.all([
|
||||
readPuzzleReferenceImageAsDataUrl(file),
|
||||
puzzleAssetClient.uploadReferenceImage({ file }),
|
||||
]);
|
||||
return {
|
||||
id: `prompt-upload:${Date.now()}:${index}:${file.name}`,
|
||||
label: file.name.trim() || `参考图 ${index + 1}`,
|
||||
imageSrc: asset.imageSrc || imageSrc,
|
||||
assetObjectId: asset.assetObjectId,
|
||||
};
|
||||
}),
|
||||
);
|
||||
setFormState((current) => ({
|
||||
...current,
|
||||
@@ -439,9 +515,15 @@ export function PuzzleAgentWorkspace({
|
||||
cropY: currentCropState.cropRect.y,
|
||||
cropSize: currentCropState.cropRect.size,
|
||||
});
|
||||
const file = puzzleReferenceImageDataUrlToFile(
|
||||
dataUrl,
|
||||
currentCropState.fileName,
|
||||
);
|
||||
const asset = await puzzleAssetClient.uploadReferenceImage({ file });
|
||||
setFormState((current) => ({
|
||||
...current,
|
||||
referenceImageSrc: dataUrl,
|
||||
referenceImageSrc: asset.imageSrc || dataUrl,
|
||||
referenceImageAssetObjectId: asset.assetObjectId,
|
||||
referenceImageLabel: currentCropState.label,
|
||||
}));
|
||||
setCropState(null);
|
||||
@@ -482,8 +564,11 @@ export function PuzzleAgentWorkspace({
|
||||
const payload = {
|
||||
seedText: payloadPictureDescription,
|
||||
pictureDescription: payloadPictureDescription,
|
||||
referenceImageSrc: formState.referenceImageSrc || null,
|
||||
referenceImageSrcs: promptReferenceImageSrcs,
|
||||
referenceImageSrc: mainReferenceImageSrcForPayload,
|
||||
referenceImageSrcs: promptReferenceImageSrcsForPayload,
|
||||
referenceImageAssetObjectId:
|
||||
formState.referenceImageAssetObjectId || null,
|
||||
referenceImageAssetObjectIds: promptReferenceAssetObjectIds,
|
||||
imageModel: formState.imageModel,
|
||||
aiRedraw: formState.aiRedraw,
|
||||
};
|
||||
@@ -499,8 +584,11 @@ export function PuzzleAgentWorkspace({
|
||||
action: 'compile_puzzle_draft',
|
||||
promptText: payloadPictureDescription,
|
||||
pictureDescription: payloadPictureDescription,
|
||||
referenceImageSrc: formState.referenceImageSrc || null,
|
||||
referenceImageSrcs: promptReferenceImageSrcs,
|
||||
referenceImageSrc: mainReferenceImageSrcForPayload,
|
||||
referenceImageSrcs: promptReferenceImageSrcsForPayload,
|
||||
referenceImageAssetObjectId:
|
||||
formState.referenceImageAssetObjectId || null,
|
||||
referenceImageAssetObjectIds: promptReferenceAssetObjectIds,
|
||||
imageModel: formState.imageModel,
|
||||
aiRedraw: formState.aiRedraw,
|
||||
candidateCount: 1,
|
||||
@@ -510,6 +598,7 @@ export function PuzzleAgentWorkspace({
|
||||
setFormState((current) => ({
|
||||
...current,
|
||||
referenceImageSrc: '',
|
||||
referenceImageAssetObjectId: '',
|
||||
referenceImageLabel: '',
|
||||
aiRedraw: true,
|
||||
}));
|
||||
@@ -645,6 +734,7 @@ export function PuzzleAgentWorkspace({
|
||||
setFormState((current) => ({
|
||||
...current,
|
||||
referenceImageSrc: asset.imageSrc,
|
||||
referenceImageAssetObjectId: asset.assetObjectId,
|
||||
referenceImageLabel: getPuzzleHistoryAssetReferenceLabel(
|
||||
asset.imageSrc,
|
||||
),
|
||||
|
||||
@@ -241,7 +241,7 @@ export function RpgCreationResultView({
|
||||
</div>
|
||||
<div className="platform-progress-track mt-3 h-3 overflow-hidden rounded-full">
|
||||
<div
|
||||
className="h-full bg-[linear-gradient(90deg,#ff4f8b_0%,#ff8a73_48%,#ffd2a6_100%)] transition-[width] duration-300"
|
||||
className="h-full bg-[linear-gradient(90deg,#df7f40_0%,#cc754c_48%,#eaccb3_100%)] transition-[width] duration-300"
|
||||
style={{ width: `${Math.max(0, Math.min(100, progress))}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -142,10 +142,10 @@ function VisualNovelStyleButton({
|
||||
aria-pressed={active}
|
||||
aria-label={label}
|
||||
onClick={onClick}
|
||||
className={`group relative h-[4.9rem] w-[5.85rem] shrink-0 snap-start overflow-hidden rounded-[0.95rem] border p-0 text-left transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose-200 sm:h-[5.45rem] sm:w-[6.4rem] ${
|
||||
className={`group relative h-[4.9rem] w-[5.85rem] shrink-0 snap-start overflow-hidden rounded-[0.95rem] border p-0 text-left transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--platform-warm-border)] sm:h-[5.45rem] sm:w-[6.4rem] ${
|
||||
active
|
||||
? 'border-rose-300 bg-white shadow-[0_8px_18px_rgba(190,18,60,0.10)] ring-2 ring-rose-100'
|
||||
: 'border-[var(--platform-subpanel-border)] bg-white/70 hover:border-rose-200 hover:bg-white/95'
|
||||
? 'border-[var(--platform-surface-hover-border)] bg-white shadow-[0_8px_18px_rgba(112,57,30,0.10)] ring-2 ring-[var(--platform-warm-border)]'
|
||||
: 'border-[var(--platform-subpanel-border)] bg-white/70 hover:border-[var(--platform-surface-hover-border)] hover:bg-white/95'
|
||||
} ${disabled ? 'cursor-not-allowed opacity-55' : ''}`}
|
||||
>
|
||||
{imageSrc ? (
|
||||
@@ -160,12 +160,12 @@ function VisualNovelStyleButton({
|
||||
)}
|
||||
<span className="absolute inset-0 bg-[linear-gradient(180deg,rgba(255,255,255,0.02)_0%,rgba(255,255,255,0.18)_44%,rgba(255,255,255,0.82)_100%)]" />
|
||||
{active ? (
|
||||
<span className="absolute right-1.5 top-1.5 h-2.5 w-2.5 rounded-full bg-rose-400 shadow-[0_0_0_3px_rgba(255,255,255,0.84)]" />
|
||||
<span className="absolute right-1.5 top-1.5 h-2.5 w-2.5 rounded-full bg-[var(--platform-accent)] shadow-[0_0_0_3px_rgba(255,255,255,0.84)]" />
|
||||
) : null}
|
||||
<span
|
||||
className={`absolute inset-x-2 bottom-1.5 truncate rounded-full px-1.5 py-0.5 text-center text-[11px] font-black shadow-[0_3px_10px_rgba(15,23,42,0.10)] ${
|
||||
active
|
||||
? 'bg-rose-50/95 text-rose-700'
|
||||
? 'bg-[var(--platform-warm-bg)] text-[var(--platform-warm-text)]'
|
||||
: 'bg-white/88 text-[var(--platform-text-strong)]'
|
||||
}`}
|
||||
>
|
||||
@@ -254,7 +254,7 @@ export function VisualNovelAgentWorkspace({
|
||||
<h1 className="m-0 text-3xl font-black leading-none tracking-normal text-[var(--platform-text-strong)] sm:text-7xl">
|
||||
{title}
|
||||
</h1>
|
||||
<span className="rounded-full border border-rose-200 bg-rose-50 px-3 py-1 text-[11px] font-black text-rose-700">
|
||||
<span className="rounded-full border border-[var(--platform-warm-border)] bg-[var(--platform-warm-bg)] px-3 py-1 text-[11px] font-black text-[var(--platform-warm-text)]">
|
||||
BETA
|
||||
</span>
|
||||
</div>
|
||||
@@ -280,7 +280,7 @@ export function VisualNovelAgentWorkspace({
|
||||
ideaText: event.target.value,
|
||||
}))
|
||||
}
|
||||
className="h-full min-h-[7.75rem] w-full resize-none rounded-[1.15rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 py-3 text-base leading-6 text-[var(--platform-text-strong)] outline-none transition focus:border-rose-200 focus:bg-white focus:ring-2 focus:ring-rose-100 sm:min-h-[9rem] lg:min-h-[14rem]"
|
||||
className="h-full min-h-[7.75rem] w-full resize-none rounded-[1.15rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 py-3 text-base leading-6 text-[var(--platform-text-strong)] outline-none transition focus:border-[var(--platform-surface-hover-border)] focus:bg-white focus:ring-2 focus:ring-[var(--platform-warm-border)] sm:min-h-[9rem] lg:min-h-[14rem]"
|
||||
aria-label="一句话创作"
|
||||
/>
|
||||
</label>
|
||||
|
||||
@@ -25,7 +25,7 @@ export function VisualNovelAttributePanel({ run }: VisualNovelAttributePanelProp
|
||||
</div>
|
||||
<div className="h-2 overflow-hidden rounded-full bg-[var(--platform-track-fill)]">
|
||||
<div
|
||||
className="h-full rounded-full bg-[var(--platform-button-primary-fill)]"
|
||||
className="h-full rounded-full bg-[var(--platform-button-primary-solid)]"
|
||||
style={{ width: `${Math.min(100, Math.max(0, value))}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,7 @@ export function VisualNovelSavePanel({
|
||||
type="button"
|
||||
disabled={!onSaveRun || isSaving}
|
||||
onClick={onSaveRun}
|
||||
className="flex min-h-12 w-full items-center justify-center gap-2 rounded-full border border-[var(--platform-subpanel-border)] bg-[var(--platform-button-primary-fill)] px-4 text-sm font-black text-white transition hover:brightness-105 disabled:cursor-not-allowed disabled:opacity-55"
|
||||
className="flex min-h-12 w-full items-center justify-center gap-2 rounded-full border border-[var(--platform-subpanel-border)] bg-[var(--platform-button-primary-solid)] px-4 text-sm font-black text-white transition hover:brightness-105 disabled:cursor-not-allowed disabled:opacity-55"
|
||||
>
|
||||
{isSaving ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
}
|
||||
|
||||
.bark-battle-primary-button {
|
||||
background: linear-gradient(135deg, #facc15, #fb7185);
|
||||
background: linear-gradient(135deg, #facc15, #c7653d);
|
||||
}
|
||||
|
||||
.bark-battle-status-card,
|
||||
|
||||
374
src/index.css
374
src/index.css
@@ -35,7 +35,7 @@
|
||||
--platform-bottom-nav-label-tracking: 0.18em;
|
||||
--platform-bottom-nav-content-gap: 0.22rem;
|
||||
--platform-bottom-nav-active-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08),
|
||||
0 8px 18px rgba(255, 91, 132, 0.1);
|
||||
0 8px 18px rgba(182, 98, 63, 0.1);
|
||||
}
|
||||
|
||||
html,
|
||||
@@ -485,272 +485,277 @@ body {
|
||||
|
||||
.platform-theme--light {
|
||||
color-scheme: light;
|
||||
--platform-accent: #c7653d;
|
||||
--platform-body-fill: radial-gradient(
|
||||
circle at top left,
|
||||
rgba(255, 196, 214, 0.14),
|
||||
transparent 24%
|
||||
),
|
||||
radial-gradient(
|
||||
circle at 88% 4%,
|
||||
rgba(255, 222, 196, 0.12),
|
||||
transparent 20%
|
||||
),
|
||||
radial-gradient(
|
||||
circle at bottom,
|
||||
rgba(255, 214, 225, 0.08),
|
||||
circle at 8% 0%,
|
||||
rgba(240, 203, 169, 0.24),
|
||||
transparent 26%
|
||||
),
|
||||
linear-gradient(180deg, #fffdfd 0%, #fffefe 50%, #fff8fa 100%);
|
||||
--platform-panel-shadow: 0 22px 60px rgba(215, 87, 134, 0.12),
|
||||
0 8px 20px rgba(255, 255, 255, 0.82);
|
||||
radial-gradient(
|
||||
circle at 92% 8%,
|
||||
rgba(226, 171, 134, 0.2),
|
||||
transparent 22%
|
||||
),
|
||||
radial-gradient(
|
||||
circle at 54% 100%,
|
||||
rgba(204, 117, 76, 0.08),
|
||||
transparent 30%
|
||||
),
|
||||
linear-gradient(180deg, #fffdf9 0%, #fdf9f5 54%, #f8efe7 100%);
|
||||
--platform-panel-shadow: 0 22px 60px rgba(112, 57, 30, 0.1),
|
||||
0 8px 22px rgba(255, 255, 255, 0.82);
|
||||
--platform-panel-fill: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.985),
|
||||
rgba(255, 250, 251, 0.96)
|
||||
rgba(253, 248, 243, 0.96)
|
||||
);
|
||||
--platform-panel-fill-soft: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.95),
|
||||
rgba(255, 248, 250, 0.88)
|
||||
rgba(255, 254, 252, 0.96),
|
||||
rgba(250, 243, 236, 0.9)
|
||||
);
|
||||
--platform-hero-fill: linear-gradient(
|
||||
135deg,
|
||||
rgba(255, 139, 162, 0.9),
|
||||
rgba(255, 184, 153, 0.88)
|
||||
rgba(244, 226, 211, 0.96),
|
||||
rgba(238, 208, 183, 0.9)
|
||||
);
|
||||
--platform-hero-glow-a: rgba(255, 255, 255, 0.22);
|
||||
--platform-hero-glow-b: rgba(255, 228, 211, 0.2);
|
||||
--platform-hero-glow-a: rgba(255, 255, 255, 0.42);
|
||||
--platform-hero-glow-b: rgba(228, 149, 91, 0.18);
|
||||
--platform-hero-overlay-strong: linear-gradient(
|
||||
135deg,
|
||||
rgba(255, 146, 170, 0.78),
|
||||
rgba(255, 201, 171, 0.72)
|
||||
rgba(238, 208, 183, 0.66),
|
||||
rgba(204, 117, 76, 0.18)
|
||||
);
|
||||
--platform-hero-overlay-soft: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.1),
|
||||
rgba(255, 246, 249, 0.26)
|
||||
rgba(255, 255, 255, 0.34),
|
||||
rgba(248, 233, 219, 0.22)
|
||||
);
|
||||
--platform-surface-border: rgba(239, 221, 228, 0.9);
|
||||
--platform-surface-hover-border: rgba(255, 154, 188, 0.58);
|
||||
--platform-shell-glow-1: rgba(255, 255, 255, 0.2);
|
||||
--platform-shell-glow-2: rgba(255, 220, 229, 0.18);
|
||||
--platform-shell-glow-3: rgba(255, 221, 194, 0.14);
|
||||
--platform-surface-glow-a: rgba(255, 213, 225, 0.14);
|
||||
--platform-surface-glow-b: rgba(255, 224, 201, 0.12);
|
||||
--platform-text-strong: #28151d;
|
||||
--platform-text-base: #5c4650;
|
||||
--platform-text-soft: #886f79;
|
||||
--platform-brand-logo-title: #3b1a24;
|
||||
--platform-brand-logo-subtitle: #d93570;
|
||||
--platform-brand-logo-shadow: #8f5870;
|
||||
--platform-line-soft: rgba(236, 214, 221, 0.72);
|
||||
--platform-surface-border: rgba(226, 203, 184, 0.88);
|
||||
--platform-surface-hover-border: rgba(204, 117, 76, 0.42);
|
||||
--platform-shell-glow-1: rgba(255, 255, 255, 0.34);
|
||||
--platform-shell-glow-2: rgba(238, 208, 183, 0.22);
|
||||
--platform-shell-glow-3: rgba(210, 132, 93, 0.12);
|
||||
--platform-surface-glow-a: rgba(240, 203, 169, 0.18);
|
||||
--platform-surface-glow-b: rgba(204, 117, 76, 0.1);
|
||||
--platform-text-strong: #3d1f10;
|
||||
--platform-text-base: #6f5848;
|
||||
--platform-text-soft: #988476;
|
||||
--platform-text-muted: #a38f80;
|
||||
--platform-brand-logo-title: #4a220f;
|
||||
--platform-brand-logo-subtitle: #c7653d;
|
||||
--platform-brand-logo-shadow: #caa48b;
|
||||
--platform-line-soft: rgba(226, 203, 184, 0.72);
|
||||
--platform-subpanel-fill: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.94),
|
||||
rgba(255, 250, 251, 0.9)
|
||||
rgba(255, 254, 252, 0.94),
|
||||
rgba(250, 243, 236, 0.9)
|
||||
);
|
||||
--platform-subpanel-border: rgba(233, 217, 223, 0.82);
|
||||
--platform-warm-border: rgba(255, 140, 116, 0.28);
|
||||
--platform-warm-bg: rgba(255, 140, 116, 0.14);
|
||||
--platform-warm-text: #cf4f4e;
|
||||
--platform-cool-border: rgba(255, 83, 142, 0.24);
|
||||
--platform-cool-bg: rgba(255, 83, 142, 0.14);
|
||||
--platform-cool-text: #d93570;
|
||||
--platform-neutral-border: rgba(232, 191, 205, 0.44);
|
||||
--platform-neutral-bg: rgba(255, 255, 255, 0.68);
|
||||
--platform-neutral-text: #715662;
|
||||
--platform-button-primary-border: rgba(255, 101, 147, 0.3);
|
||||
--platform-button-primary-fill: linear-gradient(135deg, #ff4f8b, #ff8a73);
|
||||
--platform-button-primary-text: #fff7fb;
|
||||
--platform-button-secondary-fill: rgba(255, 255, 255, 0.72);
|
||||
--platform-button-secondary-text: #4b3340;
|
||||
--platform-button-ghost-fill: rgba(255, 255, 255, 0.52);
|
||||
--platform-button-ghost-text: #6e5460;
|
||||
--platform-button-danger-border: rgba(251, 113, 133, 0.22);
|
||||
--platform-button-danger-fill: rgba(255, 228, 233, 0.94);
|
||||
--platform-button-danger-text: #c2415d;
|
||||
--platform-success-border: rgba(52, 211, 153, 0.24);
|
||||
--platform-success-bg: rgba(236, 253, 245, 0.92);
|
||||
--platform-success-text: #0f8a61;
|
||||
--platform-icon-fill: rgba(255, 255, 255, 0.62);
|
||||
--platform-icon-border: rgba(232, 191, 205, 0.46);
|
||||
--platform-icon-text: #7a5d67;
|
||||
--platform-subpanel-border: rgba(225, 204, 187, 0.82);
|
||||
--platform-warm-border: rgba(204, 117, 76, 0.28);
|
||||
--platform-warm-bg: rgba(234, 204, 179, 0.32);
|
||||
--platform-warm-text: #b6623f;
|
||||
--platform-cool-border: rgba(199, 117, 76, 0.24);
|
||||
--platform-cool-bg: rgba(238, 208, 183, 0.26);
|
||||
--platform-cool-text: #b76038;
|
||||
--platform-neutral-border: rgba(226, 203, 184, 0.44);
|
||||
--platform-neutral-bg: rgba(255, 253, 250, 0.7);
|
||||
--platform-neutral-text: #7b6150;
|
||||
--platform-button-primary-border: rgba(182, 98, 63, 0.32);
|
||||
--platform-button-primary-solid: #c7653d;
|
||||
--platform-button-primary-fill: linear-gradient(135deg, #df7f40, #b95d3a);
|
||||
--platform-button-primary-text: #fffaf5;
|
||||
--platform-button-secondary-fill: rgba(255, 253, 250, 0.78);
|
||||
--platform-button-secondary-text: #4b2412;
|
||||
--platform-button-ghost-fill: rgba(255, 253, 250, 0.56);
|
||||
--platform-button-ghost-text: #755a49;
|
||||
--platform-button-danger-border: rgba(185, 75, 58, 0.22);
|
||||
--platform-button-danger-fill: rgba(255, 237, 229, 0.94);
|
||||
--platform-button-danger-text: #a6402f;
|
||||
--platform-success-border: rgba(73, 144, 96, 0.24);
|
||||
--platform-success-bg: rgba(237, 248, 239, 0.92);
|
||||
--platform-success-text: #2f7b46;
|
||||
--platform-icon-fill: rgba(255, 253, 250, 0.68);
|
||||
--platform-icon-border: rgba(226, 203, 184, 0.5);
|
||||
--platform-icon-text: #7b5c49;
|
||||
--platform-nav-fill: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.96),
|
||||
rgba(255, 249, 250, 0.92)
|
||||
rgba(255, 254, 252, 0.96),
|
||||
rgba(250, 243, 236, 0.92)
|
||||
);
|
||||
--platform-nav-active-fill: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 91, 132, 0.16),
|
||||
rgba(255, 151, 116, 0.16)
|
||||
rgba(238, 208, 183, 0.48),
|
||||
rgba(204, 117, 76, 0.16)
|
||||
);
|
||||
--platform-nav-active-border: rgba(255, 126, 154, 0.32);
|
||||
--platform-nav-active-shadow: 0 10px 22px rgba(255, 91, 132, 0.12);
|
||||
--platform-nav-active-border: rgba(204, 117, 76, 0.34);
|
||||
--platform-nav-active-shadow: 0 10px 24px rgba(182, 98, 63, 0.12);
|
||||
--platform-modal-fill: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.96),
|
||||
rgba(255, 245, 248, 0.95)
|
||||
rgba(255, 254, 252, 0.97),
|
||||
rgba(250, 243, 236, 0.95)
|
||||
);
|
||||
--platform-modal-border: rgba(255, 255, 255, 0.52);
|
||||
--platform-modal-border: rgba(255, 255, 255, 0.58);
|
||||
--platform-desktop-shell-fill: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.99),
|
||||
rgba(255, 251, 252, 0.985)
|
||||
rgba(253, 249, 245, 0.985)
|
||||
);
|
||||
--platform-desktop-shell-border: rgba(240, 228, 232, 0.94);
|
||||
--platform-desktop-shell-inner-border: rgba(241, 230, 234, 0.92);
|
||||
--platform-desktop-shell-border: rgba(230, 213, 199, 0.94);
|
||||
--platform-desktop-shell-inner-border: rgba(233, 218, 205, 0.92);
|
||||
--platform-desktop-topbar-fill: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.95),
|
||||
rgba(255, 251, 252, 0.92)
|
||||
rgba(253, 249, 245, 0.92)
|
||||
);
|
||||
--platform-desktop-panel-fill: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.95),
|
||||
rgba(255, 250, 251, 0.91)
|
||||
rgba(255, 254, 252, 0.95),
|
||||
rgba(250, 243, 236, 0.91)
|
||||
);
|
||||
--platform-desktop-panel-border: rgba(238, 223, 228, 0.88);
|
||||
--platform-desktop-hover-shadow: 0 16px 28px rgba(222, 82, 124, 0.12);
|
||||
--platform-desktop-panel-border: rgba(226, 203, 184, 0.88);
|
||||
--platform-desktop-hover-shadow: 0 16px 30px rgba(112, 57, 30, 0.12);
|
||||
--platform-overlay-fill: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 184, 204, 0.14),
|
||||
rgba(255, 255, 255, 0.56)
|
||||
rgba(240, 203, 169, 0.14),
|
||||
rgba(255, 255, 255, 0.58)
|
||||
);
|
||||
--platform-track-border: rgba(234, 193, 208, 0.46);
|
||||
--platform-track-fill: rgba(255, 255, 255, 0.88);
|
||||
--platform-track-border: rgba(226, 203, 184, 0.48);
|
||||
--platform-track-fill: rgba(255, 253, 250, 0.9);
|
||||
--platform-page-fill: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.9),
|
||||
rgba(255, 250, 251, 0.8)
|
||||
rgba(250, 243, 236, 0.8)
|
||||
);
|
||||
--platform-page-border: rgba(241, 230, 234, 0.88);
|
||||
--platform-input-fill: rgba(255, 255, 255, 0.94);
|
||||
--platform-input-fill-focus: rgba(255, 255, 255, 0.96);
|
||||
--platform-page-border: rgba(233, 218, 205, 0.88);
|
||||
--platform-input-fill: rgba(255, 253, 250, 0.94);
|
||||
--platform-input-fill-focus: rgba(255, 254, 252, 0.96);
|
||||
--platform-input-highlight: rgba(255, 255, 255, 0.9);
|
||||
--platform-input-focus-ring: rgba(255, 91, 132, 0.14);
|
||||
--platform-nav-item-text: #7c6770;
|
||||
--platform-nav-item-text-active: #2d1820;
|
||||
--platform-nav-item-hover-fill: rgba(255, 244, 247, 0.92);
|
||||
--platform-nav-item-icon-fill: rgba(248, 244, 246, 1);
|
||||
--platform-nav-item-icon-text: #7a5d67;
|
||||
--platform-nav-item-icon-active-fill: rgba(255, 255, 255, 0.98);
|
||||
--platform-nav-item-icon-active-text: #d93570;
|
||||
--platform-nav-icon-active-shadow: 0 12px 24px rgba(255, 91, 132, 0.16);
|
||||
--platform-input-focus-ring: rgba(204, 117, 76, 0.15);
|
||||
--platform-nav-item-text: #80695a;
|
||||
--platform-nav-item-text-active: #3d1f10;
|
||||
--platform-nav-item-hover-fill: rgba(253, 248, 243, 0.94);
|
||||
--platform-nav-item-icon-fill: rgba(250, 243, 236, 1);
|
||||
--platform-nav-item-icon-text: #7b5c49;
|
||||
--platform-nav-item-icon-active-fill: rgba(255, 254, 252, 0.98);
|
||||
--platform-nav-item-icon-active-text: #c7653d;
|
||||
--platform-nav-icon-active-shadow: 0 12px 24px rgba(182, 98, 63, 0.16);
|
||||
--platform-profile-hero-fill: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.96),
|
||||
rgba(255, 245, 248, 0.9)
|
||||
rgba(255, 254, 252, 0.96),
|
||||
rgba(250, 239, 229, 0.9)
|
||||
);
|
||||
--platform-profile-hero-border: rgba(255, 255, 255, 0.52);
|
||||
--platform-profile-hero-shadow: 0 20px 56px rgba(216, 74, 124, 0.18);
|
||||
--platform-profile-hero-border: rgba(255, 255, 255, 0.56);
|
||||
--platform-profile-hero-shadow: 0 20px 56px rgba(112, 57, 30, 0.16);
|
||||
--platform-profile-avatar-fill: linear-gradient(
|
||||
135deg,
|
||||
rgba(255, 79, 139, 0.96),
|
||||
rgba(255, 140, 110, 0.9)
|
||||
rgba(223, 127, 64, 0.96),
|
||||
rgba(181, 91, 56, 0.92)
|
||||
);
|
||||
--platform-profile-avatar-shadow: 0 14px 30px rgba(255, 79, 139, 0.24);
|
||||
--platform-profile-chip-fill: rgba(255, 255, 255, 0.88);
|
||||
--platform-profile-chip-hover-fill: rgba(255, 255, 255, 0.96);
|
||||
--platform-profile-chip-text: #6a505b;
|
||||
--platform-profile-action-fill: linear-gradient(135deg, #ff4f8b, #ff8a73);
|
||||
--platform-profile-action-text: #fff7fb;
|
||||
--platform-profile-action-shadow: 0 14px 30px rgba(255, 79, 139, 0.24);
|
||||
--platform-profile-avatar-shadow: 0 14px 30px rgba(182, 98, 63, 0.22);
|
||||
--platform-profile-chip-fill: rgba(255, 253, 250, 0.88);
|
||||
--platform-profile-chip-hover-fill: rgba(255, 254, 252, 0.96);
|
||||
--platform-profile-chip-text: #755a49;
|
||||
--platform-profile-action-fill: linear-gradient(135deg, #df7f40, #b95d3a);
|
||||
--platform-profile-action-solid: #c7653d;
|
||||
--platform-profile-action-text: #fffaf5;
|
||||
--platform-profile-action-shadow: 0 14px 30px rgba(182, 98, 63, 0.22);
|
||||
--platform-card-overlay-soft: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.08),
|
||||
rgba(255, 247, 249, 0.82)
|
||||
rgba(250, 243, 236, 0.82)
|
||||
);
|
||||
--platform-card-overlay-strong: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.16),
|
||||
rgba(255, 243, 247, 0.92)
|
||||
rgba(248, 239, 231, 0.92)
|
||||
);
|
||||
--platform-card-overlay-deep: radial-gradient(
|
||||
circle at top left,
|
||||
rgba(255, 255, 255, 0.2),
|
||||
transparent 30%
|
||||
),
|
||||
radial-gradient(circle at right, rgba(255, 205, 178, 0.14), transparent 28%),
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.2), rgba(255, 241, 246, 0.9));
|
||||
radial-gradient(circle at right, rgba(228, 149, 91, 0.14), transparent 28%),
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.2), rgba(248, 239, 231, 0.9));
|
||||
--platform-recommend-runtime-fill: var(--platform-panel-fill);
|
||||
--platform-recommend-runtime-border: rgba(232, 191, 205, 0.42);
|
||||
--platform-recommend-runtime-shadow: 0 18px 44px rgba(215, 87, 134, 0.13),
|
||||
--platform-recommend-runtime-border: rgba(226, 203, 184, 0.42);
|
||||
--platform-recommend-runtime-shadow: 0 18px 44px rgba(112, 57, 30, 0.12),
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.58);
|
||||
--platform-recommend-runtime-state-fill: radial-gradient(
|
||||
circle at 50% 18%,
|
||||
rgba(255, 91, 132, 0.12),
|
||||
rgba(204, 117, 76, 0.12),
|
||||
transparent 34%
|
||||
),
|
||||
linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.98),
|
||||
rgba(255, 246, 249, 0.94)
|
||||
rgba(250, 243, 236, 0.94)
|
||||
);
|
||||
--platform-recommend-runtime-state-text: var(--platform-text-strong);
|
||||
--puzzle-runtime-shell-fill: var(--platform-body-fill);
|
||||
--puzzle-runtime-stage-fill: radial-gradient(
|
||||
circle at 50% 18%,
|
||||
rgba(255, 91, 132, 0.13),
|
||||
rgba(204, 117, 76, 0.13),
|
||||
transparent 30%
|
||||
),
|
||||
radial-gradient(
|
||||
circle at 18% 82%,
|
||||
rgba(255, 138, 115, 0.13),
|
||||
rgba(240, 203, 169, 0.18),
|
||||
transparent 28%
|
||||
),
|
||||
linear-gradient(180deg, #fffefe 0%, #fff7fa 58%, #fff1f5 100%);
|
||||
--puzzle-runtime-grid-line: rgba(130, 75, 95, 0.06);
|
||||
linear-gradient(180deg, #fffdf9 0%, #fbf5ed 58%, #f4e5d7 100%);
|
||||
--puzzle-runtime-grid-line: rgba(112, 57, 30, 0.06);
|
||||
--puzzle-runtime-text-strong: var(--platform-text-strong);
|
||||
--puzzle-runtime-text-base: var(--platform-text-base);
|
||||
--puzzle-runtime-text-soft: var(--platform-text-soft);
|
||||
--puzzle-runtime-surface-fill: rgba(255, 255, 255, 0.76);
|
||||
--puzzle-runtime-surface-fill-strong: rgba(255, 255, 255, 0.9);
|
||||
--puzzle-runtime-surface-border: rgba(232, 191, 205, 0.48);
|
||||
--puzzle-runtime-board-fill: rgba(255, 255, 255, 0.68);
|
||||
--puzzle-runtime-board-border: rgba(255, 126, 154, 0.28);
|
||||
--puzzle-runtime-board-shadow: 0 30px 80px rgba(215, 87, 134, 0.14);
|
||||
--puzzle-runtime-piece-fill: rgba(255, 255, 255, 0.74);
|
||||
--puzzle-runtime-surface-fill: rgba(255, 253, 250, 0.76);
|
||||
--puzzle-runtime-surface-fill-strong: rgba(255, 253, 250, 0.9);
|
||||
--puzzle-runtime-surface-border: rgba(226, 203, 184, 0.48);
|
||||
--puzzle-runtime-board-fill: rgba(255, 253, 250, 0.68);
|
||||
--puzzle-runtime-board-border: rgba(204, 117, 76, 0.28);
|
||||
--puzzle-runtime-board-shadow: 0 30px 80px rgba(112, 57, 30, 0.14);
|
||||
--puzzle-runtime-piece-fill: rgba(255, 253, 250, 0.74);
|
||||
--puzzle-runtime-piece-border: transparent;
|
||||
--puzzle-runtime-piece-empty-fill: rgba(255, 228, 236, 0.34);
|
||||
--puzzle-runtime-piece-empty-text: rgba(92, 70, 80, 0.38);
|
||||
--puzzle-runtime-piece-empty-fill: rgba(238, 208, 183, 0.34);
|
||||
--puzzle-runtime-piece-empty-text: rgba(111, 88, 72, 0.4);
|
||||
--puzzle-runtime-piece-selected-fill: linear-gradient(
|
||||
135deg,
|
||||
#ff4f8b,
|
||||
#ff8a73
|
||||
#df7f40,
|
||||
#b95d3a
|
||||
);
|
||||
--puzzle-runtime-piece-selected-text: #fff7fb;
|
||||
--puzzle-runtime-piece-selected-text: #fffaf5;
|
||||
--puzzle-runtime-piece-selected-border: transparent;
|
||||
--puzzle-runtime-next-card-overlay: rgba(61, 24, 38, 0.06);
|
||||
--puzzle-runtime-control-fill: rgba(255, 255, 255, 0.72);
|
||||
--puzzle-runtime-control-hover-fill: rgba(255, 91, 132, 0.1);
|
||||
--puzzle-runtime-next-card-overlay: rgba(74, 34, 15, 0.06);
|
||||
--puzzle-runtime-control-fill: rgba(255, 253, 250, 0.72);
|
||||
--puzzle-runtime-control-hover-fill: rgba(204, 117, 76, 0.1);
|
||||
--puzzle-runtime-primary-fill: var(--platform-button-primary-fill);
|
||||
--puzzle-runtime-primary-text: var(--platform-button-primary-text);
|
||||
--puzzle-runtime-primary-shadow: var(--platform-profile-action-shadow);
|
||||
--puzzle-runtime-accent-text: var(--platform-cool-text);
|
||||
--puzzle-runtime-cool-text: #0f8fa9;
|
||||
--puzzle-runtime-danger-fill: rgba(255, 228, 233, 0.9);
|
||||
--puzzle-runtime-danger-text: #c2415d;
|
||||
--puzzle-runtime-backdrop-fill: rgba(43, 20, 32, 0.34);
|
||||
--puzzle-runtime-cool-text: #6e8d42;
|
||||
--puzzle-runtime-danger-fill: rgba(255, 237, 229, 0.9);
|
||||
--puzzle-runtime-danger-text: #a6402f;
|
||||
--puzzle-runtime-backdrop-fill: rgba(61, 31, 16, 0.34);
|
||||
--puzzle-runtime-dialog-fill: radial-gradient(
|
||||
circle at 12% 0%,
|
||||
rgba(255, 91, 132, 0.18),
|
||||
rgba(204, 117, 76, 0.18),
|
||||
transparent 36%
|
||||
),
|
||||
linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.98),
|
||||
rgba(255, 246, 249, 0.95)
|
||||
rgba(255, 254, 252, 0.98),
|
||||
rgba(250, 243, 236, 0.95)
|
||||
);
|
||||
--puzzle-runtime-dialog-border: rgba(255, 126, 154, 0.3);
|
||||
--puzzle-runtime-table-fill: rgba(255, 255, 255, 0.62);
|
||||
--puzzle-runtime-table-row-fill: rgba(255, 91, 132, 0.12);
|
||||
--puzzle-runtime-next-card-fill: rgba(255, 255, 255, 0.66);
|
||||
--puzzle-runtime-next-card-hover-fill: rgba(255, 91, 132, 0.1);
|
||||
--puzzle-runtime-dialog-border: rgba(204, 117, 76, 0.3);
|
||||
--puzzle-runtime-table-fill: rgba(255, 253, 250, 0.62);
|
||||
--puzzle-runtime-table-row-fill: rgba(204, 117, 76, 0.12);
|
||||
--puzzle-runtime-next-card-fill: rgba(255, 253, 250, 0.66);
|
||||
--puzzle-runtime-next-card-hover-fill: rgba(204, 117, 76, 0.1);
|
||||
}
|
||||
|
||||
.platform-theme--dark {
|
||||
color-scheme: dark;
|
||||
--platform-accent: #5b6cff;
|
||||
--platform-body-fill: radial-gradient(
|
||||
circle at top,
|
||||
rgba(129, 140, 248, 0.2),
|
||||
@@ -797,6 +802,7 @@ body {
|
||||
--platform-text-strong: #ffffff;
|
||||
--platform-text-base: rgb(228 228 231);
|
||||
--platform-text-soft: rgb(161 161 170);
|
||||
--platform-text-muted: rgb(161 161 170);
|
||||
--platform-brand-logo-title: #fff7dc;
|
||||
--platform-brand-logo-subtitle: #9fe7ff;
|
||||
--platform-brand-logo-shadow: #040814;
|
||||
@@ -813,6 +819,7 @@ body {
|
||||
--platform-neutral-bg: rgba(255, 255, 255, 0.05);
|
||||
--platform-neutral-text: rgb(228 228 231);
|
||||
--platform-button-primary-border: rgba(129, 140, 248, 0.3);
|
||||
--platform-button-primary-solid: #5b6cff;
|
||||
--platform-button-primary-fill: linear-gradient(135deg, #5b6cff, #3dd9ff);
|
||||
--platform-button-primary-text: rgb(238 248 255);
|
||||
--platform-button-secondary-fill: rgba(255, 255, 255, 0.05);
|
||||
@@ -907,6 +914,7 @@ body {
|
||||
--platform-profile-chip-hover-fill: rgba(255, 255, 255, 0.14);
|
||||
--platform-profile-chip-text: rgb(228 228 231);
|
||||
--platform-profile-action-fill: linear-gradient(135deg, #5b6cff, #3dd9ff);
|
||||
--platform-profile-action-solid: #5b6cff;
|
||||
--platform-profile-action-text: rgb(238 248 255);
|
||||
--platform-profile-action-shadow: 0 14px 32px rgba(91, 108, 255, 0.22);
|
||||
--platform-card-overlay-soft: linear-gradient(
|
||||
@@ -1748,9 +1756,9 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
}
|
||||
|
||||
.platform-pill--rose {
|
||||
border-color: rgba(251, 113, 133, 0.2);
|
||||
background: rgba(244, 63, 94, 0.1);
|
||||
color: rgb(255 228 230);
|
||||
border-color: var(--platform-button-danger-border);
|
||||
background: var(--platform-button-danger-fill);
|
||||
color: var(--platform-button-danger-text);
|
||||
}
|
||||
|
||||
.platform-pill--success {
|
||||
@@ -1872,7 +1880,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
}
|
||||
|
||||
.creation-work-card__swipe-button--danger {
|
||||
background: linear-gradient(180deg, #fb7185, #e11d48);
|
||||
background: linear-gradient(180deg, #c7653d, #8f3f27);
|
||||
}
|
||||
|
||||
.creation-work-card__swipe-button:disabled {
|
||||
@@ -2087,7 +2095,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
width: 0.6rem;
|
||||
height: 0.6rem;
|
||||
border-radius: 9999px;
|
||||
background: #ef4444;
|
||||
background: #b64a35;
|
||||
box-shadow:
|
||||
0 0 0 3px rgba(255, 255, 255, 0.26),
|
||||
0 0 12px rgba(239, 68, 68, 0.68);
|
||||
@@ -2129,7 +2137,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
}
|
||||
|
||||
.creation-work-card-stat--like {
|
||||
--creation-work-stat-accent: #ff6b6b;
|
||||
--creation-work-stat-accent: #b64a35;
|
||||
}
|
||||
|
||||
.creation-work-card-stat__label {
|
||||
@@ -2177,7 +2185,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.08rem;
|
||||
color: #ef233c;
|
||||
color: #b64a35;
|
||||
font-size: 0.54rem;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
@@ -2334,7 +2342,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
border-color: var(--platform-button-primary-border);
|
||||
background: var(--platform-button-primary-fill);
|
||||
color: var(--platform-button-primary-text);
|
||||
box-shadow: 0 16px 34px rgba(255, 91, 132, 0.18);
|
||||
box-shadow: 0 16px 34px rgba(182, 98, 63, 0.18);
|
||||
}
|
||||
|
||||
.platform-button--secondary {
|
||||
@@ -2409,7 +2417,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
border-color: var(--platform-button-primary-border);
|
||||
background: var(--platform-button-primary-fill);
|
||||
color: var(--platform-button-primary-text);
|
||||
box-shadow: 0 12px 26px rgba(255, 91, 132, 0.16);
|
||||
box-shadow: 0 12px 26px rgba(182, 98, 63, 0.16);
|
||||
}
|
||||
|
||||
.platform-close-confirm-dialog__button--secondary {
|
||||
@@ -2567,7 +2575,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
width: 0.48rem;
|
||||
height: 0.48rem;
|
||||
border-radius: 9999px;
|
||||
background: #ef4444;
|
||||
background: #b64a35;
|
||||
box-shadow:
|
||||
0 0 0 2px rgba(255, 255, 255, 0.28),
|
||||
0 0 12px rgba(239, 68, 68, 0.72);
|
||||
@@ -5440,9 +5448,9 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
}
|
||||
|
||||
.platform-banner--danger {
|
||||
border-color: rgba(251, 113, 133, 0.2);
|
||||
background: rgba(244, 63, 94, 0.1);
|
||||
color: #c2415d;
|
||||
border-color: var(--platform-button-danger-border);
|
||||
background: var(--platform-button-danger-fill);
|
||||
color: #a6402f;
|
||||
}
|
||||
|
||||
.platform-progress-track {
|
||||
@@ -5492,7 +5500,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
rgba(255, 138, 115, 0.22),
|
||||
transparent 34%
|
||||
),
|
||||
linear-gradient(135deg, #fff9fb 0%, #ffe8f0 48%, #ffdacf 100%);
|
||||
linear-gradient(135deg, #fffdf9 0%, #f4e5d7 48%, #eaccb3 100%);
|
||||
}
|
||||
|
||||
.platform-theme--light
|
||||
@@ -6060,14 +6068,14 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
border-color: var(--platform-surface-border) !important;
|
||||
background: radial-gradient(
|
||||
circle at top left,
|
||||
rgba(255, 204, 219, 0.34),
|
||||
rgba(240, 203, 169, 0.34),
|
||||
transparent 34%
|
||||
),
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(255, 248, 250, 0.9)) !important;
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(250, 243, 236, 0.9)) !important;
|
||||
color: var(--platform-text-strong) !important;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.84),
|
||||
0 18px 42px rgba(222, 82, 124, 0.1);
|
||||
0 18px 42px rgba(112, 57, 30, 0.1);
|
||||
}
|
||||
|
||||
.platform-theme--light
|
||||
@@ -6106,18 +6114,18 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
border-color: var(--platform-button-primary-border) !important;
|
||||
background: var(--platform-button-primary-fill) !important;
|
||||
color: var(--platform-button-primary-text) !important;
|
||||
box-shadow: 0 12px 26px rgba(255, 91, 132, 0.16);
|
||||
box-shadow: 0 12px 26px rgba(182, 98, 63, 0.16);
|
||||
}
|
||||
|
||||
.platform-theme--light .map-modal-overlay {
|
||||
background: rgba(73, 45, 56, 0.24) !important;
|
||||
background: rgba(74, 34, 15, 0.24) !important;
|
||||
}
|
||||
|
||||
.platform-theme--light .map-modal-shell {
|
||||
background: var(--platform-modal-fill);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.78),
|
||||
0 24px 70px rgba(131, 77, 98, 0.2) !important;
|
||||
0 24px 70px rgba(112, 57, 30, 0.2) !important;
|
||||
}
|
||||
|
||||
.platform-theme--light .map-modal-backdrop {
|
||||
@@ -6129,7 +6137,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.7),
|
||||
rgba(255, 247, 250, 0.88)
|
||||
rgba(250, 243, 236, 0.88)
|
||||
) !important;
|
||||
}
|
||||
|
||||
@@ -6146,7 +6154,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
|
||||
}
|
||||
|
||||
.platform-theme--light .map-modal-shell svg line {
|
||||
stroke: rgba(217, 53, 112, 0.32);
|
||||
stroke: rgba(199, 101, 61, 0.32);
|
||||
}
|
||||
|
||||
button {
|
||||
@@ -7045,23 +7053,23 @@ button {
|
||||
border: 1px solid
|
||||
color-mix(
|
||||
in srgb,
|
||||
var(--platform-work-like-accent, #ff6b6b) 24%,
|
||||
var(--platform-work-like-accent, #c7653d) 24%,
|
||||
transparent
|
||||
);
|
||||
border-radius: 1rem;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--platform-work-like-accent, #ff6b6b) 10%,
|
||||
var(--platform-work-like-accent, #c7653d) 10%,
|
||||
var(--platform-panel-fill) 90%
|
||||
);
|
||||
color: var(--platform-work-like-accent, #ff6b6b);
|
||||
color: var(--platform-work-like-accent, #c7653d);
|
||||
padding: 0.6rem 0.75rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 900;
|
||||
box-shadow: 0 0.55rem 1.2rem
|
||||
color-mix(
|
||||
in srgb,
|
||||
var(--platform-work-like-accent, #ff6b6b) 10%,
|
||||
var(--platform-work-like-accent, #c7653d) 10%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
@@ -7080,7 +7088,7 @@ button {
|
||||
}
|
||||
|
||||
.platform-work-detail__stat {
|
||||
--platform-work-stat-accent: #ff4f8b;
|
||||
--platform-work-stat-accent: #c7653d;
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
@@ -7103,7 +7111,7 @@ button {
|
||||
}
|
||||
|
||||
.platform-work-detail__stat--like {
|
||||
--platform-work-stat-accent: #ff6b6b;
|
||||
--platform-work-stat-accent: #b64a35;
|
||||
}
|
||||
|
||||
.platform-work-detail__stat--time {
|
||||
|
||||
@@ -13,6 +13,153 @@ export type PuzzleHistoryAsset = {
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type PuzzleReferenceAsset = PuzzleHistoryAsset & {
|
||||
objectKey: string;
|
||||
};
|
||||
|
||||
type DirectUploadTicketResponse = {
|
||||
upload: {
|
||||
bucket: string;
|
||||
host: string;
|
||||
objectKey: string;
|
||||
legacyPublicPath: string;
|
||||
formFields: Record<string, string | null | undefined>;
|
||||
};
|
||||
};
|
||||
|
||||
type ConfirmAssetObjectResponse = {
|
||||
assetObject: {
|
||||
assetObjectId: string;
|
||||
objectKey: string;
|
||||
assetKind: 'puzzle_cover_image';
|
||||
ownerUserId?: string | null;
|
||||
profileId?: string | null;
|
||||
entityId?: string | null;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
};
|
||||
};
|
||||
|
||||
const PUZZLE_REFERENCE_IMAGE_MAX_UPLOAD_BYTES = 12 * 1024 * 1024;
|
||||
|
||||
const MIME_BY_EXTENSION: Record<string, string> = {
|
||||
jpeg: 'image/jpeg',
|
||||
jpg: 'image/jpeg',
|
||||
png: 'image/png',
|
||||
webp: 'image/webp',
|
||||
};
|
||||
|
||||
function resolvePuzzleImageContentType(file: File) {
|
||||
if (file.type.trim()) {
|
||||
return file.type.trim();
|
||||
}
|
||||
|
||||
const extension = file.name.split('.').pop()?.trim().toLowerCase() ?? '';
|
||||
return MIME_BY_EXTENSION[extension] ?? 'application/octet-stream';
|
||||
}
|
||||
|
||||
function validatePuzzleReferenceImageFile(file: File) {
|
||||
const contentType = resolvePuzzleImageContentType(file);
|
||||
if (file.size <= 0) {
|
||||
throw new Error('参考图文件为空,请重新选择。');
|
||||
}
|
||||
if (file.size > PUZZLE_REFERENCE_IMAGE_MAX_UPLOAD_BYTES) {
|
||||
throw new Error('参考图过大,请压缩后再上传。');
|
||||
}
|
||||
if (!contentType.startsWith('image/')) {
|
||||
throw new Error('参考图必须是图片文件。');
|
||||
}
|
||||
}
|
||||
|
||||
async function postDirectUploadFile(
|
||||
upload: DirectUploadTicketResponse['upload'],
|
||||
file: File,
|
||||
) {
|
||||
const formData = new FormData();
|
||||
Object.entries(upload.formFields).forEach(([key, value]) => {
|
||||
if (value !== null && value !== undefined) {
|
||||
formData.append(key, value);
|
||||
}
|
||||
});
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await fetch(upload.host, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('上传拼图参考图失败。');
|
||||
}
|
||||
}
|
||||
|
||||
export async function uploadPuzzleReferenceImage(payload: {
|
||||
file: File;
|
||||
}): Promise<PuzzleReferenceAsset> {
|
||||
validatePuzzleReferenceImageFile(payload.file);
|
||||
const contentType = resolvePuzzleImageContentType(payload.file);
|
||||
const uploadedAt = Date.now();
|
||||
const ticket = await requestJson<DirectUploadTicketResponse>(
|
||||
'/api/assets/direct-upload-tickets',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
legacyPrefix: 'generated-puzzle-assets',
|
||||
pathSegments: ['puzzle-reference', 'draft', `${uploadedAt}`],
|
||||
fileName: payload.file.name,
|
||||
contentType,
|
||||
access: 'private',
|
||||
maxSizeBytes: PUZZLE_REFERENCE_IMAGE_MAX_UPLOAD_BYTES,
|
||||
metadata: {
|
||||
asset_kind: 'puzzle_cover_image',
|
||||
puzzle_slot: 'reference_image',
|
||||
},
|
||||
}),
|
||||
},
|
||||
'创建拼图参考图上传凭证失败',
|
||||
);
|
||||
|
||||
await postDirectUploadFile(ticket.upload, payload.file);
|
||||
|
||||
const confirmed = await requestJson<ConfirmAssetObjectResponse>(
|
||||
'/api/assets/objects/confirm',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
bucket: ticket.upload.bucket,
|
||||
objectKey: ticket.upload.objectKey,
|
||||
contentType,
|
||||
contentLength: payload.file.size,
|
||||
assetKind: 'puzzle_cover_image',
|
||||
accessPolicy: 'private',
|
||||
}),
|
||||
},
|
||||
'确认拼图参考图失败',
|
||||
);
|
||||
|
||||
return {
|
||||
assetObjectId: confirmed.assetObject.assetObjectId,
|
||||
assetKind: confirmed.assetObject.assetKind,
|
||||
objectKey: confirmed.assetObject.objectKey,
|
||||
imageSrc: ticket.upload.legacyPublicPath,
|
||||
ownerUserId: confirmed.assetObject.ownerUserId,
|
||||
ownerLabel: confirmed.assetObject.ownerUserId
|
||||
? `账号 ${confirmed.assetObject.ownerUserId}`
|
||||
: '当前账号',
|
||||
profileId: confirmed.assetObject.profileId,
|
||||
entityId: confirmed.assetObject.entityId,
|
||||
createdAt: confirmed.assetObject.createdAt ?? '',
|
||||
updatedAt: confirmed.assetObject.updatedAt ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
export const puzzleReferenceAssetTestUtils = {
|
||||
maxUploadBytes: PUZZLE_REFERENCE_IMAGE_MAX_UPLOAD_BYTES,
|
||||
validateFile: validatePuzzleReferenceImageFile,
|
||||
};
|
||||
|
||||
/**
|
||||
* 读取历史拼图图片素材。结果页只把它们作为参考图来源,
|
||||
* 不直接替换当前正式图,正式图仍由后端单图生成链路写回。
|
||||
@@ -34,4 +181,5 @@ export async function listPuzzleHistoryAssets(payload: { limit?: number }) {
|
||||
|
||||
export const puzzleAssetClient = {
|
||||
listHistoryAssets: listPuzzleHistoryAssets,
|
||||
uploadReferenceImage: uploadPuzzleReferenceImage,
|
||||
};
|
||||
|
||||
@@ -238,3 +238,18 @@ export async function cropPuzzleReferenceImageDataUrl({
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function puzzleReferenceImageDataUrlToFile(
|
||||
dataUrl: string,
|
||||
fileName = 'puzzle-reference.jpg',
|
||||
) {
|
||||
const [metadata = '', encoded = ''] = dataUrl.split(',', 2);
|
||||
const mimeType =
|
||||
metadata.match(/^data:([^;]+);base64$/u)?.[1] ?? 'image/jpeg';
|
||||
const binary = atob(encoded);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let index = 0; index < binary.length; index += 1) {
|
||||
bytes[index] = binary.charCodeAt(index);
|
||||
}
|
||||
return new File([bytes], fileName, { type: mimeType });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user