From 588da459023754bb3860b0bd94574147d98188a0 Mon Sep 17 00:00:00 2001 From: Stringadmin Date: Tue, 16 Jun 2026 21:09:41 +0800 Subject: [PATCH] refactor: optimize Topbar scroll listener; sync WIP ecommerce refactor and CSS --- src/components/Topbar.tsx | 46 +- src/features/ecommerce/EcommercePage.tsx | 602 +++-------------------- src/styles/ecommerce-standalone.css | 488 +++++++++++++++++- 3 files changed, 561 insertions(+), 575 deletions(-) diff --git a/src/components/Topbar.tsx b/src/components/Topbar.tsx index 44734b3..9bba4af 100644 --- a/src/components/Topbar.tsx +++ b/src/components/Topbar.tsx @@ -39,38 +39,32 @@ export function Topbar({ useEffect(() => { let restoreTimer: number | undefined; - let cleanupScrollTarget: (() => void) | undefined; - const bindScrollTarget = () => { - cleanupScrollTarget?.(); - const target = document.querySelector( - ".ecommerce-standalone__page--workspace:not([hidden]) .clone-ai-preview", - ); - if (!target) { - cleanupScrollTarget = undefined; - return; - } + const handleScroll = (event: Event) => { + if (profileMenuOpen) return; + const target = event.target; + const activeWorkspace = document.querySelector(".ecommerce-standalone__page--workspace:not([hidden])"); + if (!activeWorkspace) return; + const isWorkspacePreviewScroll = + target instanceof HTMLElement && target.classList.contains("clone-ai-preview") && activeWorkspace.contains(target); + const isPageScroll = + target === document || + target === document.scrollingElement || + target === document.documentElement || + target === document.body; + if (!isWorkspacePreviewScroll && !isPageScroll) return; - const handleScroll = () => { - if (profileMenuOpen) return; - setIsTopbarHidden(true); - if (restoreTimer) window.clearTimeout(restoreTimer); - restoreTimer = window.setTimeout(() => { - setIsTopbarHidden(false); - }, 240); - }; - - target.addEventListener("scroll", handleScroll, { passive: true }); - cleanupScrollTarget = () => target.removeEventListener("scroll", handleScroll); + setIsTopbarHidden(true); + if (restoreTimer) window.clearTimeout(restoreTimer); + restoreTimer = window.setTimeout(() => { + setIsTopbarHidden(false); + }, 240); }; - bindScrollTarget(); - const observer = new MutationObserver(bindScrollTarget); - observer.observe(document.body, { attributes: true, childList: true, subtree: true }); + window.addEventListener("scroll", handleScroll, { capture: true, passive: true }); return () => { - cleanupScrollTarget?.(); - observer.disconnect(); + window.removeEventListener("scroll", handleScroll, { capture: true }); if (restoreTimer) window.clearTimeout(restoreTimer); }; }, [profileMenuOpen]); diff --git a/src/features/ecommerce/EcommercePage.tsx b/src/features/ecommerce/EcommercePage.tsx index ea7d2bd..f878bdd 100644 --- a/src/features/ecommerce/EcommercePage.tsx +++ b/src/features/ecommerce/EcommercePage.tsx @@ -41,6 +41,35 @@ import EcommerceTryOnPanel from "./panels/EcommerceTryOnPanel"; import EcommerceClonePanel from "./panels/EcommerceClonePanel"; import { ecommerceOssScopes, saveUnifiedEcommerceGenerationRecord, deleteEcommerceGenerationRecord } from "./ecommerceGenerationPersistence"; 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 = [ "#ffffff", @@ -266,12 +295,11 @@ import { interface ProductClonePageProps { + onWorkspaceChromeChange?: (state: { isToolPage: boolean }) => void; [key: string]: unknown; } type ProductCloneStatus = "idle" | "ready" | "generating" | "done" | "failed"; -type ProductSetOutputKey = "set" | "detail" | "model" | "video"; -type CloneOutputKey = ProductSetOutputKey | "hot"; type CloneSetCountKey = "selling" | "white" | "scene"; type CloneModelPanelTab = "scene" | "model"; type CloneVideoQualityKey = "standard" | "high" | "ultra"; @@ -429,13 +457,6 @@ interface EcommerceImagePromptOptions { detailModules?: string[]; } -type PlatformRatioModeKey = ProductSetOutputKey | "hot"; - -interface PlatformRatioGroup { - ratios: string[]; - defaultRatio: string; -} - const sideTools: Array<{ key: ProductKitToolKey; label: string; icon: ReactNode }> = [ { key: "set", label: "商品套图", icon: }, { key: "detail", label: "A+详情", icon: }, @@ -443,324 +464,6 @@ const sideTools: Array<{ key: ProductKitToolKey; label: string; icon: ReactNode { key: "clone", label: "电商AI作图", icon: }, ]; -const platformSpecOptions: Array<{ - label: string; - ratios: string[]; - defaultRatio: string; - ratioGroups?: Partial>; - 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×1920px,9: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×800px,1: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 normalized = value.toLowerCase(); if (value.includes("淘宝") || value.includes("天猫")) return "淘"; @@ -811,223 +514,6 @@ const renderPlatformLogo = (value: string) => { ); }; -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 = { - "英文": "英文", - "中文": "中文", - "英语": "英文", - "日语": "日文", - "日文": "日文", - "德语": "德文", - "德文": "德文", - "法语": "法文", - "法文": "法文", - "韩语": "韩文", - "韩文": "韩文", - "西文": "西班牙语", - "西班牙语": "西班牙语", - "葡文": "葡萄牙语", - "葡萄牙语": "葡萄牙语", - "印尼语": "印度尼西亚语", - "印度尼西亚语": "印度尼西亚语", - "菲律宾语": "菲律宾语(他加禄语)", - "菲律宾语(他加禄语)": "菲律宾语(他加禄语)", -}; -const defaultPlatformSpec = platformSpecOptions[0]!; -const getPlatformSpec = (value: string) => - platformSpecOptions.find((option) => option.label === value || option.aliases?.includes(value)) ?? defaultPlatformSpec; -const legacyPlatformAliases: Record = { - "淘宝/天猫": "淘宝/天猫", - "京东": "京东", - "拼多多": "拼多多", - "抖音电商": "抖音电商", - "亚马逊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 }> = [ { key: "set", label: "套图", desc: "主图/卖点/场景", icon: }, { key: "detail", label: "详情图", desc: "长图模块化生成", icon: }, @@ -1164,9 +650,6 @@ const maxCloneProductImages = 20; const maxCloneReferenceImages = 20; const cloneVideoDurationMin = 5; const cloneVideoDurationMax = 45; -const defaultEcommercePlatform = "淘宝/天猫"; -const defaultProductSetOutput: ProductSetOutputKey = "set"; -const defaultCloneOutput: CloneOutputKey = "set"; const cloneLatestSettingStorageKey = "omniai.clone-ai.latest-setting"; const ecommerceHistoryStorageKey = "omniai.ecommerce.history.records"; const cloneVideoQualityOptions: Array<{ key: CloneVideoQualityKey; label: string; desc: string }> = [ @@ -1594,6 +1077,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const countHoldTimeoutRef = useRef(null); const countHoldIntervalRef = useRef(null); const isAuthenticated = Boolean((_props as Record).isAuthenticated); + const onWorkspaceChromeChange = _props.onWorkspaceChromeChange; const requestLogin = () => { const handler = (_props as Record).onRequireLogin; if (typeof handler === "function") handler(); @@ -1625,6 +1109,16 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const [showHostingModal, setShowHostingModal] = useState(false); const [productImages, setProductImages] = useState([]); 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(null); const [smartCutoutBatchImages, setSmartCutoutBatchImages] = useState([]); const [smartCutoutBackgroundColor, setSmartCutoutBackgroundColor] = useState("#ffffff"); @@ -7716,6 +7210,20 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const isRecordDetailWorkspace = isMainCloneWorkspace && Boolean(activeHistoryRecordId); const currentResultCount = canvasNodes.reduce((count, node) => count + node.results.length, 0); 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 ? activeHistoryRecord.turns?.length ? activeHistoryRecord.turns diff --git a/src/styles/ecommerce-standalone.css b/src/styles/ecommerce-standalone.css index fc80974..0c3d15f 100644 --- a/src/styles/ecommerce-standalone.css +++ b/src/styles/ecommerce-standalone.css @@ -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 { border-bottom: none !important; background: transparent !important; + background-color: transparent !important; + background-image: none !important; box-shadow: none !important; 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 { content: none !important; + display: none !important; } /* 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 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__account button, -html body #root div.ecommerce-standalone.web-shell[data-view="ecommerce"] .ecommerce-profile-menu__trigger { +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) { color: #10202c !important; background: 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; } +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 { 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; +}