refactor: optimize Topbar scroll listener; sync WIP ecommerce refactor and CSS

This commit is contained in:
2026-06-16 21:09:41 +08:00
parent 5466036349
commit 588da45902
3 changed files with 561 additions and 575 deletions
+20 -26
View File
@@ -39,38 +39,32 @@ export function Topbar({
useEffect(() => { useEffect(() => {
let restoreTimer: number | undefined; let restoreTimer: number | undefined;
let cleanupScrollTarget: (() => void) | undefined;
const bindScrollTarget = () => { const handleScroll = (event: Event) => {
cleanupScrollTarget?.(); if (profileMenuOpen) return;
const target = document.querySelector<HTMLElement>( const target = event.target;
".ecommerce-standalone__page--workspace:not([hidden]) .clone-ai-preview", const activeWorkspace = document.querySelector<HTMLElement>(".ecommerce-standalone__page--workspace:not([hidden])");
); if (!activeWorkspace) return;
if (!target) { const isWorkspacePreviewScroll =
cleanupScrollTarget = undefined; target instanceof HTMLElement && target.classList.contains("clone-ai-preview") && activeWorkspace.contains(target);
return; const isPageScroll =
} target === document ||
target === document.scrollingElement ||
target === document.documentElement ||
target === document.body;
if (!isWorkspacePreviewScroll && !isPageScroll) return;
const handleScroll = () => { setIsTopbarHidden(true);
if (profileMenuOpen) return; if (restoreTimer) window.clearTimeout(restoreTimer);
setIsTopbarHidden(true); restoreTimer = window.setTimeout(() => {
if (restoreTimer) window.clearTimeout(restoreTimer); setIsTopbarHidden(false);
restoreTimer = window.setTimeout(() => { }, 240);
setIsTopbarHidden(false);
}, 240);
};
target.addEventListener("scroll", handleScroll, { passive: true });
cleanupScrollTarget = () => target.removeEventListener("scroll", handleScroll);
}; };
bindScrollTarget(); window.addEventListener("scroll", handleScroll, { capture: true, passive: true });
const observer = new MutationObserver(bindScrollTarget);
observer.observe(document.body, { attributes: true, childList: true, subtree: true });
return () => { return () => {
cleanupScrollTarget?.(); window.removeEventListener("scroll", handleScroll, { capture: true });
observer.disconnect();
if (restoreTimer) window.clearTimeout(restoreTimer); if (restoreTimer) window.clearTimeout(restoreTimer);
}; };
}, [profileMenuOpen]); }, [profileMenuOpen]);
+55 -547
View File
@@ -41,6 +41,35 @@ import EcommerceTryOnPanel from "./panels/EcommerceTryOnPanel";
import EcommerceClonePanel from "./panels/EcommerceClonePanel"; import EcommerceClonePanel from "./panels/EcommerceClonePanel";
import { ecommerceOssScopes, saveUnifiedEcommerceGenerationRecord, deleteEcommerceGenerationRecord } from "./ecommerceGenerationPersistence"; import { ecommerceOssScopes, saveUnifiedEcommerceGenerationRecord, deleteEcommerceGenerationRecord } from "./ecommerceGenerationPersistence";
import { downloadResultAsset } from "../workbench/workbenchDownload"; import { downloadResultAsset } from "../workbench/workbenchDownload";
import {
formatRatioDisplayValue,
getQuickSetRatioValue,
getRatioDisplayParts,
normalizeRatioForApi,
normalizeRatioToken,
parseRatioToAspectCss,
quickSetRatioOptions,
} from "./utils/ratioUtils";
import {
defaultCloneOutput,
defaultEcommercePlatform,
defaultProductSetOutput,
formatUploadedImageRatio,
getPlatformDefaultLanguage,
getPlatformDefaultRatio,
getPlatformLanguageOptions,
getPlatformRatioOptions,
getUniqueRatioOptions,
marketLanguageOptions,
marketOptions,
normalizeLanguageForPlatform,
normalizeMarket,
normalizePlatform,
normalizeRatioForPlatform,
platformOptions,
type CloneOutputKey,
type ProductSetOutputKey,
} from "./utils/platformRules";
const smartCutoutColorPresets = [ const smartCutoutColorPresets = [
"#ffffff", "#ffffff",
@@ -266,12 +295,11 @@ import {
interface ProductClonePageProps { interface ProductClonePageProps {
onWorkspaceChromeChange?: (state: { isToolPage: boolean }) => void;
[key: string]: unknown; [key: string]: unknown;
} }
type ProductCloneStatus = "idle" | "ready" | "generating" | "done" | "failed"; type ProductCloneStatus = "idle" | "ready" | "generating" | "done" | "failed";
type ProductSetOutputKey = "set" | "detail" | "model" | "video";
type CloneOutputKey = ProductSetOutputKey | "hot";
type CloneSetCountKey = "selling" | "white" | "scene"; type CloneSetCountKey = "selling" | "white" | "scene";
type CloneModelPanelTab = "scene" | "model"; type CloneModelPanelTab = "scene" | "model";
type CloneVideoQualityKey = "standard" | "high" | "ultra"; type CloneVideoQualityKey = "standard" | "high" | "ultra";
@@ -429,13 +457,6 @@ interface EcommerceImagePromptOptions {
detailModules?: string[]; detailModules?: string[];
} }
type PlatformRatioModeKey = ProductSetOutputKey | "hot";
interface PlatformRatioGroup {
ratios: string[];
defaultRatio: string;
}
const sideTools: Array<{ key: ProductKitToolKey; label: string; icon: ReactNode }> = [ const sideTools: Array<{ key: ProductKitToolKey; label: string; icon: ReactNode }> = [
{ key: "set", label: "商品套图", icon: <AppstoreOutlined /> }, { key: "set", label: "商品套图", icon: <AppstoreOutlined /> },
{ key: "detail", label: "A+详情", icon: <FileImageOutlined /> }, { key: "detail", label: "A+详情", icon: <FileImageOutlined /> },
@@ -443,324 +464,6 @@ const sideTools: Array<{ key: ProductKitToolKey; label: string; icon: ReactNode
{ key: "clone", label: "电商AI作图", icon: <AppstoreOutlined /> }, { key: "clone", label: "电商AI作图", icon: <AppstoreOutlined /> },
]; ];
const platformSpecOptions: Array<{
label: string;
ratios: string[];
defaultRatio: string;
ratioGroups?: Partial<Record<PlatformRatioModeKey, PlatformRatioGroup>>;
specs: string[];
tip?: string;
aliases?: string[];
}> = [
{
label: "淘宝/天猫",
ratios: ["淘宝主图 / SKU 图 800×800px", "详情页宽 750px", "详情页宽 790px"],
defaultRatio: "淘宝主图 / SKU 图 800×800px",
ratioGroups: {
set: {
ratios: ["1000×1000px\u00a0\u00a0\u00a01:1", "800×800px\u00a0\u00a0\u00a01:1"],
defaultRatio: "1000×1000px\u00a0\u00a0\u00a01:1",
},
detail: {
ratios: [
"750×1000px\u00a0\u00a0\u00a03:4",
"790×1053px\u00a0\u00a0\u00a03:4",
"750×1125px\u00a0\u00a0\u00a02:3",
"790×1185px\u00a0\u00a0\u00a02:3",
],
defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
},
model: {
ratios: ["750×1000px\u00a0\u00a0\u00a03:4"],
defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
},
video: {
ratios: ["1080×1920px\u00a0\u00a0\u00a09:16", "1080×1440px\u00a0\u00a0\u00a03:4", "1080×1080px\u00a0\u00a0\u00a01:1"],
defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
},
hot: {
ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
},
},
specs: ["主图 / SKU 图 800×800px,≤3MB", "详情页宽 750px 或 790px,单张高度≤1546px"],
tip: "建议主图 200-400KB JPG,超过 500KB 会影响加载速度。",
},
{
label: "京东",
ratios: ["京东主图 / SKU 图 800×800px", "详情页宽 750px", "首图主体占比 ≥80%"],
defaultRatio: "京东主图 / SKU 图 800×800px",
ratioGroups: {
set: {
ratios: ["1000×1000px\u00a0\u00a0\u00a01:1"],
defaultRatio: "1000×1000px\u00a0\u00a0\u00a01:1",
},
detail: {
ratios: [
"750×1000px\u00a0\u00a0\u00a03:4",
"990×1320px\u00a0\u00a0\u00a03:4",
"750×1125px\u00a0\u00a0\u00a02:3",
"990×1485px\u00a0\u00a0\u00a02:3",
],
defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
},
model: {
ratios: ["750×1125px\u00a0\u00a0\u00a02:3", "990×1485px\u00a0\u00a0\u00a02:3"],
defaultRatio: "750×1125px\u00a0\u00a0\u00a02:3",
},
video: {
ratios: ["1080×1920px\u00a0\u00a0\u00a09:16", "1920×1080px\u00a0\u00a0\u00a016:9"],
defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
},
hot: {
ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
},
},
specs: ["主图 / SKU 图 800×800px,白底,≤3MB", "详情页宽 750px,首图主体占比 ≥80%"],
},
{
label: "拼多多",
ratios: ["主图 750×352px", "主图 800×800px", "详情页宽 750px"],
defaultRatio: "主图 750×352px",
ratioGroups: {
set: {
ratios: ["800×800px\u00a0\u00a0\u00a01:1", "750×1000px\u00a0\u00a0\u00a03:4"],
defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
},
detail: {
ratios: ["750×1000px\u00a0\u00a0\u00a03:4", "750×1125px\u00a0\u00a0\u00a02:3"],
defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
},
model: {
ratios: ["750×1000px\u00a0\u00a0\u00a03:4"],
defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
},
video: {
ratios: ["1080×1920px\u00a0\u00a0\u00a09:16"],
defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
},
hot: {
ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
},
},
specs: ["主图 750×352px 或 800×800px,≤1MB", "详情页宽 750px,要求纯白底、无水印、无拼接"],
},
{
label: "抖音电商",
ratios: ["短视频1080×1920px"],
defaultRatio: "短视频1080×1920px",
ratioGroups: {
video: {
ratios: ["1080×1920px\u00a0\u00a0\u00a09:16"],
defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
},
hot: {
ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
},
},
specs: ["短视频 1080×1920px9:16", "30s 内最佳"],
},
{
label: "亚马逊 Amazon",
ratios: ["主图 ≥1600×1600px", "建议 2000×2000px+", "最小 500×500px"],
defaultRatio: "主图 ≥1600×1600px",
ratioGroups: {
set: {
ratios: ["1600×1600px\u00a0\u00a0\u00a01:1"],
defaultRatio: "1600×1600px\u00a0\u00a0\u00a01:1",
},
detail: {
ratios: ["1600×1600px\u00a0\u00a0\u00a01:1", "1200×1800px\u00a0\u00a0\u00a02:3", "1200×1600px\u00a0\u00a0\u00a03:4"],
defaultRatio: "1200×1800px\u00a0\u00a0\u00a02:3",
},
model: {
ratios: ["1200×1800px\u00a0\u00a0\u00a02:3"],
defaultRatio: "1200×1800px\u00a0\u00a0\u00a02:3",
},
video: {
ratios: ["1920×1080px\u00a0\u00a0\u00a016:9"],
defaultRatio: "1920×1080px\u00a0\u00a0\u00a016:9",
},
hot: {
ratios: ["1600×1600px\u00a0\u00a0\u00a01:1"],
defaultRatio: "1600×1600px\u00a0\u00a0\u00a01:1",
},
},
specs: ["主图 1600×1600px+,纯白底,≤10MB", "最小 500×500px,建议 2000px+ 以支持缩放"],
aliases: ["亚马逊"],
},
{
label: "Shopee",
ratios: ["商品主图 1024×1024px", "基础主图 800×800px"],
defaultRatio: "商品主图 1024×1024px",
ratioGroups: {
set: {
ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
},
detail: {
ratios: ["750×1000px\u00a0\u00a0\u00a03:4", "750×1125px\u00a0\u00a0\u00a02:3"],
defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
},
model: {
ratios: ["750×1000px\u00a0\u00a0\u00a03:4"],
defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
},
video: {
ratios: ["1080×1920px\u00a0\u00a0\u00a09:16"],
defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
},
hot: {
ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
},
},
specs: ["商品主图推荐 1024×1024px,基础 800×800px", "≤2MB,白底或浅色底"],
aliases: ["虾皮 Shopee/Lazada", "虾皮"],
},
{
label: "Lazada",
ratios: ["商品主图 800×800px"],
defaultRatio: "商品主图 800×800px",
ratioGroups: {
set: {
ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
},
detail: {
ratios: ["750×1000px\u00a0\u00a0\u00a03:4", "750×1125px\u00a0\u00a0\u00a02:3"],
defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
},
model: {
ratios: ["750×1000px\u00a0\u00a0\u00a03:4"],
defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4",
},
video: {
ratios: ["1080×1920px\u00a0\u00a0\u00a09:16"],
defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
},
hot: {
ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
},
},
specs: ["商品主图 800×800px1:1"],
},
{
label: "Instagram",
ratios: ["帖子 1080×1350px", "帖子 1080×1080px", "Stories / Reels 1080×1920px", "头像 320×320px"],
defaultRatio: "帖子 1080×1350px",
ratioGroups: {
set: {
ratios: ["1080×1080px\u00a0\u00a0\u00a01:1", "1080×1350px\u00a0\u00a0\u00a04:5"],
defaultRatio: "1080×1080px\u00a0\u00a0\u00a01:1",
},
detail: {
ratios: ["1080×1350px\u00a0\u00a0\u00a04:5"],
defaultRatio: "1080×1350px\u00a0\u00a0\u00a04:5",
},
model: {
ratios: ["1080×1350px\u00a0\u00a0\u00a04:5"],
defaultRatio: "1080×1350px\u00a0\u00a0\u00a04:5",
},
video: {
ratios: ["1080×1920px\u00a0\u00a0\u00a09:16", "1080×1350px\u00a0\u00a0\u00a04:5"],
defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
},
},
specs: ["帖子 1080×1350px 或 1080×1080px", "Stories / Reels 封面 1080×1920px,头像 320×320px"],
tip: "建议 ≤8MB JPG。",
aliases: ["Instagram Reels"],
},
{
label: "速卖通",
ratios: ["主图 800×800px", "主图 1000×1000px+"],
defaultRatio: "主图 800×800px",
ratioGroups: {
set: {
ratios: ["1000×1000px\u00a0\u00a0\u00a01:1"],
defaultRatio: "1000×1000px\u00a0\u00a0\u00a01:1",
},
detail: {
ratios: ["750×1125px\u00a0\u00a0\u00a02:3", "750×1000px\u00a0\u00a0\u00a03:4"],
defaultRatio: "750×1125px\u00a0\u00a0\u00a02:3",
},
model: {
ratios: ["750×1125px\u00a0\u00a0\u00a02:3"],
defaultRatio: "750×1125px\u00a0\u00a0\u00a02:3",
},
video: {
ratios: ["1080×1920px\u00a0\u00a0\u00a09:16", "1920×1080px\u00a0\u00a0\u00a016:9"],
defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
},
hot: {
ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
},
},
specs: ["主图建议 800×800px 或更高,1:1", "适合跨境电商主图、SKU 图和场景图"],
},
{
label: "eBay",
ratios: ["商品图1:1", "白底多角度展示图 1:1"],
defaultRatio: "商品图1:1",
ratioGroups: {
set: {
ratios: ["1600×1600px\u00a0\u00a0\u00a01:1"],
defaultRatio: "1600×1600px\u00a0\u00a0\u00a01:1",
},
detail: {
ratios: ["1000×1500px\u00a0\u00a0\u00a02:3", "1200×1600px\u00a0\u00a0\u00a03:4"],
defaultRatio: "1000×1500px\u00a0\u00a0\u00a02:3",
},
model: {
ratios: ["1000×1500px\u00a0\u00a0\u00a02:3"],
defaultRatio: "1000×1500px\u00a0\u00a0\u00a02:3",
},
video: {
ratios: ["1920×1080px\u00a0\u00a0\u00a016:9", "1080×1920px\u00a0\u00a0\u00a09:16"],
defaultRatio: "1920×1080px\u00a0\u00a0\u00a016:9",
},
hot: {
ratios: ["1600×1600px\u00a0\u00a0\u00a01:1"],
defaultRatio: "1600×1600px\u00a0\u00a0\u00a01:1",
},
},
specs: ["商品图建议 1:1,主体清晰居中", "适合白底主图和多角度展示图"],
},
{
label: "TikTok Shop",
ratios: ["商品主图 1:1", "短视频/ 竖版封面 9:16"],
defaultRatio: "商品主图 1:1",
ratioGroups: {
set: {
ratios: ["1280×1280px\u00a0\u00a0\u00a01:1"],
defaultRatio: "1280×1280px\u00a0\u00a0\u00a01:1",
},
detail: {
ratios: ["1080×1350px\u00a0\u00a0\u00a04:5"],
defaultRatio: "1080×1350px\u00a0\u00a0\u00a04:5",
},
model: {
ratios: ["1080×1350px\u00a0\u00a0\u00a04:5"],
defaultRatio: "1080×1350px\u00a0\u00a0\u00a04:5",
},
video: {
ratios: ["1080×1920px\u00a0\u00a0\u00a09:16"],
defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16",
},
hot: {
ratios: ["800×800px\u00a0\u00a0\u00a01:1"],
defaultRatio: "800×800px\u00a0\u00a0\u00a01:1",
},
},
specs: ["商品主图建议 1:1", "短视频竖版封面建议 9:16"],
},
];
const platformOptions = platformSpecOptions.map((option) => option.label);
const getPlatformLogoText = (value: string) => { const getPlatformLogoText = (value: string) => {
const normalized = value.toLowerCase(); const normalized = value.toLowerCase();
if (value.includes("淘宝") || value.includes("天猫")) return "淘"; if (value.includes("淘宝") || value.includes("天猫")) return "淘";
@@ -811,223 +514,6 @@ const renderPlatformLogo = (value: string) => {
</span> </span>
); );
}; };
const marketLanguageOptions: Array<{ country: string; languages: string[] }> = [
{ country: "中国", languages: ["中文"] },
{ country: "美国", languages: ["英文"] },
{ country: "加拿大", languages: ["英文", "法文"] },
{ country: "英国", languages: ["英文"] },
{ country: "德国", languages: ["德文"] },
{ country: "法国", languages: ["法文"] },
{ country: "意大利", languages: ["意大利语"] },
{ country: "西班牙", languages: ["西班牙语"] },
{ country: "日本", languages: ["日文"] },
{ country: "韩国", languages: ["韩文"] },
{ country: "澳大利亚", languages: ["英文"] },
{ country: "新加坡", languages: ["英文", "中文"] },
{ country: "马来西亚", languages: ["马来语", "英文", "中文"] },
{ country: "印尼", languages: ["印度尼西亚语", "英文"] },
{ country: "越南", languages: ["越南语", "英文"] },
{ country: "泰国", languages: ["泰语", "英文"] },
{ country: "菲律宾", languages: ["菲律宾语(他加禄语)", "英文"] },
{ country: "巴西", languages: ["葡萄牙语"] },
{ country: "墨西哥", languages: ["西班牙语"] },
{ country: "智利", languages: ["西班牙语"] },
{ country: "哥伦比亚", languages: ["西班牙语"] },
{ country: "阿联酋", languages: ["阿拉伯语", "英文"] },
{ country: "沙特阿拉伯", languages: ["阿拉伯语", "英文"] },
{ country: "俄罗斯", languages: ["俄语"] },
{ country: "波兰", languages: ["波兰语"] },
];
const marketOptions = marketLanguageOptions.map((option) => option.country);
const languageOptions = Array.from(new Set(marketLanguageOptions.flatMap((option) => option.languages)));
const languageAliases: Record<string, string> = {
"英文": "英文",
"中文": "中文",
"英语": "英文",
"日语": "日文",
"日文": "日文",
"德语": "德文",
"德文": "德文",
"法语": "法文",
"法文": "法文",
"韩语": "韩文",
"韩文": "韩文",
"西文": "西班牙语",
"西班牙语": "西班牙语",
"葡文": "葡萄牙语",
"葡萄牙语": "葡萄牙语",
"印尼语": "印度尼西亚语",
"印度尼西亚语": "印度尼西亚语",
"菲律宾语": "菲律宾语(他加禄语)",
"菲律宾语(他加禄语)": "菲律宾语(他加禄语)",
};
const defaultPlatformSpec = platformSpecOptions[0]!;
const getPlatformSpec = (value: string) =>
platformSpecOptions.find((option) => option.label === value || option.aliases?.includes(value)) ?? defaultPlatformSpec;
const legacyPlatformAliases: Record<string, string> = {
"淘宝/天猫": "淘宝/天猫",
"京东": "京东",
"拼多多": "拼多多",
"抖音电商": "抖音电商",
"亚马逊Amazon": "亚马逊 Amazon",
"速卖通": "速卖通",
};
const normalizePlatform = (value: string) => getPlatformSpec(legacyPlatformAliases[value] ?? value).label;
const domesticPlatformLabels = new Set(["淘宝/天猫", "京东", "拼多多", "抖音电商"]);
const domesticPlatformLanguages = ["中文"];
const isDomesticPlatform = (platformValue: string) => domesticPlatformLabels.has(normalizePlatform(platformValue));
const getPlatformRatioGroup = (value: string, mode?: PlatformRatioModeKey): PlatformRatioGroup => {
const platformSpec = getPlatformSpec(value);
return (mode ? platformSpec.ratioGroups?.[mode] : null) ?? {
ratios: platformSpec.ratios,
defaultRatio: platformSpec.defaultRatio,
};
};
const getPlatformRatioOptions = (value: string, mode?: PlatformRatioModeKey) => getPlatformRatioGroup(value, mode).ratios;
const getPlatformDefaultRatio = (value: string, mode?: PlatformRatioModeKey) => getPlatformRatioGroup(value, mode).defaultRatio;
const getUniqueRatioOptions = (ratios: string[]) => Array.from(new Set(ratios));
const normalizeRatioToken = (value: string) =>
value
.replaceAll("\u00a0", " ")
.replaceAll("脳", "×")
.replaceAll("*", "×")
.replaceAll("", ":")
.replace(/锛\?/g, ":")
.replace(/\s+/g, " ")
.trim();
const normalizeRatioForPlatform = (platformValue: string, ratioValue: string, mode?: PlatformRatioModeKey) => {
const platformRatios = getPlatformRatioOptions(platformValue, mode);
if (platformRatios.includes(ratioValue)) return ratioValue;
const normalizedRatio = normalizeRatioToken(ratioValue);
const matchedRatio = platformRatios.find((option) => normalizeRatioToken(option).includes(normalizedRatio));
return matchedRatio ?? getPlatformDefaultRatio(platformValue, mode);
};
const quickSetRatioOptions = ["1:1", "3:4", "4:3", "9:16", "16:9"];
const getQuickSetRatioValue = (value: string) => {
const normalizedValue = normalizeRatioToken(value);
if (quickSetRatioOptions.includes(normalizedValue)) return normalizedValue;
const sizeMatch = normalizedValue.match(/(\d+)\s*[×xX]\s*(\d+)/u);
if (sizeMatch) {
const width = Number(sizeMatch[1]);
const height = Number(sizeMatch[2]);
if (Number.isFinite(width) && Number.isFinite(height) && width > 0 && height > 0) {
const aspect = formatAspectRatio(width, height);
if (quickSetRatioOptions.includes(aspect)) return aspect;
}
}
const ratioMatch = normalizedValue.match(/(\d+)\s*[:]\s*(\d+)/u);
if (ratioMatch) {
const aspect = `${Number(ratioMatch[1])}:${Number(ratioMatch[2])}`;
if (quickSetRatioOptions.includes(aspect)) return aspect;
}
return quickSetRatioOptions[0]!;
};
const formatRatioDisplayValue = (value: string) => {
const normalizedValue = normalizeRatioToken(value);
const sizeMatch = normalizedValue.match(/(\d+)\s*[×xX]\s*(\d+)\s*px?/u);
if (sizeMatch) {
const width = Number(sizeMatch[1]);
const height = Number(sizeMatch[2]);
return `${width}×${height}px\u00a0\u00a0\u00a0${formatAspectRatio(width, height)}`;
}
return normalizedValue
.replace("淘宝主图 / SKU 图 ", "淘宝主图 / SKU 图 ")
.replace("京东主图 / SKU 图 ", "京东主图 / SKU 图 ")
.replace("详情页宽", "详情页宽")
.replace("短视频", "短视频")
.replace("主图", "主图")
.replace("商品主图", "商品主图")
.replace("鍟嗗搧鍥?", "商品图")
.replace(/\s+:/g, ":")
.replace(/:\s+/g, ":");
};
const getRatioDisplayParts = (value: string) => {
const display = formatRatioDisplayValue(value).replace(/\u00a0/g, " ").replace(/\s+/g, " ").trim();
const aspectMatch = display.match(/(\d+\s*[:]\s*\d+)(?!.*\d+\s*[:]\s*\d+)/u);
const aspect = aspectMatch?.[1]?.replace(/\s+/g, "") ?? "自适应";
const size = aspectMatch ? display.replace(aspectMatch[0], "").trim() : display;
return {
size: size || "原图比例",
aspect,
};
};
/** Extract CSS aspect-ratio from a ratio string like "1000x1000px 1:1" -> "1 / 1" */
const parseRatioToAspectCss = (ratioStr: string): string => {
const match = ratioStr.match(/(\d+)\D+(\d+)/u);
if (!match) return "1 / 1";
return `${match[1]} / ${match[2]}`;
};
const supportedImageApiRatios = ["1:1", "3:4", "4:3", "9:16", "16:9"] as const;
type SupportedImageApiRatio = typeof supportedImageApiRatios[number];
const toSupportedImageApiRatio = (width: number, height: number): SupportedImageApiRatio => {
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return "1:1";
let bestRatio: SupportedImageApiRatio = "1:1";
let bestScore = Number.POSITIVE_INFINITY;
const target = Math.log(width / height);
for (const ratio of supportedImageApiRatios) {
const [left, right] = ratio.split(":").map(Number);
const score = Math.abs(target - Math.log(left / right));
if (score < bestScore) {
bestRatio = ratio;
bestScore = score;
}
}
return bestRatio;
};
/** Normalize ratio display string ("1000×1000px 1:1") to an image API aspect ratio ("1:1"). */
const normalizeRatioForApi = (ratioStr: string): string => {
const normalizedValue = normalizeRatioToken(ratioStr);
const explicitRatios = Array.from(normalizedValue.matchAll(/(\d+(?:\.\d+)?)\s*:\s*(\d+(?:\.\d+)?)/g));
const explicitRatio = explicitRatios.at(-1);
if (explicitRatio) {
return toSupportedImageApiRatio(Number(explicitRatio[1]), Number(explicitRatio[2]));
}
const sizeMatch = normalizedValue.match(/(\d+(?:\.\d+)?)\s*[×xX*]\s*(\d+(?:\.\d+)?)/u);
if (!sizeMatch) return "1:1";
return toSupportedImageApiRatio(Number(sizeMatch[1]), Number(sizeMatch[2]));
};
const greatestCommonDivisor = (left: number, right: number): number => {
let a = Math.abs(left);
let b = Math.abs(right);
while (b) {
[a, b] = [b, a % b];
}
return a || 1;
};
const formatAspectRatio = (width: number, height: number) => {
const divisor = greatestCommonDivisor(width, height);
return `${Math.round(width / divisor)}:${Math.round(height / divisor)}`;
};
const formatUploadedImageRatio = (image?: CloneImageItem) => {
if (!image) return null;
const format = image.format ? `\u00a0\u00a0\u00a0${image.format}` : "";
if (!image.width || !image.height) return `上传图片\u00a0\u00a0\u00a0原图比例${format}`;
return `上传图片 ${image.width}×${image.height}px\u00a0\u00a0\u00a0${formatAspectRatio(image.width, image.height)}${format}`;
};
const defaultMarketLanguageOption = marketLanguageOptions[0]!;
const normalizeMarket = (value: string) =>
marketLanguageOptions.some((option) => option.country === value) ? value : defaultMarketLanguageOption.country;
const normalizeLanguage = (value: string) => languageAliases[value] ?? value;
const uniqueLanguages = (languages: string[]) => Array.from(new Set(languages));
const appendEnglish = (languages: string[]) => Array.from(new Set([...languages, "英文"]));
const getMarketLanguageOptions = (marketValue: string) =>
appendEnglish((marketLanguageOptions.find((option) => option.country === marketValue) ?? defaultMarketLanguageOption).languages);
const getPlatformLanguageOptions = (platformValue: string, marketValue: string) => {
const marketLanguages = getMarketLanguageOptions(marketValue);
if (!isDomesticPlatform(platformValue)) return marketLanguages;
const localLanguages = marketLanguages.filter((item) => item !== "英文");
return uniqueLanguages([...localLanguages, ...domesticPlatformLanguages, "英文"]);
};
const getPlatformDefaultLanguage = (platformValue: string, marketValue: string) =>
isDomesticPlatform(platformValue) ? "中文" : (getPlatformLanguageOptions(platformValue, marketValue)[0] ?? languageOptions[0] ?? "英文");
const normalizeLanguageForPlatform = (platformValue: string, marketValue: string, languageValue: string) => {
const normalizedLanguage = normalizeLanguage(languageValue);
const platformLanguages = getPlatformLanguageOptions(platformValue, marketValue);
return platformLanguages.includes(normalizedLanguage) ? normalizedLanguage : getPlatformDefaultLanguage(platformValue, marketValue);
};
const productSetOutputOptions: Array<{ key: ProductSetOutputKey; label: string; desc: string; icon: ReactNode }> = [ const productSetOutputOptions: Array<{ key: ProductSetOutputKey; label: string; desc: string; icon: ReactNode }> = [
{ key: "set", label: "套图", desc: "主图/卖点/场景", icon: <AppstoreOutlined /> }, { key: "set", label: "套图", desc: "主图/卖点/场景", icon: <AppstoreOutlined /> },
{ key: "detail", label: "详情图", desc: "长图模块化生成", icon: <LayoutOutlined /> }, { key: "detail", label: "详情图", desc: "长图模块化生成", icon: <LayoutOutlined /> },
@@ -1164,9 +650,6 @@ const maxCloneProductImages = 20;
const maxCloneReferenceImages = 20; const maxCloneReferenceImages = 20;
const cloneVideoDurationMin = 5; const cloneVideoDurationMin = 5;
const cloneVideoDurationMax = 45; const cloneVideoDurationMax = 45;
const defaultEcommercePlatform = "淘宝/天猫";
const defaultProductSetOutput: ProductSetOutputKey = "set";
const defaultCloneOutput: CloneOutputKey = "set";
const cloneLatestSettingStorageKey = "omniai.clone-ai.latest-setting"; const cloneLatestSettingStorageKey = "omniai.clone-ai.latest-setting";
const ecommerceHistoryStorageKey = "omniai.ecommerce.history.records"; const ecommerceHistoryStorageKey = "omniai.ecommerce.history.records";
const cloneVideoQualityOptions: Array<{ key: CloneVideoQualityKey; label: string; desc: string }> = [ const cloneVideoQualityOptions: Array<{ key: CloneVideoQualityKey; label: string; desc: string }> = [
@@ -1594,6 +1077,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const countHoldTimeoutRef = useRef<number | null>(null); const countHoldTimeoutRef = useRef<number | null>(null);
const countHoldIntervalRef = useRef<number | null>(null); const countHoldIntervalRef = useRef<number | null>(null);
const isAuthenticated = Boolean((_props as Record<string, unknown>).isAuthenticated); const isAuthenticated = Boolean((_props as Record<string, unknown>).isAuthenticated);
const onWorkspaceChromeChange = _props.onWorkspaceChromeChange;
const requestLogin = () => { const requestLogin = () => {
const handler = (_props as Record<string, unknown>).onRequireLogin; const handler = (_props as Record<string, unknown>).onRequireLogin;
if (typeof handler === "function") handler(); if (typeof handler === "function") handler();
@@ -1625,6 +1109,16 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const [showHostingModal, setShowHostingModal] = useState(false); const [showHostingModal, setShowHostingModal] = useState(false);
const [productImages, setProductImages] = useState<CloneImageItem[]>([]); const [productImages, setProductImages] = useState<CloneImageItem[]>([]);
const [activeQuickTool, setActiveQuickTool] = useState<"cutout" | "detail" | "watermark" | "image-edit" | "translate" | "hot" | null>(null); const [activeQuickTool, setActiveQuickTool] = useState<"cutout" | "detail" | "watermark" | "image-edit" | "translate" | "hot" | null>(null);
useEffect(() => {
const handleWorkspaceHome = () => {
setActiveTool("clone");
setActiveQuickTool(null);
setActiveHistoryRecordId(null);
};
window.addEventListener("ecommerce-workspace-home", handleWorkspaceHome);
return () => window.removeEventListener("ecommerce-workspace-home", handleWorkspaceHome);
}, []);
const [smartCutoutImage, setSmartCutoutImage] = useState<SmartCutoutImageItem | null>(null); const [smartCutoutImage, setSmartCutoutImage] = useState<SmartCutoutImageItem | null>(null);
const [smartCutoutBatchImages, setSmartCutoutBatchImages] = useState<SmartCutoutImageItem[]>([]); const [smartCutoutBatchImages, setSmartCutoutBatchImages] = useState<SmartCutoutImageItem[]>([]);
const [smartCutoutBackgroundColor, setSmartCutoutBackgroundColor] = useState("#ffffff"); const [smartCutoutBackgroundColor, setSmartCutoutBackgroundColor] = useState("#ffffff");
@@ -7716,6 +7210,20 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const isRecordDetailWorkspace = isMainCloneWorkspace && Boolean(activeHistoryRecordId); const isRecordDetailWorkspace = isMainCloneWorkspace && Boolean(activeHistoryRecordId);
const currentResultCount = canvasNodes.reduce((count, node) => count + node.results.length, 0); const currentResultCount = canvasNodes.reduce((count, node) => count + node.results.length, 0);
const activeHistoryRecord = activeHistoryRecordId ? ecommerceHistoryRecords.find((record) => record.id === activeHistoryRecordId) : null; const activeHistoryRecord = activeHistoryRecordId ? ecommerceHistoryRecords.find((record) => record.id === activeHistoryRecordId) : null;
const isFocusedToolPage =
isRecordDetailWorkspace || isSmartCutoutTool || isQuickDetailTool || isWatermarkTool || isTranslateTool || isImageEditTool || isHotCloneTool;
useEffect(() => {
onWorkspaceChromeChange?.({
isToolPage: isFocusedToolPage,
});
return () => {
onWorkspaceChromeChange?.({
isToolPage: false,
});
};
}, [isFocusedToolPage, onWorkspaceChromeChange]);
const activeConversationTurns = activeHistoryRecord const activeConversationTurns = activeHistoryRecord
? activeHistoryRecord.turns?.length ? activeHistoryRecord.turns?.length
? activeHistoryRecord.turns ? activeHistoryRecord.turns
+486 -2
View File
@@ -17418,21 +17418,24 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__topbar { html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__topbar {
border-bottom: none !important; border-bottom: none !important;
background: transparent !important; background: transparent !important;
background-color: transparent !important;
background-image: none !important;
box-shadow: none !important; box-shadow: none !important;
backdrop-filter: none !important; backdrop-filter: none !important;
-webkit-backdrop-filter: none !important; -webkit-backdrop-filter: none !important;
} }
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__topbar::before,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__topbar::after { html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__topbar::after {
content: none !important; content: none !important;
display: none !important;
} }
/* Keep topbar transparent and remove any background/border from inner controls. */ /* Keep topbar transparent and remove any background/border from inner controls. */
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__brand, html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__brand,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__brand strong, html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__brand strong,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__credits, html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__credits,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__account button, html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__account button:not(.ecommerce-standalone__login-button):not(.ecommerce-profile-menu__trigger) {
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-profile-menu__trigger {
color: #10202c !important; color: #10202c !important;
background: transparent !important; background: transparent !important;
background-color: transparent !important; background-color: transparent !important;
@@ -17444,6 +17447,487 @@ html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecomm
-webkit-backdrop-filter: none !important; -webkit-backdrop-filter: none !important;
} }
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__brand:hover,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__account button:not(.ecommerce-standalone__login-button):not(.ecommerce-profile-menu__trigger):hover {
background: transparent !important;
background-color: transparent !important;
background-image: none !important;
box-shadow: none !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__credits { html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__credits {
color: #3a5a6a !important; color: #3a5a6a !important;
} }
/* Topbar composition: quiet brand chip + single account capsule. */
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__topbar {
padding: 14px clamp(18px, 2.5vw, 30px) !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] {
--ecommerce-workspace-top-offset: 50px;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__brand {
gap: 10px !important;
min-height: 42px !important;
padding: 5px 12px 5px 6px !important;
border: 1px solid rgba(30, 189, 219, 0.16) !important;
border-radius: 999px !important;
background: rgba(255, 255, 255, 0.62) !important;
box-shadow: 0 12px 34px rgba(16, 115, 204, 0.07) !important;
backdrop-filter: blur(18px) saturate(1.08) !important;
-webkit-backdrop-filter: blur(18px) saturate(1.08) !important;
transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease, background 180ms ease !important;
will-change: opacity, transform, filter !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__brand:hover {
border-color: rgba(30, 189, 219, 0.28) !important;
background: rgba(255, 255, 255, 0.78) !important;
box-shadow: 0 16px 42px rgba(16, 115, 204, 0.1) !important;
transform: translateY(-1px);
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__logo {
width: 34px !important;
height: 34px !important;
border-radius: 12px !important;
box-shadow: 0 8px 18px rgba(16, 115, 204, 0.16) !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__brand strong {
max-width: 168px !important;
overflow: hidden !important;
color: #10202c !important;
font-size: 13px !important;
font-weight: 800 !important;
line-height: 1 !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-profile-menu {
position: relative !important;
gap: 0 !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-profile-menu__trigger {
gap: 10px !important;
min-height: 46px !important;
padding: 4px 6px 4px 14px !important;
border: 1px solid rgba(30, 189, 219, 0.16) !important;
border-radius: 999px !important;
background: rgba(255, 255, 255, 0.66) !important;
box-shadow: 0 12px 34px rgba(16, 115, 204, 0.08) !important;
backdrop-filter: blur(18px) saturate(1.08) !important;
-webkit-backdrop-filter: blur(18px) saturate(1.08) !important;
transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease, background 180ms ease !important;
will-change: opacity, transform, filter !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__topbar[data-scroll-hidden="true"] .ecommerce-standalone__brand,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__topbar[data-scroll-hidden="true"] .ecommerce-profile-menu__trigger,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__topbar[data-scroll-hidden="true"] .ecommerce-standalone__login-button {
opacity: 0 !important;
pointer-events: none !important;
filter: blur(4px) saturate(0.96) !important;
transform: translateY(-14px) scale(0.96) !important;
transition:
opacity 160ms ease,
transform 190ms cubic-bezier(0.4, 0, 1, 1),
filter 160ms ease !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__topbar[data-scroll-hidden="false"] .ecommerce-standalone__brand,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__topbar[data-scroll-hidden="false"] .ecommerce-profile-menu__trigger,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__topbar[data-scroll-hidden="false"] .ecommerce-standalone__login-button {
opacity: 1 !important;
filter: blur(0) saturate(1) !important;
transform: translateY(0) scale(1) !important;
transition:
opacity 260ms ease 40ms,
transform 360ms cubic-bezier(0.16, 1, 0.3, 1) 40ms,
filter 260ms ease 40ms,
border-color 180ms ease,
box-shadow 180ms ease,
background 180ms ease !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-profile-menu__trigger:hover,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-profile-menu__trigger[aria-expanded="true"] {
border-color: rgba(30, 189, 219, 0.3) !important;
background: rgba(255, 255, 255, 0.82) !important;
box-shadow: 0 16px 42px rgba(16, 115, 204, 0.12) !important;
transform: translateY(-1px);
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-profile-menu__trigger .ecommerce-standalone__credits {
min-height: auto !important;
padding: 0 !important;
border: 0 !important;
border-radius: 0 !important;
color: #3a5a6a !important;
background: transparent !important;
box-shadow: none !important;
font-size: 12px !important;
font-weight: 800 !important;
letter-spacing: 0 !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-profile-menu__name {
max-width: 82px !important;
overflow: hidden !important;
color: #10202c !important;
font-size: 13px !important;
font-weight: 800 !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-profile-menu__trigger .local-user-avatar--sm {
width: 34px !important;
height: 34px !important;
border: 2px solid rgba(255, 255, 255, 0.86) !important;
border-radius: 999px !important;
box-shadow:
0 0 0 1px rgba(30, 189, 219, 0.22),
0 8px 18px rgba(16, 115, 204, 0.16) !important;
}
@media (max-width: 720px) {
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__brand strong,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-profile-menu__name {
display: none !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-profile-menu__trigger {
padding-left: 12px !important;
}
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__account button.ecommerce-standalone__login-button {
pointer-events: auto !important;
min-height: 40px !important;
padding: 0 16px !important;
border: 1px solid rgba(30, 189, 219, 0.22) !important;
border-radius: 999px !important;
color: #10202c !important;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.88), rgba(241, 250, 252, 0.82)) !important;
box-shadow:
0 10px 26px rgba(16, 115, 204, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.92) !important;
font-weight: 700 !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__account button.ecommerce-standalone__login-button:hover {
border-color: rgba(30, 189, 219, 0.38) !important;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(232, 250, 253, 0.9)) !important;
box-shadow:
0 14px 32px rgba(30, 189, 219, 0.14),
inset 0 1px 0 rgba(255, 255, 255, 0.96) !important;
transform: translateY(-1px);
}
/* Let the workspace surface paint behind the transparent fixed topbar. */
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__content {
box-sizing: border-box !important;
height: 100dvh !important;
padding-top: 0 !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--profile {
box-sizing: border-box !important;
height: 100dvh !important;
padding-top: var(--ecommerce-topbar-height) !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-shell,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-preview.clone-ai-preview {
height: 100% !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-preview.clone-ai-preview:has(.ecom-inspiration-lab) {
padding-top: calc(var(--ecommerce-topbar-height) + var(--ecommerce-workspace-top-offset, 50px) + clamp(18px, 2.5vh, 30px)) !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"].is-history-collapsed .ecom-command-history__toggle.ecom-command-history__toggle {
top: calc(var(--ecommerce-topbar-height) + 12px) !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"].is-history-collapsed .ecom-command-history__toggle.ecom-command-history__toggle:hover {
transform: translateY(-2px) scale(1.035) !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-history.ecom-command-history {
top: calc(var(--ecommerce-topbar-height) + 18px) !important;
right: 18px !important;
bottom: auto !important;
height: calc(100dvh - var(--ecommerce-topbar-height) - 42px) !important;
width: min(316px, calc(100vw - 72px)) !important;
overflow: hidden !important;
border: 1px solid rgba(30, 189, 219, 0.2) !important;
border-radius: 24px !important;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.88), rgba(238, 250, 253, 0.82)) !important;
box-shadow:
0 24px 68px rgba(16, 115, 204, 0.16),
inset 0 1px 0 rgba(255, 255, 255, 0.9) !important;
backdrop-filter: blur(22px) saturate(1.12) !important;
-webkit-backdrop-filter: blur(22px) saturate(1.12) !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"].is-history-collapsed .ecom-command-history.ecom-command-history {
top: calc(var(--ecommerce-topbar-height) + 12px) !important;
right: 18px !important;
bottom: auto !important;
width: 42px !important;
height: 42px !important;
overflow: visible !important;
border: 0 !important;
border-radius: 15px !important;
background: transparent !important;
box-shadow: none !important;
transform: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-history__tools {
padding: 14px 14px 10px !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-history__heading {
padding-inline: 16px !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-history__list {
padding: 10px 12px 16px !important;
overflow: hidden auto !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"].is-history-collapsed .ecom-command-history__toggle.ecom-command-history__toggle {
position: relative !important;
top: auto !important;
right: auto !important;
left: auto !important;
z-index: 130 !important;
width: 42px !important;
height: 42px !important;
min-height: 42px !important;
transform: none !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"].is-history-collapsed .ecom-command-history__tools {
display: block !important;
width: 42px !important;
height: 42px !important;
padding: 0 !important;
}
/* History overlay final behavior: panel floats above the workspace and never reserves layout space. */
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] {
--ecom-history-offset: 0px !important;
--ecom-history-panel-width: 0px !important;
--history-detail-workspace-width: 100vw !important;
padding-right: 0 !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] > .product-clone-shell.product-clone-shell {
width: 100% !important;
max-width: none !important;
padding-right: 0 !important;
filter: none !important;
opacity: 1 !important;
transform: none !important;
pointer-events: auto !important;
user-select: auto !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] .product-clone-preview.clone-ai-preview {
width: 100% !important;
max-width: none !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"].is-history-detail .ecom-command-composer-wrap.has-generated.is-compact {
left: 50vw !important;
width: min(760px, calc(100vw - clamp(48px, 8vw, 96px))) !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"].is-history-detail .clone-ai-canvas-node:not(.is-generating) {
max-width: min(860px, calc(100vw - 80px)) !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-history__backdrop {
background: rgba(16, 38, 56, 0.08) !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
animation: ecommerce-soft-scrim-in 180ms ease-out both !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-history.ecom-command-history {
position: fixed !important;
z-index: 130 !important;
opacity: 1 !important;
transform: translateX(0) scale(1) !important;
transform-origin: top right !important;
transition:
opacity 240ms ease,
top 360ms cubic-bezier(0.22, 0.61, 0.36, 1),
width 360ms cubic-bezier(0.22, 0.61, 0.36, 1),
height 360ms cubic-bezier(0.22, 0.61, 0.36, 1),
border-radius 360ms cubic-bezier(0.22, 0.61, 0.36, 1),
border-color 220ms ease,
background 220ms ease,
transform 360ms cubic-bezier(0.22, 0.61, 0.36, 1),
box-shadow 260ms ease !important;
will-change: opacity, transform !important;
animation: none !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"].is-history-collapsed .ecom-command-history.ecom-command-history {
opacity: 1 !important;
transform: translateX(0) scale(1) !important;
animation: none !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-history__tools,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-history__new,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-history__refresh,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-history__heading,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-history__refresh-note,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-history__list {
transition:
opacity 220ms ease,
transform 360ms cubic-bezier(0.34, 0, 0.22, 1),
visibility 0s linear 220ms !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"]:not(.is-history-collapsed) .ecom-command-history__tools,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"]:not(.is-history-collapsed) .ecom-command-history__new,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"]:not(.is-history-collapsed) .ecom-command-history__refresh,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"]:not(.is-history-collapsed) .ecom-command-history__heading,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"]:not(.is-history-collapsed) .ecom-command-history__refresh-note,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"]:not(.is-history-collapsed) .ecom-command-history__list {
opacity: 1 !important;
visibility: visible !important;
transform: translateY(0) !important;
transition:
opacity 180ms ease 90ms,
transform 280ms cubic-bezier(0.22, 0.61, 0.36, 1) 70ms,
visibility 0s linear 0s !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"].is-history-collapsed .ecom-command-history__new,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"].is-history-collapsed .ecom-command-history__refresh,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"].is-history-collapsed .ecom-command-history__heading,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"].is-history-collapsed .ecom-command-history__refresh-note,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"][data-tool="clone"].is-history-collapsed .ecom-command-history__list {
opacity: 0 !important;
visibility: hidden !important;
transform: translateY(6px) !important;
transition:
opacity 160ms ease,
transform 220ms ease,
visibility 0s linear 160ms !important;
}
@keyframes ecom-history-panel-enter {
from {
opacity: 0;
transform: translateX(26px) scale(0.975);
}
to {
opacity: 1;
transform: translateX(0) scale(1);
}
}
/* Focused tool page sample: the inner tool header owns navigation, so the global brand steps away. */
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__brand {
opacity: 0 !important;
pointer-events: none !important;
filter: blur(4px) saturate(0.96) !important;
transform: translateY(-10px) scale(0.96) !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__topbar[data-scroll-hidden] .ecommerce-standalone__brand {
opacity: 0 !important;
pointer-events: none !important;
filter: blur(4px) saturate(0.96) !important;
transform: translateY(-10px) scale(0.96) !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__topbar {
padding-inline: clamp(16px, 2vw, 24px) !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"].is-quick-set-page .ecom-quick-set-page,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"].is-hot-clone-page .ecom-quick-set-page,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"].is-image-workbench-page .ecom-image-workbench-page,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"].is-watermark-page .ecom-watermark-page,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__page--workspace .product-clone-page[data-tool="clone"].is-translate-page .ecom-translate-page {
padding-top: 14px !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__page--workspace .ecom-quick-set-panel,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__page--workspace .ecom-image-workbench-side,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__page--workspace .ecom-watermark-side,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__page--workspace .ecom-translate-side {
border-radius: 18px !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__page--workspace .ecom-quick-set-panel-head,
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__page--workspace .ecom-image-workbench-panel-head {
display: grid !important;
grid-template-columns: minmax(0, 1fr) auto auto !important;
align-items: center !important;
gap: 8px !important;
min-height: 48px !important;
margin: -18px -16px 10px !important;
padding: 12px 12px 10px 16px !important;
border-bottom: 1px solid rgba(16, 115, 204, 0.08) !important;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(248, 252, 254, 0.9)) !important;
box-shadow: 0 10px 28px rgba(16, 115, 204, 0.05) !important;
backdrop-filter: blur(18px) saturate(1.08) !important;
-webkit-backdrop-filter: blur(18px) saturate(1.08) !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__page--workspace .ecom-quick-set-page-title {
min-width: 0 !important;
margin: 0 !important;
overflow: hidden !important;
color: #10202c !important;
font-size: 18px !important;
font-weight: 950 !important;
line-height: 1.1 !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__page--workspace .ecom-quick-set-back {
min-width: 58px !important;
min-height: 32px !important;
padding: 0 12px !important;
border: 1px solid rgba(16, 115, 204, 0.12) !important;
border-radius: 999px !important;
color: #526474 !important;
background: rgba(255, 255, 255, 0.78) !important;
box-shadow: none !important;
font-size: 13px !important;
font-weight: 850 !important;
letter-spacing: 0 !important;
}
html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"][data-workspace-tool-page="true"] .ecommerce-standalone__page--workspace .ecom-quick-set-back:hover {
border-color: rgba(30, 189, 219, 0.34) !important;
color: #1073cc !important;
background: rgba(232, 249, 253, 0.92) !important;
transform: translateY(-1px) !important;
}