// ==UserScript==
// @name 0.7.17珠宝AI 移动端适配
// @namespace https://zbai.art/
// @version 0.7.17
// @description 强化移动端真实生成按钮、上传、历史、下载与会员支付入口防误点验证
// @author Sulong
// @match https://zbai.art/*
// @match https://www.zbai.art/*
// @match https://jew.haistudio.ai/*
// @run-at document-start
// @grant none
// ==/UserScript==
(function () {
'use strict';
const STYLE_ID = 'zbai-mobile-adapter-style';
const ROOT_ID = 'zbai-mobile-root';
const CLASS_READY = 'zbai-mobile-adapted';
const HOME_SHELL_ID = 'zbai-mobile-home-shell';
const WORK_DETAIL_FALLBACK_ID = 'zbai-mobile-work-detail-fallback';
const MOBILE_QUERY = '(max-width: 768px), (pointer: coarse), (max-device-width: 820px)';
const ASSETS_URL = '/index/mine-homepage';
const VERSION = '0.7.17';
const GENERATE_TEXT_RE = /立即创作|开始创作|立即生成|创作|生成/;
const PRIMARY_GENERATE_TEXT_RE = /立即创作|开始创作|立即生成/;
const PAYMENT_ACTION_RE = /开通|升级|我要升级|续费|购买|支付|充值|去开通|去升级|会员升级|升级会员|购买套餐|开通会员/;
const DIRECT_PAYMENT_ACTION_RE = /^(立即|马上|去|前往)?(开通|升级|我要升级|续费|购买|支付|充值|去开通|去升级|购买套餐|开通会员|会员升级|升级会员)$/i;
const PAYMENT_CONTEXT_RE = /套餐|会员(?!模型)|权益|订阅|入门版|个人版|专业版|旗舰版|商业授权|收银台|订单|支付方式|连续包月|连续包年|个人创作者/;
const PAYMENT_CLASS_RE = /vip|member|pay|payment|subscribe|subscription|price|pricing|plan|package|checkout|order/i;
const NON_PAYMENT_MODEL_TEXT_RE = /高阶模型|商业模型|商业级模型|会员模型|会员专属模型|旗舰版模型/g;
const CLASS = {
enabled: 'zbai-mobile-enabled',
compose: 'zbai-mobile-compose-source',
composeOpen: 'zbai-mobile-compose-open',
formatChoice: 'zbai-mobile-format-choice',
formatLabel: 'zbai-mobile-format-label',
formatRow: 'zbai-mobile-format-row',
duplicateTitle: 'zbai-mobile-duplicate-title',
templateTextMode: 'zbai-mobile-template-text-mode',
history: 'zbai-mobile-history-source',
historyOpen: 'zbai-mobile-history-open',
historyParent: 'zbai-mobile-history-parent',
uploadArmed: 'zbai-mobile-upload-armed',
lock: 'zbai-mobile-lock',
};
const EDITOR_ROOT_SELECTOR = '#pictureEditor';
const HOME_TOOL_ACTIONS = [
{ key: 'image', label: '图片创作', eyebrow: 'Image', href: '/create/module/image', desc: '生成首饰海报、商品图和创意画面' },
{ key: 'video', label: '视频创作', eyebrow: 'Video', href: '/create/module/video', desc: '制作运镜展示、旋转视频和短片素材' },
];
const TEMPLATE_ACTIONS = [
{ label: '珠宝海报', href: '/create/ai-effects/jewelry-poster', tag: '海报', desc: '快速生成东方美学商品海报' },
{ label: '道具展示', href: '/create/ai-effects/jewelry-display', tag: '展示', desc: '为首饰匹配托盘、布景与道具' },
{ label: '社交媒体图', href: '/create/ai-effects/social-media', tag: '种草', desc: '小红书、朋友圈风格内容图' },
{ label: '大片模特', href: '/create/ai-effects/jewelry-models-display', tag: '模特', desc: '真人佩戴与商业大片画面' },
{ label: '珠宝纯净图', href: '/create/ai-effects/jewelry-background', tag: '底图', desc: '白底、黑底和纯净背景图' },
{ label: '精致摄影', href: '/create/ai-effects/finephotograph', tag: '摄影', desc: '高级光影、倒影和质感摄影' },
{ label: '布料与珠宝', href: '/create/ai-effects/jewelry-cloth', tag: '布料', desc: '丝绸、天鹅绒等材质布景' },
{ label: '珠宝多视角', href: '/create/ai-effects/jewelry-multiview', tag: '视角', desc: '多角度展示结构与细节' },
{ label: '珠宝详情页', href: '/create/ai-effects/product-details-page', tag: '详情', desc: '电商详情页视觉图' },
{ label: '珠宝视频', href: '/create/ai-effects/jewelry-360-display', tag: '视频', desc: '旋转展示与火彩展示视频' },
];
window.__ZBAI_DEBUG__ = window.__ZBAI_DEBUG__ === true;
window.__ZBAI_MOBILE_ADAPTER_VERSION__ = VERSION;
window.__ZBAI_MOBILE_ADAPTER_MERGE__ = '0.7.5-ui+0.2.3-actions';
window.__ZBAI_MOBILE_ADAPTER_DIAGNOSE__ = collectDiagnostics;
window.ZBAI_MOBILE_ADAPTER_DIAGNOSE = collectDiagnostics;
const media = window.matchMedia(MOBILE_QUERY);
let activeSurface = '';
let toastTimer = 0;
let authRefreshScheduled = false;
injectStyles();
waitForBody(boot);
function boot() {
if (document.getElementById(ROOT_ID)) return;
const root = buildRoot();
document.body.append(root);
syncMode();
installListeners(root);
scheduleAuthRefreshes();
const observer = new MutationObserver(throttle(() => {
syncMode();
}, 180));
observer.observe(document.body, { attributes: true, childList: true, subtree: true });
}
function installListeners(root) {
root.addEventListener('click', onRootClick);
listenForModeChange(media);
window.addEventListener('resize', throttle(syncMode, 180), { passive: true });
document.addEventListener('click', delayPromoteFloatingMenus, true);
document.addEventListener('touchend', delayPromoteFloatingMenus, { capture: true, passive: true });
}
function buildRoot() {
const root = document.createElement('div');
root.id = ROOT_ID;
root.innerHTML = `
移动适配已启用
`;
return root;
}
function onRootClick(event) {
const button = event.target.closest('[data-zbai-action]');
if (!button || !isMobile()) return;
event.preventDefault();
event.stopPropagation();
const action = button.dataset.zbaiAction;
if (action === 'close') closeSurface();
if (action === 'back') goBack();
if (action === 'compose') handleComposeAction();
if (action === 'upload') openUpload(button);
if (action === 'history') toggleHistory();
if (action === 'download') openDownload();
if (action === 'auth') toggleAuth();
}
async function toggleCompose() {
const panel = await waitForComposePanel();
if (!panel) {
toast('还没找到生成面板,页面加载完再点一次');
return;
}
if (activeSurface === 'compose') {
closeSurface();
return;
}
openSurface('compose', panel);
focusPrompt(panel);
}
async function handleComposeAction() {
if (activeSurface === 'compose') {
await runGenerate();
return;
}
await toggleCompose();
}
async function runGenerate() {
const panel = findComposePanel() || await waitForComposePanel();
if (!panel) {
toast('没找到立即创作按钮,请确认已进入创作页');
return;
}
// 只在真实创作控制面板内找按钮,避免扫到移动 dock、会员卡片或全局入口。
const generate = findGenerateButton(panel);
if (!generate) {
debugLog('runGenerate:missing', {
panel: describeElement(panel),
candidates: describeGenerateCandidates(panel),
});
toast('没找到立即创作按钮,请确认已进入创作页');
return;
}
openSurface('compose', panel);
// openSurface 可能改变布局,但不应扩大搜索范围。
const scopedPanel = findComposePanel() || panel;
const generate2 = generate.isConnected ? generate : (findGenerateButton(scopedPanel) || generate);
await prepareGenerateTargetForClick(generate2);
const beforeCount = findResultImages().length;
const audit = auditGenerateTarget(generate2, { requireClickSafety: true });
debugLog('runGenerate:target', {
beforeCount,
panel: describeElement(scopedPanel),
audit,
candidates: describeGenerateCandidates(scopedPanel),
});
if (!audit.allowed) {
debugLog('runGenerate:blocked', audit);
if (audit.directPaymentNode || audit.paymentAncestor || audit.insidePaymentArea) {
toast('识别到会员升级区,已阻止误点');
} else if (audit.click?.paymentHit || audit.click?.insidePaymentArea) {
toast('识别到升级/支付遮挡,已阻止误点');
} else if (audit.click && !audit.click.safe) {
toast('立即创作按钮被遮挡,请关闭弹层后再试');
} else {
toast('立即创作按钮当前不可用,请先完成提示词、上传或登录');
}
return;
}
if (!activateGenerateButton(generate2, audit)) return;
await waitForNewResultAfterGenerate(beforeCount);
}
async function openUpload(triggerButton) {
const currentPanel = findComposePanel();
const immediateUpload = currentPanel ? findUploadControl(currentPanel) : null;
if (immediateUpload) {
openSurface('compose', currentPanel);
armNativeUploadProxy(immediateUpload, triggerButton);
toast('上传参考图已准备好,请再点一次上传');
return;
}
const panel = await waitForComposePanel();
if (!panel) {
toast('还没找到上传区,先进入图片创作页');
return;
}
openSurface('compose', panel);
const imageMode = findModeToggle(panel, '图生');
if (imageMode) {
activate(imageMode);
await delay(360);
await waitForImageUploadControl(panel);
}
const upload = findUploadControl(findComposePanel() || panel);
if (!upload) {
toast('没找到上传参考图按钮');
return;
}
try {
upload.scrollIntoView({ block: 'center', behavior: 'smooth' });
} catch (_) {
upload.scrollIntoView?.();
}
armNativeUploadProxy(upload, triggerButton);
toast('上传参考图已准备好,请再点一次上传');
}
async function toggleHistory() {
const rail = await waitForHistoryRail();
if (!rail) {
toast('当前创作页没有找到历史缩略图,可点右上角资产查看全部');
return;
}
if (activeSurface === 'history') {
closeSurface();
return;
}
rail.classList.add(CLASS.history);
debugLog('history:openBefore', describeHistoryRail(rail));
openSurface('history', rail);
debugLog('history:openAfter', describeHistoryRail(rail));
rail.scrollIntoView({ block: 'nearest', inline: 'nearest' });
}
async function openDownload() {
closeSurface();
const detailDownload = document.querySelector(`#${WORK_DETAIL_FALLBACK_ID} [data-zbai-work-download]`);
if (detailDownload) {
activate(detailDownload);
return;
}
const download = findDownloadControl();
if (download) {
activate(download);
return;
}
const result = findResultImages()[0] || findPrimaryResult();
if (!result) {
toast('先打开或生成一张图片,再点下载');
return;
}
const hadPreview = hasVisiblePreviewModal();
const beforeUrl = location.href;
activate(result);
await delay(500);
const previewDownload = findDownloadControl();
if (previewDownload) {
activate(previewDownload);
return;
}
if (!hadPreview && !hasVisiblePreviewModal() && location.href === beforeUrl) {
toast('未能打开预览图');
return;
}
toast('预览已打开,请点预览里的下载图标');
}
function openSurface(surface, node) {
closeSurface();
activeSurface = surface;
if (surface === 'history') prepareHistoryRailForMobile(node);
node.classList.add(surface === 'compose' ? CLASS.composeOpen : CLASS.historyOpen);
if (surface === 'compose') node.scrollTop = 0;
document.body.classList.add(CLASS.lock);
document.getElementById(ROOT_ID)?.setAttribute('data-zbai-surface', surface);
}
function closeSurface() {
activeSurface = '';
disarmNativeUploadProxy();
document.querySelector(`.${CLASS.composeOpen}`)?.classList.remove(CLASS.composeOpen);
document.querySelector(`.${CLASS.historyOpen}`)?.classList.remove(CLASS.historyOpen);
document.querySelectorAll(`.${CLASS.historyParent}`).forEach((node) => node.classList.remove(CLASS.historyParent));
document.body?.classList.remove(CLASS.lock);
document.getElementById(ROOT_ID)?.removeAttribute('data-zbai-surface');
}
function goBack() {
closeSurface();
let sameOriginReferrer = false;
try {
sameOriginReferrer = Boolean(document.referrer) &&
new URL(document.referrer).origin === window.location.origin;
} catch (_) {
sameOriginReferrer = false;
}
if (sameOriginReferrer && window.history.length > 1) {
window.history.back();
return;
}
navigateTo('/');
}
function refreshTargets() {
if (!document.body) return;
ensureViewport();
updatePageType();
syncLoginModalState();
refreshRoute();
refreshAuthControl();
if (!isMobile()) {
closeSurface();
cleanupMobileHomeShell();
cleanupWorkDetailFallback();
return;
}
if (document.documentElement.dataset.zbaiPage === 'home') ensureMobileHomeShell();
else cleanupMobileHomeShell();
ensureWorkDetailFallback();
document.querySelector(EDITOR_ROOT_SELECTOR)?.classList.add(CLASS_READY);
const compose = findComposePanel();
compose?.classList.add(CLASS.compose);
markFormatControls(compose);
markTemplateTextMode(compose);
markDuplicateTitles(compose);
findHistoryRail()?.classList.add(CLASS.history);
promoteFloatingMenus();
if (window.__ZBAI_DEBUG__ === true) debugLog('refreshTargets', collectDiagnostics());
}
function refreshRoute() {
const root = document.getElementById(ROOT_ID);
if (!root) return;
root.dataset.zbaiRoute = findComposePanel() || isCreateRoute() ? 'compose' : 'page';
}
function refreshAuthControl() {
const button = document.querySelector(`#${ROOT_ID} .zbai-mobile-auth`);
if (!button) return;
const loggedIn = isLoggedIn();
button.textContent = loggedIn ? '退出' : '登录';
button.setAttribute('aria-label', loggedIn ? '退出登录' : '登录');
}
function scheduleAuthRefreshes() {
if (authRefreshScheduled) return;
authRefreshScheduled = true;
[300, 900, 1800, 3200, 5200, 8000].forEach((ms) => {
window.setTimeout(() => {
refreshAuthControl();
const shell = document.getElementById(HOME_SHELL_ID);
if (shell) updateMobileHomeAuth(shell);
if (ms === 8000) authRefreshScheduled = false;
}, ms);
});
}
function syncMode() {
const enabled = isMobile();
document.documentElement.dataset.zbaiMobile = enabled ? '1' : '0';
document.body?.classList.toggle(CLASS.enabled, enabled);
if (!enabled) closeSurface();
refreshTargets();
}
function listenForModeChange(query) {
const onChange = () => syncMode();
if (typeof query.addEventListener === 'function') query.addEventListener('change', onChange);
else query.addListener?.(onChange);
}
function delayPromoteFloatingMenus() {
window.setTimeout(promoteFloatingMenus, 60);
}
function promoteFloatingMenus() {
if (!isMobile() || !activeSurface) return;
const menus = document.querySelectorAll([
'.ant-select-dropdown',
'.ant-dropdown',
'.arco-select-popup',
'.el-select-dropdown',
'[role="listbox"]',
'[data-radix-popper-content-wrapper]',
].join(','));
[...menus].filter(isFloatingMenu).forEach((node) => {
if (node.style.getPropertyValue('z-index') !== '2147483605') {
node.style.setProperty('z-index', '2147483605', 'important');
}
});
}
function isFloatingMenu(node) {
if (!node || node.closest(`#${ROOT_ID}`)) return false;
const style = getComputedStyle(node);
const rect = node.getBoundingClientRect();
const position = style.position === 'absolute' || style.position === 'fixed';
return position && rect.width > 0 && rect.height > 0 &&
style.display !== 'none' && style.visibility !== 'hidden';
}
async function toggleAuth() {
closeSurface();
if (isLoggedIn()) {
await openLogout();
return;
}
openLogin();
}
function openLogin() {
document.documentElement.dataset.zbaiLoginOpen = '1';
document.documentElement.dataset.zbaiLoginRequestedAt = String(Date.now());
cleanupMobileHomeShell();
const login = findLoginButton();
if (!login) {
delete document.documentElement.dataset.zbaiLoginOpen;
delete document.documentElement.dataset.zbaiLoginRequestedAt;
if (getPageType() === 'home') ensureMobileHomeShell();
toast('还没找到原站登录入口,刷新首页后再点');
debugLog('openLogin:missing', collectDiagnostics());
return;
}
debugLog('openLogin', describeElement(login));
activate(login);
scheduleLoginStateCleanup();
scheduleAuthRefreshes();
}
async function openLogout() {
if (getPageType() === 'home') {
// 首页展示原站 #root 以便找到菜单和退出按钮
document.documentElement.dataset.zbaiNativeMenuOpen = '1';
cleanupMobileHomeShell();
scheduleNativeMenuCleanup();
}
let logout = findLogoutButton();
if (logout) {
debugLog('openLogout:direct', describeElement(logout));
activate(logout);
return;
}
const menu = findMobileMenuButton();
if (!menu) {
toast('还没找到原站菜单入口,刷新首页后再点');
return;
}
debugLog('openLogout:menuButton', describeElement(menu));
activate(menu);
await delay(520);
const drawer = findOpenMobileDrawer();
logout = findLogoutButton(drawer || document);
if (logout) {
debugLog('openLogout:drawer', describeElement(logout));
activate(logout);
return;
}
toast('账号菜单已打开,请点 Log Out');
}
function scheduleLoginStateCleanup() {
[1800, 2600, 4200, 7000].forEach((ms) => {
window.setTimeout(() => {
if (hasLoginModalDom()) {
document.documentElement.dataset.zbaiLoginOpen = '1';
if (getPageType() === 'home') cleanupMobileHomeShell();
return;
}
if (!hasVisibleLoginModal()) {
delete document.documentElement.dataset.zbaiLoginOpen;
delete document.documentElement.dataset.zbaiLoginRequestedAt;
if (isMobile() && getPageType() === 'home') ensureMobileHomeShell();
}
}, ms);
});
}
function scheduleNativeMenuCleanup() {
const cleanup = () => {
if (!document.documentElement.dataset.zbaiNativeMenuOpen) return;
const drawer = findOpenMobileDrawer();
if (drawer) { window.setTimeout(cleanup, 800); return; }
delete document.documentElement.dataset.zbaiNativeMenuOpen;
if (isMobile() && getPageType() === 'home') ensureMobileHomeShell();
};
[1200, 2200, 4500, 8000].forEach((ms) => window.setTimeout(cleanup, ms));
}
function syncLoginModalState() {
if (hasLoginModalDom()) {
document.documentElement.dataset.zbaiLoginOpen = '1';
if (getPageType() === 'home') cleanupMobileHomeShell();
return;
}
cleanupStaleLoginState();
}
function cleanupStaleLoginState() {
if (document.documentElement.dataset.zbaiLoginOpen !== '1') return;
if (hasVisibleLoginModal()) return;
if (hasLoginModalDom()) return;
const requestedAt = Number(document.documentElement.dataset.zbaiLoginRequestedAt || '0');
if (requestedAt && Date.now() - requestedAt < 2200) return;
delete document.documentElement.dataset.zbaiLoginOpen;
delete document.documentElement.dataset.zbaiLoginRequestedAt;
}
function hasVisibleLoginModal() {
const modal = [
...document.querySelectorAll([
'.LoginModal_LoginModal__O59AS',
'[class*="LoginModal"]',
'[class*="OverseasLogin_overseasLogin"]',
'[class*="overseasLoginModal"]',
'.fixed.top-0.left-0.w-screen.h-screen',
].join(',')),
].some((node) => node instanceof HTMLElement && isUsable(node));
if (modal) return true;
const phone = document.querySelector('input#first-phone, input[placeholder*="请输入手机号码"]');
if (phone instanceof HTMLElement && isUsable(phone)) return true;
return [...document.querySelectorAll('div, section, main')]
.some((node) => {
if (!(node instanceof HTMLElement) || !isUsable(node)) return false;
const text = normalizedText(node);
return text.includes('欢迎来到') && text.includes('珠宝AI') &&
(text.includes('手机号登录') || text.includes('邮箱登录') || text.includes('下一步'));
});
}
function hasLoginModalDom() {
const modalSelectors = [
'.LoginModal_LoginModal__O59AS',
'[class*="LoginModal"]',
'[class*="OverseasLogin_overseasLogin"]',
'[class*="overseasLoginModal"]',
'.fixed.top-0.left-0.w-screen.h-screen',
].join(',');
const phone = document.querySelector('input#first-phone, input[placeholder*="请输入手机号码"]');
if (phone instanceof HTMLElement) {
const style = getComputedStyle(phone);
if (style.display !== 'none' && style.visibility !== 'hidden') return true;
}
return [...document.querySelectorAll(modalSelectors)]
.some((node) => {
if (!(node instanceof HTMLElement)) return false;
const style = getComputedStyle(node);
if (style.display === 'none' || style.visibility === 'hidden') return false;
const text = normalizedText(node);
return text.includes('欢迎来到') && text.includes('珠宝AI') &&
(text.includes('手机号登录') || text.includes('邮箱登录') || text.includes('下一步'));
});
}
function isLoggedIn() {
return Boolean(findProfileAvatar() || findLoggedInIndicator());
}
function findProfileAvatar() {
const candidates = document.querySelectorAll(
'img[src*="avatar" i], img[src*="profile" i], img[src*="head" i]'
);
return [...candidates].find((image) => {
if (!isUsable(image)) return false;
const rect = image.getBoundingClientRect();
const src = image.currentSrc || image.src || '';
const style = getComputedStyle(image);
const avatarSource = /avatar|profile|head|default_profile_picture/i.test(src);
const headerAvatarShape = rect.top < 120 && rect.right > window.innerWidth * 0.66 &&
rect.width >= 24 && rect.width <= 72 && rect.height >= 24 && rect.height <= 72 &&
style.cursor === 'pointer';
return avatarSource && headerAvatarShape;
});
}
function findLoggedInIndicator() {
const candidates = document.querySelectorAll([
'[class*="VipCenterButton"]',
'[class*="VipLevelButton"]',
'[class*="UserInfo"]',
'[class*="Avatar"]',
'[class*="Profile"]',
'button',
'a',
'[role="button"]',
'div',
'span',
].join(','));
return [...candidates]
.filter((node) => node instanceof HTMLElement && !node.closest(`#${ROOT_ID}, #${HOME_SHELL_ID}`))
.find((node) => {
const text = normalizedText(node);
const className = String(node.className || '');
const looksLikeVip = /VipCenterButton|VipLevelButton|UserInfo|Avatar|Profile/i.test(className) &&
/升级|会员|旗舰版|专业版|标准版|基础版|高级版|企业版|个人|账户|我的|VIP/i.test(text || className);
const visibleHeaderVip = isUsable(node) && node.getBoundingClientRect().top < 120 &&
/^(升级|会员中心|旗舰版|专业版|标准版|基础版|高级版|企业版|个人中心|我的|VIP)$/i.test(text);
return looksLikeVip || visibleHeaderVip;
}) || null;
}
function findAuthTextControl(pattern, scope = document, ancestorSelector = 'button, a, [role="button"], div') {
return [...scope.querySelectorAll(
'button, a, [role="button"], [role="menuitem"], li, div, span'
)]
.filter((node) => !node.closest(`#${ROOT_ID}, #${HOME_SHELL_ID}`))
.map((node) => {
const text = normalizedText(node);
if (text.length > 20 || !pattern.test(text) || !isUsable(node)) return null;
return node.closest(ancestorSelector) || findClickableAncestor(node);
})
.find(Boolean);
}
function findLoginButton() {
return findAuthTextControl(/^(Log In|Login|Sign In|登录|登录\/注册|注册\/登录)$/i) ||
findAuthTextControl(/Log In|Login|Sign In|登录/i);
}
function findLogoutButton(scope = document) {
const resolved = findAuthTextControl(
/^(退出登录|退出账号|退出|Log Out|Logout|Sign Out)$/i,
scope,
'[class*="MobileActionBar_LoginButton"], button, a, [role="button"], div, span'
) || findAuthTextControl(
/退出登录|退出账号|退出|Log Out|Logout|Sign Out/i,
scope,
'[class*="MobileActionBar_LoginButton"], button, a, [role="button"], div, span'
);
if (resolved) return resolved;
// span 标签需要向上找可点击父级
const span = [...(scope || document).querySelectorAll('span')]
.filter((node) => node instanceof HTMLElement && !node.closest(`#${ROOT_ID}, #${HOME_SHELL_ID}`))
.find((node) => {
const text = normalizedText(node);
return text.length <= 20 && /^(退出登录|退出账号|退出|Log Out|Logout|Sign Out)$/i.test(text) && isUsable(node);
});
return span instanceof HTMLElement
? (span.closest('button, a, [role="button"], div, [class*="MobileActionBar_LoginButton"]') || span)
: null;
}
function findClickableAncestor(node) {
return node?.closest?.('button, a, [role="button"], div') || node;
}
function findComposePanel() {
const anchors = [
...document.querySelectorAll('textarea'),
...document.querySelectorAll('input[type="file"]'),
...[...document.querySelectorAll('button, label, [role="button"], div, span')]
.filter((node) => /上传|立即创作|开始创作|立即生成/.test(normalizedText(node))),
];
for (const anchor of anchors) {
const panel = walkParents(anchor, isComposePanel);
if (panel) return panel;
}
const editorControl = document.querySelector('.picture-editor-content-control');
if (editorControl instanceof HTMLElement && isUsable(editorControl)) return editorControl;
return null;
}
function isComposePanel(node) {
if (!(node instanceof HTMLElement)) return false;
const text = normalizedText(node);
const hasGeneratorConfig = text.includes('选择模型') ||
text.includes('选择模板') ||
text.includes('选个风格') ||
text.includes('生成比例') ||
text.includes('数量');
const hasCreate = text.includes('立即创作') || text.includes('开始创作') || text.includes('立即生成');
const hasPrompt = Boolean(findPromptInput(node)) ||
text.includes('在此输入') ||
text.includes('快捷提示词') ||
text.includes('提示词');
const hasUpload = text.includes('上传') || Boolean(node.querySelector?.('input[type="file"]'));
const hasSizing = text.includes('生成比例') || text.includes('尺寸') || text.includes('张数') || text.includes('数量');
return hasGeneratorConfig && hasCreate && (hasUpload || hasPrompt || hasSizing);
}
function markFormatControls(panel) {
if (!panel) return;
const labels = [...panel.querySelectorAll('div, span, p, label')]
.filter((node) => normalizedText(node) === '多格式');
labels.forEach((label) => {
label.classList.add(CLASS.formatLabel);
const row = walkParents(label, (node) => isFormatRow(node, panel));
if (!row) return;
row.classList.add(CLASS.formatRow);
markFormatChoices(row);
});
}
function markTemplateTextMode(panel) {
document.querySelectorAll(`.${CLASS.templateTextMode}`).forEach((node) => node.classList.remove(CLASS.templateTextMode));
if (!panel || !isTemplateRoute()) return;
[...panel.querySelectorAll('button, a, label, [role="button"], div, span')]
.filter((node) => node instanceof HTMLElement && normalizedText(node) === '文生' && isUsable(node))
.map((node) => node.closest('button, a, label, [role="button"]') || node)
.filter((node) => node instanceof HTMLElement && !node.querySelector('textarea, input, .ant-upload, .ant-upload-btn'))
.forEach((node) => node.classList.add(CLASS.templateTextMode));
}
function isFormatRow(node, panel) {
if (!node || node === panel) return false;
const text = normalizedText(node);
return text.includes('多格式') && text.includes('JPEG') && text.includes('PNG') &&
text.length <= 80;
}
function markFormatChoices(row) {
[...row.querySelectorAll('button, label, div, span, p')]
.filter((node) => /^(JPEG|PNG)$/.test(normalizedText(node)))
.forEach((node) => {
node.classList.add(CLASS.formatChoice);
const parent = node.parentElement;
if (parent && parent !== row && normalizedText(parent).length <= 20) {
parent.classList.add(CLASS.formatChoice);
}
});
}
function markDuplicateTitles(panel) {
if (!panel) return;
panel.querySelectorAll(`.${CLASS.duplicateTitle}`).forEach((node) => node.classList.remove(CLASS.duplicateTitle));
const titles = [...panel.querySelectorAll('.control-config-title, [class*="title" i], h1, h2, h3')]
.filter((node) => node instanceof HTMLElement && isUsable(node))
.filter(isIndependentTitleNode)
.filter((node) => {
const text = normalizedText(node);
if (!text || text.length > 40) return false;
return !/选择模型|选择模板|上传参考图|立即创作|开始创作/.test(text);
});
titles.forEach((node) => {
const text = normalizedText(node);
if (/^(请上传图片|请上传首饰图|上传首饰图)$/.test(text) || /^请上传前/.test(text)) {
node.classList.add(CLASS.duplicateTitle);
}
});
for (let i = 0; i < titles.length; i += 1) {
for (let j = i + 1; j < titles.length; j += 1) {
if (!rectsOverlap(titles[i].getBoundingClientRect(), titles[j].getBoundingClientRect())) continue;
const firstText = normalizedText(titles[i]);
const secondText = normalizedText(titles[j]);
const firstUploadTitle = /^(请上传图片|请上传首饰图|上传首饰图)$/.test(firstText) || /^请上传前/.test(firstText);
const secondUploadTitle = /^(请上传图片|请上传首饰图|上传首饰图)$/.test(secondText) || /^请上传前/.test(secondText);
if (firstUploadTitle || secondUploadTitle) {
(firstUploadTitle ? titles[i] : titles[j]).classList.add(CLASS.duplicateTitle);
continue;
}
if (/Hi|你想创作|创作什么/.test(firstText) && /上传|图片/.test(secondText)) {
titles[j].classList.add(CLASS.duplicateTitle);
continue;
}
titles[j].classList.add(CLASS.duplicateTitle);
}
}
}
function markOverlappingTitles(panel) {
markDuplicateTitles(panel);
}
function isIndependentTitleNode(node) {
if (!(node instanceof HTMLElement)) return false;
if (node.matches('button, a, label, textarea, input, [role="button"], [role="textbox"]')) return false;
if (node.closest('button, a, label, [role="button"], .ant-upload, .ant-upload-btn')) return false;
if (node.querySelector('textarea, input, button, a, label, [role="button"], .ant-upload, .ant-upload-btn')) return false;
return true;
}
function rectsOverlap(a, b) {
return a.width > 0 && a.height > 0 && b.width > 0 && b.height > 0 &&
a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top;
}
function findPromptInput(scope) {
return scope?.querySelector(
'textarea[placeholder*="描述"], textarea[placeholder*="重绘"], textarea'
);
}
function findUploadControl(scope) {
if (!scope) return null;
return [...scope.querySelectorAll('input[type="file"]')].find(isImageUploadInput) ||
findUploadTrigger(scope) ||
[...scope.querySelectorAll('[class*="upload" i]')].find((node) =>
isUsable(node) && /上传参考图|上传图片|添加图片/.test(normalizedText(node))
);
}
async function waitForComposePanel() {
const waits = [0, 180, 420, 800, 1300, 2100];
let fallback = null;
for (const wait of waits) {
if (wait) await delay(wait);
const panel = findComposePanel();
if (!panel) continue;
if (isReadyComposePanel(panel)) return panel;
if (!fallback) fallback = panel;
}
return fallback;
}
function isReadyComposePanel(panel) {
return Boolean(
findPromptInput(panel) ||
findGenerateButton(panel) ||
findModeToggle(panel, '图生') ||
findUploadTrigger(panel) ||
[...panel.querySelectorAll('input[type="file"]')].some(isImageUploadInput)
);
}
async function waitForImageUploadControl(panel) {
const waits = [160, 300, 520, 820, 1200, 1800];
for (const wait of waits) {
await delay(wait);
if (findUploadControl(findComposePanel() || panel)) return true;
}
return false;
}
function findModeToggle(scope, text) {
if (!scope) return null;
return [...scope.querySelectorAll('button, a, label, [role="button"], div, span')]
.filter((node) => normalizedText(node) === text && isUsable(node))
.map((node) => node.closest('button, a, label, [role="button"]') || node)
.sort((a, b) => elementArea(a) - elementArea(b))[0] || null;
}
function findUploadTrigger(scope) {
const input = [...scope.querySelectorAll('input[type="file"]')].find(isImageUploadInput);
if (input) {
const uploadParent = findUploadParent(input);
if (uploadParent) return uploadParent;
}
return [...scope.querySelectorAll([
'button',
'a',
'label',
'[role="button"]',
'.ant-upload',
'.ant-upload-btn',
'.upload-custom-button',
'[class*="CoverUpload" i]',
'div',
'span',
].join(','))]
.filter((node) => {
const text = normalizedText(node);
return isUsable(node) && /上传参考图|上传图片|添加图片/.test(text);
})
.map((node) => node.closest('button, a, label, [role="button"], .ant-upload, .ant-upload-btn') || node)
.sort((a, b) => elementArea(a) - elementArea(b))[0] || null;
}
function findUploadParent(input) {
return input.closest?.('label, .ant-upload, .ant-upload-btn, button, [role="button"], [class*="CoverUpload" i]') ||
walkParents(input.parentElement, (node) => {
if (!(node instanceof HTMLElement)) return false;
if (node.matches?.('label, .ant-upload, .ant-upload-btn, button, [role="button"], [class*="CoverUpload" i]')) return true;
const text = normalizedText(node);
return text.includes('上传参考图') && elementArea(node) > 1;
});
}
function activateUpload(node) {
if (!node) return false;
let target = node;
const fileInput = node instanceof HTMLInputElement && node.type === 'file' ? node : null;
if (fileInput) {
target = findUploadParent(node) || node;
}
debugLog('activateUpload', describeElement(target));
try {
target.scrollIntoView?.({ block: 'center', inline: 'center', behavior: 'auto' });
} catch (_) {
// iOS/Safari can reject object-style scroll options in some wrappers.
}
target.focus?.({ preventScroll: true });
target.click?.();
if (fileInput && target !== fileInput) {
debugLog('activateUpload:fileInputFallback', describeElement(fileInput));
fileInput.click?.();
}
return true;
}
function armNativeUploadProxy(node, triggerButton) {
if (!(node instanceof HTMLElement)) return false;
const target = node instanceof HTMLInputElement && node.type === 'file' ?
findUploadParent(node) || node :
node;
if (!(target instanceof HTMLElement)) return false;
const button = triggerButton instanceof HTMLElement ?
triggerButton :
document.querySelector(`#${ROOT_ID} [data-zbai-action="upload"]`);
const rect = button?.getBoundingClientRect?.();
if (!rect || rect.width <= 0 || rect.height <= 0) return false;
disarmNativeUploadProxy();
target.classList.add(CLASS.uploadArmed);
target.dataset.zbaiUploadArmed = '1';
target.style.setProperty('--zbai-upload-left', `${Math.round(rect.left)}px`);
target.style.setProperty('--zbai-upload-top', `${Math.round(rect.top)}px`);
target.style.setProperty('--zbai-upload-width', `${Math.round(rect.width)}px`);
target.style.setProperty('--zbai-upload-height', `${Math.round(rect.height)}px`);
target.addEventListener('click', scheduleUploadProxyDisarm, { capture: true, once: true });
debugLog('uploadProxy:armed', { target: describeElement(target), trigger: describeElement(button) });
return true;
}
function scheduleUploadProxyDisarm() {
window.setTimeout(disarmNativeUploadProxy, 1200);
}
function disarmNativeUploadProxy() {
document.querySelectorAll(`.${CLASS.uploadArmed}`).forEach((node) => {
node.classList.remove(CLASS.uploadArmed);
node.removeAttribute('data-zbai-upload-armed');
node.style.removeProperty('--zbai-upload-left');
node.style.removeProperty('--zbai-upload-top');
node.style.removeProperty('--zbai-upload-width');
node.style.removeProperty('--zbai-upload-height');
node.removeEventListener('click', scheduleUploadProxyDisarm, { capture: true });
});
}
function isImageUploadInput(node) {
if (!(node instanceof HTMLInputElement) || node.type !== 'file') return false;
const accept = node.accept || '';
if (!accept) return true;
return /image\/(jpeg|jpg|png|bmp|webp|heic)|\.(jpe?g|png|bmp|webp|heic)/i.test(accept);
}
function elementArea(node) {
const rect = node.getBoundingClientRect();
return Math.max(1, Math.round(rect.width * rect.height));
}
function findGenerateButton(scope = document) {
const root = scope === document ? (findComposePanel() || document) : (scope || document);
const seen = new Set();
return collectGenerateCandidates(root)
.map((node) => getGenerateClickTarget(node))
.filter((node) => {
if (!(node instanceof HTMLElement) || seen.has(node)) return false;
seen.add(node);
return isRealGenerateButton(node);
})
.map((node) => ({ node, score: scoreGenerateButton(node) }))
.sort((a, b) => b.score - a.score)[0]?.node || null;
}
function isRealGenerateButton(node) {
if (!(node instanceof HTMLElement) || node.closest(`#${ROOT_ID}, #${HOME_SHELL_ID}`)) return false;
const text = normalizedText(node);
if (!GENERATE_TEXT_RE.test(text)) return false;
if (hasPaymentActionText(text)) return false;
if (!isGenerateClickable(node)) return false;
if (!hasComposeConfigContext(node)) return false;
if (auditGenerateTarget(node).insidePaymentArea) return false;
return isUsable(node);
}
function hasPaymentOrMembershipAncestor(node) {
let parent = node.parentElement;
while (parent && parent !== document.body && parent.id !== 'root' && parent.id !== 'haimeta') {
if (isGenerateScopeBoundary(parent)) return false;
if (isPaymentOrMembershipNode(parent)) return true;
parent = parent.parentElement;
}
return false;
}
function isGenerateScopeBoundary(node) {
if (!(node instanceof HTMLElement)) return false;
return node.matches?.([
'.generate-button-container',
'.ControlBottom_controlBottom__gooOU',
'.control-buttons',
'.control-config',
'.PictureEditorControl_PictureEditorControl__T6FHN',
'.picture-editor-content-control',
'.picture-editor-content-box',
'.picture-editor-content',
'[class*="PictureEditor_pictureEditor"]',
].join(','));
}
function isInsidePaymentOrMembershipArea(node) {
if (!(node instanceof HTMLElement)) return false;
let parent = node.parentElement;
while (parent && parent !== document.body && parent.id !== 'root' && parent.id !== 'haimeta') {
if (isGenerateScopeBoundary(parent)) return false;
if (isPaymentOrMembershipContainer(parent)) return true;
parent = parent.parentElement;
}
return false;
}
function isPaymentOrMembershipNode(node) {
const text = normalizedText(node);
const className = String(node?.className || '');
if (!text && !className) return false;
const compact = normalizePaymentText(text.slice(0, 260));
const hasGenerateAction = GENERATE_TEXT_RE.test(compact);
const hasPaymentAction = hasPaymentActionText(compact);
if (hasGenerateAction && !hasPaymentAction && isGenerateScopeBoundary(node)) return false;
const directPaymentAction = DIRECT_PAYMENT_ACTION_RE.test(compact) ||
(/^(立即|马上|去|前往)?(开通会员|升级会员|购买套餐|会员升级)$/i.test(compact));
const hasPrice = /¥|¥|\d+\s*元|\/年|\/月|每年|每月/.test(compact);
const hasPaymentContext = hasPaymentContextText(compact);
const classLooksPaid = PAYMENT_CLASS_RE.test(className);
const smallActionButton = node.matches?.('button, a, [role="button"], [role="menuitem"]') &&
compact.length <= 48 && hasPaymentAction;
return directPaymentAction ||
smallActionButton ||
(hasPaymentAction && (hasPrice || (hasPaymentContext && looksLikePaymentShell(node)))) ||
(classLooksPaid && hasPaymentAction);
}
function getGenerateClickTarget(node) {
if (!(node instanceof HTMLElement)) return null;
if (node.matches('.generate-button-container, .ControlBottom_controlBottom__gooOU, .control-buttons')) {
return node.querySelector('button, [role="button"]') || node;
}
const button = node.closest('button, [role="button"]');
if (button) return button;
return node.closest('.generate-button-container, .ControlBottom_controlBottom__gooOU, .control-buttons') ||
node;
}
function isGenerateClickable(node) {
if (!(node instanceof HTMLElement)) return false;
if (node.matches('button, [role="button"]')) return true;
if (node.closest('button, [role="button"]')) return true;
if (node.matches('.generate-button-container, .ControlBottom_controlBottom__gooOU')) return true;
const style = getComputedStyle(node);
return style.cursor === 'pointer' && elementArea(node) >= 900;
}
function hasComposeConfigContext(node) {
if (!(node instanceof HTMLElement)) return false;
const controlPanel = node.closest?.([
'.PictureEditorControl_PictureEditorControl__T6FHN',
'.picture-editor-content-control',
'.control-config',
].join(','));
if (controlPanel instanceof HTMLElement && (isComposePanel(controlPanel) || hasComposeConfigSignal(controlPanel))) {
return true;
}
const context = walkParents(node, (parent) => {
if (!(parent instanceof HTMLElement)) return false;
return hasComposeConfigSignal(parent) || isComposePanel(parent);
});
return Boolean(context);
}
function hasComposeConfigSignal(node) {
if (!(node instanceof HTMLElement)) return false;
const text = normalizedText(node);
return Boolean(node.querySelector?.('textarea, input[type="file"], .ant-upload, .ant-upload-btn')) ||
/选择模板|选择模型|选择尺寸|尺寸|张数|上传参考图|上传图片|添加图片|提示词|描述|参考图|多格式/.test(text);
}
function scoreGenerateButton(node) {
const text = normalizedText(node);
const rect = node.getBoundingClientRect();
const className = String(node.className || '');
let score = 0;
if (/^(立即创作|开始创作|立即生成)(\s*\d+)?$/.test(text)) score += 130;
else if (/立即创作|开始创作|立即生成/.test(text)) score += 96;
else if (/^(创作|生成)(\s*\d+)?$/.test(text)) score += 48;
else score += 24;
if (node.closest('.generate-button-container')) score += 60;
if (node.closest('.ControlBottom_controlBottom__gooOU, .control-buttons')) score += 36;
if (/generate|ControlBottom|controlBottom/i.test(className)) score += 28;
if (rect.width >= 120 && rect.height >= 40) score += 16;
score += Math.min(18, Math.round(elementArea(node) / 1800));
score += Math.round(rect.top / Math.max(1, window.innerHeight)) * 4;
return score;
}
function collectGenerateCandidates(root) {
const containers = [...root.querySelectorAll([
'.generate-button-container',
'.ControlBottom_controlBottom__gooOU',
'.control-buttons',
].join(','))].filter((node) => node instanceof HTMLElement && !node.closest(`#${ROOT_ID}, #${HOME_SHELL_ID}`));
const precise = containers.flatMap((container) => [
...container.querySelectorAll('button, [role="button"], div, span'),
container,
]).filter((node) => {
const text = normalizedText(node);
if (!GENERATE_TEXT_RE.test(text)) return false;
if (hasPaymentActionText(text)) return false;
return true;
});
if (precise.length) return precise;
return [...root.querySelectorAll('button, [role="button"], div, span')]
.filter((node) => {
const text = normalizedText(node);
if (!PRIMARY_GENERATE_TEXT_RE.test(text)) return false;
if (hasPaymentActionText(text)) return false;
if (text.length > 40) return false;
return !node.closest(`#${ROOT_ID}, #${HOME_SHELL_ID}`);
});
}
function isPaymentOrMembershipContainer(node) {
if (!(node instanceof HTMLElement)) return false;
if (isPaymentOrMembershipNode(node)) return true;
const text = normalizePaymentText(normalizedText(node).slice(0, 1200));
const className = String(node.className || '');
const hasPaymentAction = hasPaymentActionText(text);
if (PRIMARY_GENERATE_TEXT_RE.test(text) && isGenerateScopeBoundary(node) && !hasPaymentAction) return false;
const classLooksPaid = PAYMENT_CLASS_RE.test(className);
const hasPrice = /¥|¥|\d+\s*元|\/年|\/月|每年|每月/.test(text);
const hasPaymentContext = hasPaymentContextText(text);
return (classLooksPaid && hasPaymentAction) ||
(hasPaymentAction && hasPrice) ||
(hasPaymentAction && hasPaymentContext && looksLikePaymentShell(node)) ||
(hasPrice && hasPaymentContext && looksLikePaymentShell(node));
}
function normalizePaymentText(text) {
return String(text || '').replace(NON_PAYMENT_MODEL_TEXT_RE, '');
}
function hasPaymentActionText(text) {
return PAYMENT_ACTION_RE.test(normalizePaymentText(text));
}
function hasPaymentContextText(text) {
return PAYMENT_CONTEXT_RE.test(normalizePaymentText(text));
}
function looksLikePaymentShell(node) {
if (!(node instanceof HTMLElement)) return false;
if (isGenerateScopeBoundary(node)) return false;
const className = String(node.className || '');
if (PAYMENT_CLASS_RE.test(className)) return true;
if (node.matches?.('.ant-modal, .ant-drawer, [class*="Modal"], [class*="modal"], [class*="Drawer"], [class*="drawer"]')) {
return true;
}
const text = normalizePaymentText(normalizedText(node).slice(0, 800));
return /会员中心|选择套餐|购买套餐|开通会员|会员权益|支付方式|确认支付|收银台|订单/.test(text);
}
async function prepareGenerateTargetForClick(node) {
if (!(node instanceof HTMLElement)) return;
try {
node.scrollIntoView?.({ block: 'center', inline: 'center', behavior: 'auto' });
} catch (_) {
node.scrollIntoView?.();
}
const panel = node.closest?.(`.${CLASS.compose}`) || findComposePanel();
if (panel instanceof HTMLElement) {
keepGenerateButtonAboveMobileDock(node, panel);
}
await delay(60);
}
function keepGenerateButtonAboveMobileDock(node, panel) {
if (!(node instanceof HTMLElement) || !(panel instanceof HTMLElement) || !isMobile()) return;
const rect = node.getBoundingClientRect();
const dock = document.querySelector(`#${ROOT_ID} .zbai-mobile-dock`);
const dockRect = dock instanceof HTMLElement && isVisibleBox(dock)
? dock.getBoundingClientRect()
: null;
const safeBottom = dockRect ? Math.max(0, dockRect.top - 12) : window.innerHeight - 92;
const safeTop = 72;
if (rect.bottom > safeBottom) {
panel.scrollTop += Math.ceil(rect.bottom - safeBottom);
return;
}
if (rect.top < safeTop) {
panel.scrollTop -= Math.ceil(safeTop - rect.top);
}
}
function inspectClickTarget(node) {
if (!(node instanceof HTMLElement)) return null;
const rect = node.getBoundingClientRect();
const points = getClickProbePoints(rect);
if (!points.length) {
return {
safe: false,
reason: 'no-visible-point',
target: describeElement(node),
};
}
let fallback = null;
for (const point of points) {
const hit = document.elementFromPoint(point.x, point.y);
const hitElement = hit instanceof HTMLElement ? hit : hit?.parentElement;
const acceptsTarget = hitElement instanceof HTMLElement && isAcceptedGenerateHit(node, hitElement);
const adapterOverlay = hitElement instanceof HTMLElement && Boolean(hitElement.closest(`#${ROOT_ID}`));
const paymentHit = hitElement instanceof HTMLElement && (
isPaymentOrMembershipNode(hitElement) ||
hasPaymentOrMembershipAncestor(hitElement) ||
isInsidePaymentOrMembershipArea(hitElement)
);
const safe = acceptsTarget && !adapterOverlay && !paymentHit;
const result = {
safe,
reason: safe ? 'ok' : (paymentHit ? 'payment-hit' : (adapterOverlay ? 'adapter-overlay-hit' : 'obscured')),
point,
acceptsTarget,
adapterOverlay,
paymentHit,
insidePaymentArea: hitElement instanceof HTMLElement && isInsidePaymentOrMembershipArea(hitElement),
hit: describeElement(hitElement),
};
if (safe) return result;
if (!fallback || paymentHit) fallback = result;
}
return fallback;
}
function getClickProbePoints(rect) {
if (!rect || rect.width <= 0 || rect.height <= 0) return [];
const insetX = Math.min(18, Math.max(2, rect.width / 4));
const insetY = Math.min(18, Math.max(2, rect.height / 4));
const raw = [
[rect.left + rect.width / 2, rect.top + rect.height / 2],
[rect.left + insetX, rect.top + rect.height / 2],
[rect.right - insetX, rect.top + rect.height / 2],
[rect.left + rect.width / 2, rect.top + insetY],
[rect.left + rect.width / 2, rect.bottom - insetY],
];
return raw
.map(([x, y]) => ({ x: Math.round(x), y: Math.round(y) }))
.filter((point, index, list) =>
point.x >= 0 && point.x < window.innerWidth &&
point.y >= 0 && point.y < window.innerHeight &&
list.findIndex((item) => item.x === point.x && item.y === point.y) === index
);
}
function isAcceptedGenerateHit(target, hit) {
if (!(target instanceof HTMLElement) || !(hit instanceof HTMLElement)) return false;
if (target === hit || target.contains(hit)) return true;
const generateShell = target.closest?.('.generate-button-container, .ControlBottom_controlBottom__gooOU, .control-buttons');
if (generateShell instanceof HTMLElement && (generateShell === hit || generateShell.contains(hit) || hit.contains(generateShell))) {
return true;
}
return hit.contains(target) && isGenerateScopeBoundary(hit);
}
function auditGenerateTarget(node, options = {}) {
const element = describeElement(node);
const directPaymentNode = node instanceof HTMLElement && isPaymentOrMembershipNode(node);
const paymentAncestor = node instanceof HTMLElement && hasPaymentOrMembershipAncestor(node);
const insidePaymentArea = node instanceof HTMLElement && (
directPaymentNode ||
paymentAncestor ||
isInsidePaymentOrMembershipArea(node)
);
const click = node instanceof HTMLElement ? inspectClickTarget(node) : null;
const usable = node instanceof HTMLElement && isUsable(node);
const realGenerate = node instanceof HTMLElement &&
!node.closest(`#${ROOT_ID}, #${HOME_SHELL_ID}`) &&
GENERATE_TEXT_RE.test(normalizedText(node)) &&
!hasPaymentActionText(normalizedText(node)) &&
isGenerateClickable(node) &&
hasComposeConfigContext(node) &&
!insidePaymentArea &&
usable;
const unsafeClick = options.requireClickSafety === true && (!click || !click.safe);
return {
element,
disabled: isDisabledControl(node),
usable,
directPaymentNode,
paymentAncestor,
insidePaymentArea,
click,
allowed: realGenerate && !isDisabledControl(node) && !unsafeClick,
};
}
function describeGenerateCandidates(scope = document) {
try {
return collectGenerateCandidates(scope)
.map((node) => getGenerateClickTarget(node))
.filter((node, index, list) => node instanceof HTMLElement && list.indexOf(node) === index)
.slice(0, 12)
.map((node) => ({
element: describeElement(node),
score: scoreGenerateButton(node),
audit: {
disabled: isDisabledControl(node),
paymentActionText: hasPaymentActionText(normalizedText(node)),
directPaymentNode: isPaymentOrMembershipNode(node),
paymentAncestor: hasPaymentOrMembershipAncestor(node),
insidePaymentArea: isInsidePaymentOrMembershipArea(node),
click: inspectClickTarget(node),
composeContext: hasComposeConfigContext(node),
real: isRealGenerateButton(node),
},
}));
} catch (error) {
return [{ error: String(error?.message || error) }];
}
}
function isDisabledControl(node) {
if (!(node instanceof HTMLElement)) return false;
return Boolean(
node.disabled ||
node.getAttribute('disabled') !== null ||
node.getAttribute('aria-disabled') === 'true' ||
/disabled|not-allowed/i.test(String(node.className || ''))
);
}
function findResultImages() {
return [...document.images]
.map((image) => ({ image, rect: image.getBoundingClientRect(), src: image.currentSrc || image.src || '' }))
.filter(({ image, rect, src }) => {
if (!isUsable(image)) return false;
if (rect.width < 120 || rect.height < 120) return false;
if (/logo|avatar|profile|placeholder|icon|ic_|input-bg|bg-/i.test(src)) return false;
if (image.closest(`#${ROOT_ID}, #${HOME_SHELL_ID}, .picture-editor-header`)) return false;
return true;
})
.sort((a, b) => b.rect.width * b.rect.height - a.rect.width * a.rect.height)
.map(({ image }) => image);
}
async function waitForNewResultAfterGenerate(beforeCount) {
const waits = Array.from({ length: 40 }, (_, index) => index < 6 ? 800 : 1000);
for (const wait of waits) {
await delay(wait);
const paymentOverlay = findPaymentOrMembershipOverlay();
if (paymentOverlay) {
debugLog('generate:paymentAfterClick', {
overlay: describeElement(paymentOverlay),
text: normalizedText(paymentOverlay).slice(0, 500),
});
toast('原站返回升级/支付弹窗,请检查会员权限');
return false;
}
const images = findResultImages();
if (images.length > beforeCount) {
closeSurface();
revealResult(images[0]);
return true;
}
const text = normalizedText(document.body);
if (!/创作中|生成中|processing|creating/i.test(text) && images.length > 0) {
closeSurface();
revealResult(images[0]);
return true;
}
}
return false;
}
function findPaymentOrMembershipOverlay() {
return [...document.querySelectorAll([
'.ant-modal',
'.ant-drawer',
'.ant-popover',
'[class*="Modal"]',
'[class*="modal"]',
'[class*="Drawer"]',
'[class*="drawer"]',
'[class*="Popover"]',
'[class*="popover"]',
].join(','))]
.find((node) => node instanceof HTMLElement &&
isVisibleBox(node) &&
!node.closest(`#${ROOT_ID}, #${HOME_SHELL_ID}`) &&
isPaymentOrMembershipContainer(node)
) || null;
}
function revealResult(node) {
try {
node?.scrollIntoView?.({ block: 'center', inline: 'center', behavior: 'smooth' });
} catch (_) {
node?.scrollIntoView?.();
}
}
function focusPrompt(panel) {
const prompt = findPromptInput(panel);
if (!prompt) return;
prompt.scrollIntoView({ block: 'center' });
prompt.focus({ preventScroll: true });
}
function findHistoryRail() {
if (getPageType() !== 'editor') return null;
const knownRails = [...document.querySelectorAll([
'.picture-editor-content-left-drawer',
'.PictureEditorHistoryDrawer_container__fn3Ud',
'.TaskSide_record__JuEDU',
'[class*="History" i]',
'[class*="record" i]',
'[class*="Record" i]',
'[class*="task" i]',
'[class*="Task" i]',
'[class*="drawer" i]',
'[class*="Drawer" i]',
].join(','))]
.filter((node) => node instanceof HTMLElement)
.filter((node) => !node.closest(`#${ROOT_ID}, #${HOME_SHELL_ID}, .ant-drawer-body, [class*="MobileDrawer"]`));
for (const rail of knownRails) {
if (hasHistoryItems(rail) && looksLikeHistoryRail(rail)) return rail;
}
const images = [...document.images].filter((image) => {
const rect = image.getBoundingClientRect();
return rect.left < 160 && rect.width > 24 && rect.width < 130 && rect.height > 24;
});
for (const image of images) {
const rail = walkParents(image, (node) => {
const rect = node.getBoundingClientRect();
const imageCount = node.querySelectorAll?.('img').length || 0;
return imageCount >= 2 && rect.height > 220 && rect.width > 44 && rect.width < 190;
});
if (rail && hasHistoryItems(rail)) return rail;
}
return null;
}
function hasHistoryItems(rail) {
if (!rail) return false;
const mediaItems = [...rail.querySelectorAll?.('img, canvas, video') || []]
.filter((node) => {
if (!(node instanceof HTMLElement)) return false;
if (node instanceof HTMLCanvasElement) return node.width > 20 && node.height > 20;
if (node instanceof HTMLVideoElement) return Boolean(node.currentSrc || node.src || node.poster);
const src = node.currentSrc || node.src || '';
return Boolean(src) && !/logo|avatar|profile|placeholder|icon|ic_|input-bg|bg-/i.test(src);
});
if (mediaItems.length > 0) return true;
const linkItems = [...rail.querySelectorAll?.('a[href], [data-href], [data-url], [style*="background"]') || []]
.filter((node) => {
const signature = [
node.getAttribute?.('href'),
node.getAttribute?.('data-href'),
node.getAttribute?.('data-url'),
node.getAttribute?.('style'),
].join(' ');
return /work-detail|image|img|oss|cos|cdn|\.jpe?g|\.png|\.webp|\.heic/i.test(signature);
});
if (linkItems.length > 0) return true;
const nodes = [...rail.querySelectorAll?.('div, button, a, li') || []];
return nodes.some((node) => {
if (!(node instanceof HTMLElement)) return false;
const style = getComputedStyle(node);
const className = String(node.className || '');
const text = normalizedText(node);
const hasBg = Boolean(style.backgroundImage && style.backgroundImage !== 'none');
const taskLike = /history|record|task|item|card|work/i.test(className) &&
(node.onclick || node.getAttribute('role') === 'button' || node.querySelector('img, canvas, video, a[href]'));
return hasBg || taskLike || /已完成|生成|创作/.test(text) && elementArea(node) > 1200;
});
}
async function waitForHistoryRail() {
let rail = findHistoryRail();
if (rail) {
debugLog('history:found', describeHistoryRail(rail));
return rail;
}
const trigger = findHistoryTrigger();
if (trigger) {
debugLog('history:wakeTrigger', describeElement(trigger));
activate(trigger);
await delay(620);
rail = findHistoryRail();
if (rail) {
debugLog('history:foundAfterWake', describeHistoryRail(rail));
return rail;
}
}
await delay(320);
rail = findHistoryRail();
debugLog('history:final', describeHistoryRail(rail));
return rail;
}
function findHistoryTrigger() {
const pattern = /^(历史|记录|任务|生成记录|创作记录|History|Record|Tasks?)$/i;
return [...document.querySelectorAll('button, a, [role="button"], div, span, i')]
.filter((node) => !node.closest(`#${ROOT_ID}, #${HOME_SHELL_ID}, .ant-drawer-body, [class*="MobileDrawer"]`))
.map((node) => findClickableAncestor(node))
.filter((node, index, list) => node instanceof HTMLElement && list.indexOf(node) === index)
.find((node) => {
if (!isUsable(node)) return false;
const text = normalizedText(node);
const label = [node.getAttribute('aria-label'), node.getAttribute('title'), String(node.className || '')].join(' ');
return pattern.test(text) || /history|record|task/i.test(label);
}) || null;
}
function looksLikeHistoryRail(rail) {
if (!(rail instanceof HTMLElement)) return false;
const className = String(rail.className || '');
if (/picture-editor-content-left-drawer|PictureEditorHistoryDrawer|TaskSide_record/i.test(className)) return true;
const rect = rail.getBoundingClientRect();
const leftLike = rect.left <= Math.max(180, window.innerWidth * 0.48) || rect.width === 0;
return leftLike && /History|history|record|Record|task|Task|drawer|Drawer/i.test(className);
}
function prepareHistoryRailForMobile(rail) {
if (!(rail instanceof HTMLElement)) return;
rail.classList.add(CLASS.history);
let parent = rail.parentElement;
while (parent && parent !== document.body && parent.id !== 'root' && parent.id !== 'haimeta') {
const style = getComputedStyle(parent);
if (style.display === 'none' || style.visibility === 'hidden') {
parent.classList.add(CLASS.historyParent);
}
parent = parent.parentElement;
}
}
function describeHistoryRail(rail) {
if (!(rail instanceof HTMLElement)) return null;
const rect = rail.getBoundingClientRect();
const parentHidden = getHiddenAncestor(rail);
const mediaCount = rail.querySelectorAll?.('img, canvas, video').length || 0;
const clickableCount = rail.querySelectorAll?.('button, a, [role="button"], [onclick], [data-href], [data-url]').length || 0;
return {
element: describeElement(rail),
rect: {
x: Math.round(rect.x),
y: Math.round(rect.y),
width: Math.round(rect.width),
height: Math.round(rect.height),
},
mediaCount,
clickableCount,
hasItems: hasHistoryItems(rail),
hiddenAncestor: parentHidden ? describeElement(parentHidden) : null,
scrollHeight: rail.scrollHeight || 0,
clientHeight: rail.clientHeight || 0,
};
}
function getHiddenAncestor(node) {
let parent = node?.parentElement;
while (parent && parent !== document.body) {
const style = getComputedStyle(parent);
if (style.display === 'none' || style.visibility === 'hidden') return parent;
parent = parent.parentElement;
}
return null;
}
function findDownloadControl() {
const labeled = [
'[title*="下载"]',
'[aria-label*="下载"]',
'[download]',
'[class*="download" i]',
].flatMap((selector) => [...document.querySelectorAll(selector)]);
const byText = [...document.querySelectorAll('button, a, [role="button"], div, span')].filter((node) =>
normalizedText(node).includes('下载')
);
const byRightPreviewIcon = [...document.querySelectorAll('button, a, [role="button"], i, span')]
.filter(isLikelyPreviewDownload);
return [...labeled, ...byText, ...byRightPreviewIcon]
.map((node) => findClickableAncestor(node))
.find((node) => node instanceof HTMLElement && isUsable(node) && !node.closest(`#${ROOT_ID}, #${HOME_SHELL_ID}`) &&
!/上传|上传参考图|上传图片/.test(normalizedText(node)));
}
function findPrimaryResult() {
return [...document.images]
.map((image) => ({ image, rect: image.getBoundingClientRect() }))
.filter(({ image, rect }) => {
const src = image.currentSrc || image.src || '';
return isUsable(image) && rect.width > 150 && rect.height > 150 &&
!src.includes('logo') && !src.includes('bg-') &&
!image.closest(`#${ROOT_ID}, #${HOME_SHELL_ID}, .picture-editor-header`);
})
.sort((a, b) => b.rect.width * b.rect.height - a.rect.width * a.rect.height)
.map(({ image }) => image)[0];
}
function isLikelyPreviewDownload(node) {
const rect = node.getBoundingClientRect();
if (!isUsable(node) || rect.left < window.innerWidth * 0.62 || rect.top > window.innerHeight * 0.66) {
return false;
}
const signature = [
node.className,
node.getAttribute('title'),
node.getAttribute('aria-label'),
node.innerHTML,
].join(' ');
return /download|xiazai|icon-download||/i.test(signature) &&
!normalizedText(node).includes('上传');
}
function hasVisiblePreviewModal() {
return [...document.querySelectorAll([
'.ant-modal',
'[class*="Preview"]',
'[class*="preview"]',
'[class*="Viewer"]',
'[class*="viewer"]',
'[class*="Modal"]',
'[class*="modal"]',
].join(','))]
.some((node) => {
if (!(node instanceof HTMLElement) || node.closest(`#${ROOT_ID}, #${HOME_SHELL_ID}`)) return false;
if (!isUsable(node)) return false;
const text = normalizedText(node);
if (/欢迎来到|手机号登录|邮箱登录|请输入手机号码/.test(text)) return false;
const rect = node.getBoundingClientRect();
const hasLargeImage = [...node.querySelectorAll('img, canvas, video')].some((item) => {
const itemRect = item.getBoundingClientRect();
return itemRect.width > 120 && itemRect.height > 120;
});
return hasLargeImage && (rect.width > window.innerWidth * 0.45 || rect.height > window.innerHeight * 0.45);
});
}
function findClickableByText(scope, text) {
if (!scope) return null;
return [...scope.querySelectorAll('button, a, label, [role="button"]')]
.find((node) => normalizedText(node).includes(text) && isUsable(node));
}
function walkParents(start, predicate) {
let node = start;
for (let depth = 0; node && depth < 18; depth += 1, node = node.parentElement) {
if (predicate(node)) return node;
}
return null;
}
function normalizedText(node) {
return (node?.innerText || node?.textContent || '').replace(/\s+/g, ' ').trim();
}
function isUsable(node) {
if (!node || node.closest?.(`#${ROOT_ID}`)) return false;
return isVisibleBox(node);
}
function isVisibleBox(node) {
if (!(node instanceof HTMLElement)) return false;
const rect = node.getBoundingClientRect();
const style = getComputedStyle(node);
return rect.width > 0 && rect.height > 0 &&
style.visibility !== 'hidden' && style.display !== 'none';
}
function activateGenerateButton(node, audit) {
if (!(node instanceof HTMLElement)) return false;
const target = node.closest?.('button, a, label, [role="button"], [role="menuitem"]') || node;
const finalAudit = audit?.element?.domPath === describeElement(node)?.domPath
? audit
: auditGenerateTarget(node, { requireClickSafety: true });
if (!finalAudit.allowed) {
debugLog('activateGenerate:blocked', finalAudit);
return false;
}
debugLog('activateGenerate', {
target: describeElement(target),
audit: finalAudit,
});
try {
target.scrollIntoView?.({ block: 'center', inline: 'center', behavior: 'auto' });
} catch (_) {
target.scrollIntoView?.();
}
target.focus?.({ preventScroll: true });
target.click?.();
return true;
}
function activate(node) {
if (!node) return false;
const target = node.closest?.('button, a, label, [role="button"], [role="menuitem"]') || node;
debugLog('activate', describeElement(target));
try {
target.scrollIntoView?.({ block: 'center', inline: 'center', behavior: 'auto' });
} catch (_) {
// keep going; old mobile browsers can reject scroll options
}
target.focus?.({ preventScroll: true });
target.click?.();
return true;
}
function debugLog(label, data) {
if (window.__ZBAI_DEBUG__ !== true) return;
console.debug('[ZBAI mobile]', label, data);
}
function collectDiagnostics() {
const composePanel = findComposePanel();
const uploadControl = composePanel ? findUploadControl(composePanel) : null;
const generateButton = findGenerateButton(composePanel || document);
const historyRail = findHistoryRail();
const downloadButton = findDownloadControl();
const loginButton = findLoginButton();
const logoutButton = findLogoutButton();
const profileAvatar = findProfileAvatar();
const mobileMenuButton = findMobileMenuButton();
const paymentOverlay = findPaymentOrMembershipOverlay();
return {
version: VERSION,
href: window.location.href,
pageType: document.documentElement.dataset.zbaiPage || getPageType(),
mobile: isMobile(),
activeSurface,
found: {
composePanel: Boolean(composePanel),
uploadControl: Boolean(uploadControl),
generateButton: Boolean(generateButton),
historyRail: Boolean(historyRail),
downloadButton: Boolean(downloadButton),
loginButton: Boolean(loginButton),
logoutButton: Boolean(logoutButton),
mobileMenuButton: Boolean(mobileMenuButton),
profileAvatar: Boolean(profileAvatar),
loggedIn: isLoggedIn(),
paymentOverlay: Boolean(paymentOverlay),
},
elements: {
composePanel: describeElement(composePanel),
uploadControl: describeElement(uploadControl),
generateButton: describeElement(generateButton),
historyRail: describeElement(historyRail),
downloadButton: describeElement(downloadButton),
loginButton: describeElement(loginButton),
logoutButton: describeElement(logoutButton),
mobileMenuButton: describeElement(mobileMenuButton),
profileAvatar: describeElement(profileAvatar),
paymentOverlay: describeElement(paymentOverlay),
},
debug: {
generateAudit: auditGenerateTarget(generateButton),
generateClickAudit: auditGenerateTarget(generateButton, { requireClickSafety: true }),
generateCandidates: describeGenerateCandidates(composePanel || document),
historyRail: describeHistoryRail(historyRail),
},
};
}
function describeElement(node) {
if (!(node instanceof HTMLElement)) return null;
const rect = node.getBoundingClientRect();
const style = getComputedStyle(node);
return {
tag: node.tagName.toLowerCase(),
id: node.id || '',
className: String(node.className || '').slice(0, 120),
text: normalizedText(node).slice(0, 80),
innerText: (node.innerText || '').replace(/\s+/g, ' ').trim().slice(0, 160),
ariaLabel: node.getAttribute('aria-label') || '',
title: node.getAttribute('title') || '',
domPath: getDomPath(node),
disabled: isDisabledControl(node),
pointerEvents: style.pointerEvents,
rect: {
x: Math.round(rect.x),
y: Math.round(rect.y),
width: Math.round(rect.width),
height: Math.round(rect.height),
},
};
}
function getDomPath(node) {
if (!(node instanceof HTMLElement)) return '';
const parts = [];
let current = node;
for (let depth = 0; current && current instanceof HTMLElement && depth < 8; depth += 1) {
let part = current.tagName.toLowerCase();
if (current.id) part += `#${current.id}`;
const className = String(current.className || '')
.split(/\s+/)
.filter(Boolean)
.slice(0, 3)
.map((name) => `.${name}`)
.join('');
part += className;
parts.unshift(part);
current = current.parentElement;
}
return parts.join(' > ');
}
function ensureMobileHomeShell() {
let shell = document.getElementById(HOME_SHELL_ID);
if (!shell) {
shell = document.createElement('main');
shell.id = HOME_SHELL_ID;
shell.innerHTML = `
`;
shell.addEventListener('click', handleHomeShellClick);
document.body.appendChild(shell);
}
updateMobileHomeAuth(shell);
renderMobileHomeActions(shell);
}
function cleanupMobileHomeShell() {
document.getElementById(HOME_SHELL_ID)?.remove();
}
function ensureWorkDetailFallback() {
if (!isWorkDetailRoute()) {
cleanupWorkDetailFallback();
return;
}
const imageUrl = getWorkDetailImageUrl();
if (!imageUrl) {
cleanupWorkDetailFallback();
return;
}
let fallback = document.getElementById(WORK_DETAIL_FALLBACK_ID);
if (!fallback) {
fallback = document.createElement('section');
fallback.id = WORK_DETAIL_FALLBACK_ID;
fallback.innerHTML = `
`;
fallback.querySelector('[data-zbai-work-back]')?.addEventListener('click', () => {
if (window.history.length > 1) window.history.back();
else navigateTo(ASSETS_URL);
});
fallback.querySelector('[data-zbai-work-download]')?.addEventListener('click', async () => {
await downloadPreparedWorkDetail(fallback);
});
document.body.appendChild(fallback);
}
const image = fallback.querySelector('img');
if (image && image.src !== imageUrl) image.src = imageUrl;
if (fallback.dataset.sourceUrl !== imageUrl) {
if (fallback.dataset.downloadUrl) URL.revokeObjectURL(fallback.dataset.downloadUrl);
fallback.dataset.sourceUrl = imageUrl;
delete fallback.dataset.downloadUrl;
delete fallback.dataset.downloadName;
prepareWorkDetailDownload(fallback, imageUrl);
}
}
function cleanupWorkDetailFallback() {
const fallback = document.getElementById(WORK_DETAIL_FALLBACK_ID);
if (fallback?.dataset.downloadUrl) URL.revokeObjectURL(fallback.dataset.downloadUrl);
fallback?.remove();
}
function getWorkDetailImageUrl() {
try {
const url = new URL(location.href);
return url.searchParams.get('url') || url.searchParams.get('imageUrl') || '';
} catch (_) {
return '';
}
}
async function downloadImageUrl(url) {
if (!url) {
toast('没找到可下载的图片');
return;
}
try {
const response = await fetch(url, { mode: 'cors', credentials: 'omit' });
if (!response.ok) throw new Error(`download ${response.status}`);
const blob = await response.blob();
const objectUrl = URL.createObjectURL(blob);
triggerDownload(objectUrl, `zbai-${Date.now()}.${guessImageExtension(blob.type, url)}`);
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 4000);
return;
} catch (_) {
triggerDownload(url, `zbai-${Date.now()}.jpg`);
window.open(url, '_blank', 'noopener');
}
}
async function prepareWorkDetailDownload(fallback, url) {
try {
const response = await fetch(url, { mode: 'cors', credentials: 'omit' });
if (!response.ok) throw new Error(`download ${response.status}`);
const blob = await response.blob();
if (fallback.dataset.sourceUrl !== url) return;
const objectUrl = URL.createObjectURL(blob);
if (fallback.dataset.downloadUrl) URL.revokeObjectURL(fallback.dataset.downloadUrl);
fallback.dataset.downloadUrl = objectUrl;
fallback.dataset.downloadName = `zbai-${Date.now()}.${guessImageExtension(blob.type, url)}`;
} catch (_) {
// The click handler will fall back to the original URL.
}
}
async function downloadPreparedWorkDetail(fallback) {
const preparedUrl = fallback?.dataset.downloadUrl;
if (preparedUrl) {
triggerDownload(preparedUrl, fallback.dataset.downloadName || `zbai-${Date.now()}.jpg`);
return;
}
await downloadImageUrl(getWorkDetailImageUrl());
}
function triggerDownload(url, filename) {
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.rel = 'noopener';
document.body.appendChild(link);
link.click();
link.remove();
}
function guessImageExtension(mimeType, url) {
if (/png/i.test(mimeType)) return 'png';
if (/webp/i.test(mimeType)) return 'webp';
if (/gif/i.test(mimeType)) return 'gif';
const match = String(url || '').split('?')[0].match(/\.([a-z0-9]{3,5})$/i);
return match?.[1] || 'jpg';
}
function handleHomeShellClick(event) {
const actionButton = event.target.closest('[data-home-action]');
if (actionButton) {
event.preventDefault();
event.stopPropagation();
runHomeAction(actionButton.getAttribute('data-home-action'));
return;
}
const toolButton = event.target.closest('[data-tool-key]');
if (toolButton) {
event.preventDefault();
event.stopPropagation();
openHomeTool(toolButton.getAttribute('data-tool-key'));
return;
}
const navButton = event.target.closest('[data-href]');
if (!navButton) return;
event.preventDefault();
event.stopPropagation();
navigateTo(navButton.getAttribute('data-href'));
}
function updateMobileHomeAuth(shell) {
const loggedIn = isLoggedIn();
const loginButton = shell.querySelector('[data-home-action="login"]');
const logoutButton = shell.querySelector('[data-home-action="logout"]');
if (loginButton) loginButton.hidden = loggedIn;
if (logoutButton) logoutButton.hidden = !loggedIn;
}
function renderMobileHomeActions(shell) {
const toolGrid = shell.querySelector('.zbai-mobile-tool-grid');
const templateGrid = shell.querySelector('.zbai-mobile-template-grid');
if (toolGrid && toolGrid.dataset.ready !== '1') {
toolGrid.innerHTML = HOME_TOOL_ACTIONS.map((item) => `
`).join('');
toolGrid.dataset.ready = '1';
}
if (templateGrid && templateGrid.dataset.ready !== '1') {
templateGrid.innerHTML = TEMPLATE_ACTIONS.map((item) => `
`).join('');
templateGrid.dataset.ready = '1';
}
}
function runHomeAction(action) {
if (action === 'login') {
openLogin();
return;
}
if (action === 'logout') {
openLogout();
return;
}
if (action === 'assets' || action === 'history') {
navigateTo(ASSETS_URL);
return;
}
if (action === 'menu') {
activate(findMobileMenuButton());
}
}
function openHomeTool(key) {
const action = HOME_TOOL_ACTIONS.find((item) => item.key === key);
if (action) navigateTo(action.href);
}
function findOriginalMenuButton() {
return findMobileMenuButton();
}
function findMobileMenuButton() {
const candidates = [...document.querySelectorAll('button, a, [role="button"], [aria-label], [title], i, span, div')]
.filter((node) => node instanceof HTMLElement && !node.closest(`#${ROOT_ID}, #${HOME_SHELL_ID}`))
.map((node) => findClickableAncestor(node))
.filter((node, index, list) => node instanceof HTMLElement && list.indexOf(node) === index && isUsable(node));
const scored = candidates.map((node) => {
const rect = node.getBoundingClientRect();
const text = normalizedText(node);
const label = [
node.getAttribute('aria-label'),
node.getAttribute('title'),
String(node.className || ''),
].join(' ');
let score = 0;
if (/^(☰|三|菜单|Menu)$/i.test(text) || /菜单|menu|hamburger|ic_menu|icon-menu/i.test(label)) score += 90;
if (/iconfont/i.test(label) && /menu|caidan|drawer|nav/i.test(label)) score += 40;
if (rect.left >= 0 && rect.left < 92 && rect.top >= 0 && rect.top < 96) score += 36;
if (rect.width >= 20 && rect.width <= 64 && rect.height >= 20 && rect.height <= 64) score += 18;
if (/Log In|Log Out|登录|退出|升级|资产|历史/.test(text)) score -= 80;
return { node, score };
}).filter((item) => item.score > 45)
.sort((a, b) => b.score - a.score);
return scored[0]?.node || null;
}
function findOpenMobileDrawer() {
return [...document.querySelectorAll('.ant-drawer-body, [class*="MobileDrawer"]')]
.find((node) => node instanceof HTMLElement && isUsable(node) &&
/Log Out|Logout|退出|升级|从这开始|图片创作|资产/.test(normalizedText(node))) || null;
}
function navigateTo(path) {
if (!path) return;
const target = new URL(path, window.location.origin).href;
if (window.location.href === target) return;
window.location.assign(target);
}
function escapeHtml(value) {
return String(value).replace(/[&<>"']/g, (char) => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
}[char]));
}
function toast(message) {
const box = document.querySelector(`#${ROOT_ID} .zbai-mobile-toast`);
if (!box) return;
box.textContent = message;
box.classList.add('is-visible');
window.clearTimeout(toastTimer);
toastTimer = window.setTimeout(() => box.classList.remove('is-visible'), 2600);
}
function ensureViewport() {
if (!isMobile()) return;
let viewport = document.querySelector('meta[name="viewport"]');
if (!viewport) {
viewport = document.createElement('meta');
viewport.name = 'viewport';
(document.head || document.documentElement).appendChild(viewport);
}
viewport.setAttribute('content', 'width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover');
}
function getPageType() {
if (location.pathname === ASSETS_URL) return 'assets';
if (isWorkDetailRoute()) return 'assets';
if (document.querySelector(EDITOR_ROOT_SELECTOR)) return 'editor';
if (isCreateRoute()) return 'editor';
return 'home';
}
function updatePageType() {
document.documentElement.dataset.zbaiPage = getPageType();
}
function isCreateRoute() {
return location.pathname.startsWith('/create/') || location.pathname.startsWith('/effects/');
}
function isTemplateRoute() {
return location.pathname.startsWith('/create/ai-effects/') || location.pathname.startsWith('/effects/');
}
function isWorkDetailRoute() {
return /\/(?:mobile\/)?work-detail/.test(location.pathname);
}
function isMobile() {
const physicalWidth = Math.min(window.screen.width || window.innerWidth, window.screen.height || window.innerHeight);
const hasTouch = navigator.maxTouchPoints > 0 || 'ontouchstart' in window;
return media.matches || window.innerWidth <= 768 || (hasTouch && physicalWidth <= 820);
}
function delay(ms) {
return new Promise((resolve) => window.setTimeout(resolve, ms));
}
function throttle(callback, wait) {
let timer = 0;
return () => {
if (timer) return;
timer = window.setTimeout(() => {
timer = 0;
callback();
}, wait);
};
}
function waitForBody(callback) {
if (document.body) {
callback();
return;
}
let done = false;
const run = () => {
if (done || !document.body) return;
done = true;
window.clearInterval(timer);
callback();
};
const timer = window.setInterval(run, 50);
window.setTimeout(() => window.clearInterval(timer), 8000);
document.addEventListener('DOMContentLoaded', run, { once: true });
window.addEventListener('load', run, { once: true });
}
function injectStyles() {
let style = document.getElementById(STYLE_ID);
const shouldAppend = !style;
if (!style) style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = `
@media (max-width: 768px), (pointer: coarse), (max-device-width: 820px) {
html[data-zbai-mobile="1"],
html[data-zbai-mobile="1"] body#haimeta {
width: 100vw !important;
min-width: 0 !important;
min-height: 100dvh !important;
overflow-x: hidden !important;
-webkit-text-size-adjust: 100% !important;
}
html[data-zbai-mobile="1"][data-zbai-page="editor"],
html[data-zbai-mobile="1"][data-zbai-page="editor"] body#haimeta {
height: 100dvh !important;
overflow: hidden !important;
overscroll-behavior: none !important;
}
html[data-zbai-mobile="1"] #haimeta *,
html[data-zbai-mobile="1"] #haimeta *::before,
html[data-zbai-mobile="1"] #haimeta *::after {
box-sizing: border-box !important;
}
html[data-zbai-mobile="1"] img,
html[data-zbai-mobile="1"] video,
html[data-zbai-mobile="1"] canvas {
opacity: 1 !important;
filter: none !important;
-webkit-filter: none !important;
mix-blend-mode: normal !important;
background-blend-mode: normal !important;
image-rendering: auto !important;
}
body.${CLASS.enabled}.${CLASS.lock} {
overflow: hidden !important;
}
html[data-zbai-mobile="1"][data-zbai-page="editor"] #root,
html[data-zbai-mobile="1"][data-zbai-page="editor"] .PageContainer_haimetaApp__qfjGd,
html[data-zbai-mobile="1"][data-zbai-page="editor"] .PageContainer_indexContent__5aBHJ,
html[data-zbai-mobile="1"][data-zbai-page="editor"] #pictureEditor {
width: 100vw !important;
min-width: 0 !important;
max-width: 100vw !important;
height: 100dvh !important;
min-height: 100dvh !important;
overflow: hidden !important;
}
html[data-zbai-mobile="1"][data-zbai-page="editor"] #pictureEditor {
display: flex !important;
flex-direction: column !important;
transform: none !important;
margin: 0 !important;
left: 0 !important;
right: auto !important;
}
html[data-zbai-mobile="1"] .picture-editor-header {
height: calc(52px + env(safe-area-inset-top, 0px)) !important;
min-height: calc(52px + env(safe-area-inset-top, 0px)) !important;
padding-top: env(safe-area-inset-top, 0px) !important;
position: sticky !important;
top: 0 !important;
z-index: 80 !important;
background: var(--Background-1-Normal, #f4f5fb) !important;
}
html[data-zbai-mobile="1"] #picture-editor-header {
height: 52px !important;
min-height: 52px !important;
padding: 0 10px !important;
display: grid !important;
grid-template-columns: minmax(0, 1fr) auto !important;
align-items: center !important;
gap: 8px !important;
}
html[data-zbai-mobile="1"] .picture-editor-header .center {
display: none !important;
}
html[data-zbai-mobile="1"] .picture-editor-header .left {
width: auto !important;
min-width: 0 !important;
max-width: 100% !important;
position: static !important;
transform: none !important;
overflow: hidden !important;
}
html[data-zbai-mobile="1"] .picture-editor-header .left img {
max-height: 30px !important;
width: auto !important;
}
html[data-zbai-mobile="1"] .picture-editor-header .right {
width: auto !important;
min-width: 0 !important;
max-width: 46vw !important;
position: static !important;
display: flex !important;
align-items: center !important;
justify-content: flex-end !important;
gap: 8px !important;
overflow: hidden !important;
}
html[data-zbai-mobile="1"] .picture-editor-header .right .login-btn {
white-space: nowrap !important;
}
html[data-zbai-mobile="1"] .picture-editor-header .right button {
min-width: 0 !important;
height: 32px !important;
padding: 0 10px !important;
white-space: nowrap !important;
}
html[data-zbai-mobile="1"] .picture-editor-content {
height: calc(100dvh - 52px - env(safe-area-inset-top, 0px)) !important;
width: 100vw !important;
min-width: 0 !important;
display: flex !important;
flex-direction: column !important;
overflow: hidden !important;
position: relative !important;
transform: none !important;
margin: 0 !important;
padding: 0 !important;
left: 0 !important;
right: auto !important;
}
html[data-zbai-mobile="1"] .picture-editor-content-left-drawer,
html[data-zbai-mobile="1"] .PictureEditorHistoryDrawer_container__fn3Ud,
html[data-zbai-mobile="1"] .TaskSide_record__JuEDU {
display: block !important;
visibility: hidden !important;
position: fixed !important;
z-index: 2147483601 !important;
inset: 0 auto 0 0 !important;
width: min(40vw, 148px) !important;
min-width: 92px !important;
height: 100dvh !important;
margin: 0 !important;
padding: 12px 8px calc(92px + env(safe-area-inset-bottom)) !important;
border-radius: 0 18px 18px 0 !important;
overflow: auto !important;
overscroll-behavior: contain !important;
pointer-events: none !important;
background: #f8f8fb !important;
box-shadow: 10px 0 34px rgba(19, 24, 33, .22) !important;
transform: translateX(-112%) !important;
-webkit-overflow-scrolling: touch !important;
}
html[data-zbai-mobile="1"] .picture-editor-content-box {
width: 100vw !important;
min-width: 0 !important;
height: 100% !important;
margin: 0 !important;
left: auto !important;
right: auto !important;
transform: none !important;
display: flex !important;
flex-direction: column !important;
overflow: hidden !important;
position: relative !important;
}
html[data-zbai-mobile="1"] .picture-editor-content-render {
width: 100vw !important;
min-width: 0 !important;
flex: 0 0 auto !important;
height: 38dvh !important;
min-height: 180px !important;
max-height: 42dvh !important;
padding: 6px 10px !important;
margin: 0 !important;
left: 0 !important;
right: auto !important;
transform: none !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
overflow: auto !important;
-webkit-overflow-scrolling: touch !important;
}
html[data-zbai-mobile="1"] .picture-editor-content-render > * {
max-width: 100% !important;
max-height: 100% !important;
}
html[data-zbai-mobile="1"] .picture-editor-content-control {
width: 100vw !important;
min-width: 0 !important;
max-width: 100vw !important;
flex: 1 1 auto !important;
height: auto !important;
min-height: 0 !important;
margin: 0 !important;
left: 0 !important;
right: auto !important;
transform: none !important;
padding: 12px 12px calc(92px + env(safe-area-inset-bottom, 0px)) !important;
overflow-y: auto !important;
-webkit-overflow-scrolling: touch !important;
border-radius: 18px 18px 0 0 !important;
background: var(--Background-2-Normal, #f1f2f8) !important;
box-shadow: 0 -10px 30px rgba(20, 24, 38, 0.08) !important;
}
html[data-zbai-mobile="1"] .PictureEditorControl_PictureEditorControl__T6FHN {
width: 100% !important;
min-width: 0 !important;
height: auto !important;
padding: 0 !important;
}
html[data-zbai-mobile="1"] .control-config {
width: 100% !important;
height: auto !important;
display: flex !important;
flex-direction: column !important;
gap: 12px !important;
}
html[data-zbai-mobile="1"] .control-config > div,
html[data-zbai-mobile="1"] .option-need-margin {
width: 100% !important;
margin: 0 !important;
}
html[data-zbai-mobile="1"] .control-config-title {
width: 100% !important;
height: auto !important;
min-height: 24px !important;
margin: 0 0 8px !important;
line-height: 24px !important;
position: relative !important;
z-index: 1 !important;
white-space: normal !important;
word-break: keep-all !important;
transform: none !important;
translate: none !important;
}
html[data-zbai-mobile="1"] #multifunctional-input,
html[data-zbai-mobile="1"] .MultifunctionalInput_multifunctionalInput__NHcuq,
html[data-zbai-mobile="1"] .Input_input__A9sVA,
html[data-zbai-mobile="1"] .input-box,
html[data-zbai-mobile="1"] textarea.textarea-buttons,
html[data-zbai-mobile="1"] textarea.ant-input {
width: 100% !important;
min-width: 0 !important;
}
html[data-zbai-mobile="1"] textarea.textarea-buttons,
html[data-zbai-mobile="1"] textarea.ant-input {
min-height: 92px !important;
max-height: 160px !important;
font-size: 16px !important;
line-height: 1.45 !important;
}
html[data-zbai-mobile="1"] .MultifunctionalInput_multifunctionalInput__NHcuq .bg-1,
html[data-zbai-mobile="1"] .MultifunctionalInput_multifunctionalInput__NHcuq .bg-2 {
display: none !important;
}
html[data-zbai-mobile="1"] .ChooseModelWithDetails_ChooseModelWithDetails__m_Cbs,
html[data-zbai-mobile="1"] .ChooseModelWithDetails_selectBox__HoIuc,
html[data-zbai-mobile="1"] .ChooseModelWithDetails_selectItem__7NA37 {
width: 100% !important;
min-height: 56px !important;
height: auto !important;
}
html[data-zbai-mobile="1"] .ChooseSizeBase_chooseSizeBase__KgHNI,
html[data-zbai-mobile="1"] .choose-size-base-shape {
width: 100% !important;
overflow-x: auto !important;
overflow-y: hidden !important;
display: flex !important;
gap: 8px !important;
-webkit-overflow-scrolling: touch !important;
scroll-snap-type: x proximity !important;
}
html[data-zbai-mobile="1"] .choose-size-base-shape > div,
html[data-zbai-mobile="1"] .shape-item,
html[data-zbai-mobile="1"] .shape-item .item {
flex: 0 0 64px !important;
width: 64px !important;
height: 60px !important;
scroll-snap-align: start !important;
}
html[data-zbai-mobile="1"] .ControlBottom_controlBottom__gooOU,
html[data-zbai-mobile="1"] .generate-button-container {
width: 100% !important;
height: auto !important;
margin-top: 10px !important;
position: relative !important;
bottom: auto !important;
z-index: 2 !important;
transform: none !important;
box-shadow: none !important;
background: var(--Background-2-Normal, #f1f2f8) !important;
pointer-events: auto !important;
touch-action: manipulation !important;
}
html[data-zbai-mobile="1"] .ChooseNumber_ChooseNumber__J1uo_,
html[data-zbai-mobile="1"] .choose-number-content {
width: 100% !important;
}
html[data-zbai-mobile="1"] .choose-number-content {
display: grid !important;
grid-template-columns: auto minmax(120px, 1fr) !important;
align-items: center !important;
gap: 12px !important;
}
html[data-zbai-mobile="1"] .choose-number-options {
justify-self: end !important;
width: min(160px, 48vw) !important;
}
html[data-zbai-mobile="1"] .generate-button-container,
html[data-zbai-mobile="1"] .control-buttons {
width: 100% !important;
min-height: 50px !important;
pointer-events: auto !important;
touch-action: manipulation !important;
}
html[data-zbai-mobile="1"] .generate-button-container button {
width: 100% !important;
min-height: 52px !important;
border-radius: 16px !important;
background: #11120d !important;
color: #fff !important;
box-shadow: 0 14px 34px rgba(17, 18, 13, 0.18) !important;
font-size: 15px !important;
pointer-events: auto !important;
touch-action: manipulation !important;
}
html[data-zbai-mobile="1"] .fixed.top-0.left-0.w-screen.h-screen {
padding: env(safe-area-inset-top, 0px) 12px env(safe-area-inset-bottom, 0px) !important;
align-items: center !important;
}
html[data-zbai-mobile="1"] .LoginModal_LoginModal__O59AS,
html[data-zbai-mobile="1"] [class*="LoginModal"],
html[data-zbai-mobile="1"] [class*="OverseasLogin_overseasLogin"],
html[data-zbai-mobile="1"] [class*="overseasLoginModal"] {
width: calc(100vw - 24px) !important;
max-width: 420px !important;
min-width: 0 !important;
max-height: calc(100dvh - 48px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px)) !important;
overflow-y: auto !important;
border-radius: 18px !important;
}
html[data-zbai-mobile="1"][data-zbai-login-open="1"] .fixed.top-0.left-0.w-screen.h-screen,
html[data-zbai-mobile="1"][data-zbai-login-open="1"] .LoginModal_LoginModal__O59AS,
html[data-zbai-mobile="1"][data-zbai-login-open="1"] [class*="LoginModal"],
html[data-zbai-mobile="1"][data-zbai-login-open="1"] [class*="OverseasLogin_overseasLogin"],
html[data-zbai-mobile="1"][data-zbai-login-open="1"] [class*="overseasLoginModal"] {
display: flex !important;
visibility: visible !important;
opacity: 1 !important;
pointer-events: auto !important;
}
html[data-zbai-mobile="1"][data-zbai-page="home"] #root {
display: none !important;
}
html[data-zbai-mobile="1"][data-zbai-page="home"][data-zbai-login-open="1"] #root {
display: block !important;
}
html[data-zbai-mobile="1"][data-zbai-page="home"][data-zbai-login-open="1"] #${HOME_SHELL_ID} {
display: none !important;
}
html[data-zbai-mobile="1"][data-zbai-page="home"][data-zbai-native-menu-open="1"] #root {
display: block !important;
}
html[data-zbai-mobile="1"][data-zbai-page="home"][data-zbai-native-menu-open="1"] #${HOME_SHELL_ID} {
display: none !important;
}
html[data-zbai-mobile="1"][data-zbai-page="home"] body.${CLASS.enabled} #${ROOT_ID} .zbai-mobile-auth {
display: none !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} {
display: block !important;
position: fixed !important;
inset: 0 !important;
z-index: 10025 !important;
width: 100vw !important;
height: 100dvh !important;
min-height: 100dvh !important;
padding: calc(12px + env(safe-area-inset-top, 0px)) 10px calc(86px + env(safe-area-inset-bottom, 0px)) !important;
background: linear-gradient(180deg, #fbfaf8 0%, #eef2f6 100%) !important;
color: #111318 !important;
overflow-x: hidden !important;
overflow-y: auto !important;
scroll-padding-bottom: calc(86px + env(safe-area-inset-bottom, 0px)) !important;
overscroll-behavior-y: contain !important;
box-sizing: border-box !important;
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
-webkit-overflow-scrolling: touch !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} * {
box-sizing: border-box !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-topbar {
display: grid !important;
grid-template-columns: 34px minmax(0, 1fr) auto !important;
align-items: center !important;
gap: 8px !important;
min-height: 38px !important;
margin-bottom: 16px !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-menu-button {
width: 32px !important;
height: 32px !important;
border: 0 !important;
border-radius: 16px !important;
display: grid !important;
place-items: center !important;
padding: 0 !important;
background: rgba(255, 255, 255, 0.94) !important;
color: #17191f !important;
box-shadow: 0 8px 22px rgba(21, 24, 33, 0.08) !important;
font: 900 18px/32px -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
touch-action: manipulation !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-brand {
min-width: 0 !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
color: #111318 !important;
font: 900 13px/18px -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
letter-spacing: 0 !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-auth-pills {
display: inline-flex !important;
align-items: center !important;
gap: 5px !important;
min-height: 34px !important;
padding: 3px !important;
border-radius: 18px !important;
background: rgba(255, 255, 255, 0.82) !important;
box-shadow: 0 10px 28px rgba(19, 24, 33, 0.12) !important;
backdrop-filter: blur(14px) !important;
-webkit-backdrop-filter: blur(14px) !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-pill-button {
min-width: 46px !important;
height: 28px !important;
border: 0 !important;
border-radius: 14px !important;
padding: 0 11px !important;
background: #eef0f5 !important;
color: #202333 !important;
font: 800 12px/28px -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
white-space: nowrap !important;
touch-action: manipulation !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-pill-button[data-home-action="login"],
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-pill-button[data-home-action="logout"] {
background: #050605 !important;
color: #fff !important;
box-shadow: 0 8px 18px rgba(5, 6, 5, 0.18) !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-section-title {
margin: 12px 0 10px !important;
display: flex !important;
align-items: baseline !important;
justify-content: space-between !important;
gap: 8px !important;
width: 100% !important;
min-height: 30px !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-section-title strong {
color: #141722 !important;
font: 900 20px/30px -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
letter-spacing: 0 !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-section-title small {
color: #7a808e !important;
font: 700 11px/18px -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-tool-grid,
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-template-grid {
display: grid !important;
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
gap: 10px !important;
width: 100% !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-tool-card,
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-template-card {
position: relative !important;
display: block !important;
width: 100% !important;
height: auto !important;
border: 0 !important;
border-radius: 12px !important;
background: rgba(255, 255, 255, 0.94) !important;
color: #141722 !important;
box-shadow: 0 12px 30px rgba(38, 45, 65, 0.08) !important;
overflow: hidden !important;
text-align: left !important;
touch-action: manipulation !important;
cursor: pointer !important;
pointer-events: auto !important;
-webkit-tap-highlight-color: transparent !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-tool-card {
min-height: 98px !important;
padding: 14px !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-template-card {
min-height: 92px !important;
padding: 12px !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-tool-card small,
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-template-card small {
display: block !important;
color: #6f7584 !important;
font: 700 11px/16px -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-tool-card strong {
display: block !important;
margin-top: 7px !important;
color: #171a22 !important;
font: 900 18px/23px -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-tool-card span {
display: block !important;
margin-top: 5px !important;
color: #5b6070 !important;
font: 600 11px/16px -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-template-tag {
display: inline-flex !important;
align-items: center !important;
height: 22px !important;
padding: 0 8px !important;
border-radius: 11px !important;
background: #f0f2f6 !important;
color: #6d7481 !important;
font: 800 10px/22px -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-template-card strong {
display: block !important;
margin-top: 8px !important;
color: #171a22 !important;
font: 900 15px/20px -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
}
html[data-zbai-mobile="1"] #${HOME_SHELL_ID} .zbai-mobile-template-card small {
margin-top: 4px !important;
display: -webkit-box !important;
-webkit-line-clamp: 2 !important;
-webkit-box-orient: vertical !important;
overflow: hidden !important;
}
html[data-zbai-mobile="1"][data-zbai-page="home"] #root,
html[data-zbai-mobile="1"][data-zbai-page="home"] .PageContainer_haimetaApp__qfjGd,
html[data-zbai-mobile="1"][data-zbai-page="home"] .PageContainer_indexContent__5aBHJ,
html[data-zbai-mobile="1"][data-zbai-page="home"] main,
html[data-zbai-mobile="1"][data-zbai-page="home"] section {
width: 100vw !important;
min-width: 0 !important;
max-width: 100vw !important;
height: auto !important;
overflow-x: hidden !important;
transform: none !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
html[data-zbai-mobile="1"][data-zbai-page="home"] #picture-editor-header.PictureEditorHeader_homepageHeader__j_gnO,
html[data-zbai-mobile="1"][data-zbai-page="home"] .picture-editor-header,
html[data-zbai-mobile="1"][data-zbai-page="home"] #picture-editor-header,
html[data-zbai-mobile="1"][data-zbai-page="home"] header {
width: 100vw !important;
min-width: 0 !important;
height: calc(52px + env(safe-area-inset-top, 0px)) !important;
min-height: calc(52px + env(safe-area-inset-top, 0px)) !important;
padding: env(safe-area-inset-top, 0px) 10px 0 !important;
position: sticky !important;
top: 0 !important;
z-index: 80 !important;
display: flex !important;
align-items: center !important;
justify-content: space-between !important;
background: var(--Background-1-Normal, #f4f5fb) !important;
}
html[data-zbai-mobile="1"][data-zbai-page="home"] .SidebarNavigation_sidebarNavigation__KTG_b {
display: none !important;
}
html[data-zbai-mobile="1"][data-zbai-page="home"] main.index-main,
html[data-zbai-mobile="1"][data-zbai-page="home"] .index-main-content,
html[data-zbai-mobile="1"][data-zbai-page="home"] #index-main-content--right {
width: 100vw !important;
min-width: 0 !important;
max-width: 100vw !important;
height: auto !important;
min-height: calc(100dvh - 52px) !important;
margin: 0 !important;
padding-left: 0 !important;
padding-right: 0 !important;
transform: none !important;
left: 0 !important;
right: auto !important;
overflow-x: hidden !important;
overflow-y: visible !important;
}
html[data-zbai-mobile="1"][data-zbai-page="home"] [class*="waterfall"],
html[data-zbai-mobile="1"][data-zbai-page="home"] [class*="Waterfall"],
html[data-zbai-mobile="1"][data-zbai-page="home"] [class*="card-list"],
html[data-zbai-mobile="1"][data-zbai-page="home"] [class*="CardList"],
html[data-zbai-mobile="1"][data-zbai-page="home"] [class*="template-list"],
html[data-zbai-mobile="1"][data-zbai-page="home"] [class*="TemplateList"] {
width: 100% !important;
max-width: 100% !important;
min-width: 0 !important;
}
body.${CLASS.enabled} #${ROOT_ID} {
display: block !important;
position: fixed !important;
inset: 0 !important;
z-index: 2147483600 !important;
pointer-events: none !important;
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
}
body.${CLASS.enabled} #${ROOT_ID} .zbai-mobile-auth {
position: fixed !important;
z-index: 2147483603 !important;
top: max(12px, env(safe-area-inset-top)) !important;
right: max(12px, env(safe-area-inset-right)) !important;
min-width: 64px !important;
min-height: 38px !important;
padding: 0 14px !important;
border: 1px solid rgba(255, 255, 255, 0.56) !important;
border-radius: 19px !important;
color: #fff !important;
background: rgba(17, 18, 13, 0.9) !important;
box-shadow: 0 8px 22px rgba(20, 24, 38, 0.16) !important;
backdrop-filter: blur(12px) !important;
-webkit-backdrop-filter: blur(12px) !important;
font: 800 13px/38px -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
letter-spacing: 0 !important;
pointer-events: auto !important;
touch-action: manipulation !important;
}
body.${CLASS.enabled} #${ROOT_ID} .zbai-mobile-close {
display: none !important;
position: fixed !important;
z-index: 2147483606 !important;
top: max(10px, env(safe-area-inset-top)) !important;
left: max(10px, env(safe-area-inset-left)) !important;
min-height: 40px !important;
padding: 0 14px !important;
border: 1px solid rgba(31, 35, 40, .14) !important;
border-radius: 20px !important;
color: #17191f !important;
background: rgba(255, 255, 255, .96) !important;
box-shadow: 0 10px 28px rgba(19, 24, 33, .18) !important;
font: 800 14px/40px -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
letter-spacing: 0 !important;
pointer-events: auto !important;
}
body.${CLASS.enabled} #${ROOT_ID} .zbai-mobile-back {
display: none !important;
position: fixed !important;
z-index: 2147483603 !important;
top: max(10px, env(safe-area-inset-top)) !important;
left: max(10px, env(safe-area-inset-left)) !important;
min-height: 40px !important;
padding: 0 14px !important;
border: 1px solid rgba(31, 35, 40, .14) !important;
border-radius: 20px !important;
color: #17191f !important;
background: rgba(255, 255, 255, .96) !important;
box-shadow: 0 10px 28px rgba(19, 24, 33, .18) !important;
font: 800 14px/40px -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
letter-spacing: 0 !important;
pointer-events: auto !important;
touch-action: manipulation !important;
}
html[data-zbai-mobile="1"]:not([data-zbai-page="home"]) body.${CLASS.enabled} #${ROOT_ID}:not([data-zbai-surface]) .zbai-mobile-back {
display: block !important;
}
body.${CLASS.enabled} #${ROOT_ID}[data-zbai-surface] .zbai-mobile-close {
display: block !important;
}
body.${CLASS.enabled} #${ROOT_ID}:not([data-zbai-route="compose"]) .zbai-mobile-dock {
display: none !important;
}
body.${CLASS.enabled} #${ROOT_ID} .zbai-mobile-dock {
position: fixed !important;
z-index: 2147483602 !important;
right: max(12px, env(safe-area-inset-right)) !important;
bottom: calc(12px + env(safe-area-inset-bottom)) !important;
left: max(12px, env(safe-area-inset-left)) !important;
display: grid !important;
grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
gap: 8px !important;
padding: 8px !important;
border: 1px solid rgba(255, 255, 255, 0.66) !important;
border-radius: 18px !important;
background: rgba(255, 255, 255, .9) !important;
box-shadow: 0 12px 36px rgba(19, 24, 33, .18) !important;
backdrop-filter: blur(14px) !important;
-webkit-backdrop-filter: blur(14px) !important;
pointer-events: auto !important;
}
body.${CLASS.enabled} #${ROOT_ID} .zbai-mobile-dock button {
min-width: 0 !important;
min-height: 44px !important;
border: 0 !important;
border-radius: 13px !important;
color: #17191f !important;
background: #eef0f7 !important;
font: 800 14px/20px -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif !important;
letter-spacing: 0 !important;
touch-action: manipulation !important;
}
body.${CLASS.enabled} #${ROOT_ID} .zbai-mobile-dock button:first-child {
color: #fff !important;
background: #11120d !important;
box-shadow: 0 8px 20px rgba(17, 18, 13, .16) !important;
}
body.${CLASS.enabled} .${CLASS.uploadArmed} {
position: fixed !important;
z-index: 2147483606 !important;
left: var(--zbai-upload-left) !important;
top: var(--zbai-upload-top) !important;
width: var(--zbai-upload-width) !important;
height: var(--zbai-upload-height) !important;
min-width: 0 !important;
min-height: 0 !important;
max-width: none !important;
max-height: none !important;
margin: 0 !important;
padding: 0 !important;
border: 0 !important;
border-radius: 13px !important;
opacity: .01 !important;
overflow: hidden !important;
transform: none !important;
pointer-events: auto !important;
touch-action: manipulation !important;
}
body.${CLASS.enabled} .${CLASS.uploadArmed} * {
pointer-events: auto !important;
}
body.${CLASS.enabled} #${ROOT_ID} .zbai-mobile-ready {
position: fixed !important;
z-index: 2147483603 !important;
right: max(12px, env(safe-area-inset-right)) !important;
bottom: calc(78px + env(safe-area-inset-bottom)) !important;
padding: 5px 9px !important;
border: 1px solid rgba(31, 35, 40, .12) !important;
border-radius: 999px !important;
color: #17191f !important;
background: rgba(255, 255, 255, .88) !important;
box-shadow: 0 8px 22px rgba(19, 24, 33, .16) !important;
font-size: 12px !important;
font-weight: 700 !important;
line-height: 1.35 !important;
pointer-events: none !important;
}
html[data-zbai-mobile="1"][data-zbai-page="home"] body.${CLASS.enabled} #${ROOT_ID} .zbai-mobile-ready {
left: 10px !important;
right: auto !important;
bottom: calc(8px + env(safe-area-inset-bottom)) !important;
}
body.${CLASS.enabled} #${ROOT_ID} .zbai-mobile-backdrop {
position: fixed !important;
inset: 0 !important;
border: 0 !important;
opacity: 0 !important;
background: rgba(10, 12, 18, .34) !important;
pointer-events: none !important;
transition: opacity .18s ease !important;
}
body.${CLASS.enabled} #${ROOT_ID}[data-zbai-surface] .zbai-mobile-backdrop {
opacity: 1 !important;
pointer-events: auto !important;
}
body.${CLASS.enabled} #${ROOT_ID} .zbai-mobile-toast {
position: fixed !important;
z-index: 2147483607 !important;
right: 18px !important;
bottom: calc(82px + env(safe-area-inset-bottom)) !important;
left: 18px !important;
padding: 11px 13px !important;
border-radius: 14px !important;
color: #fff !important;
background: rgba(23, 25, 31, .94) !important;
box-shadow: 0 12px 30px rgba(19, 24, 33, .24) !important;
font-size: 13px !important;
line-height: 1.4 !important;
opacity: 0 !important;
transform: translateY(8px) !important;
transition: opacity .16s ease, transform .16s ease !important;
pointer-events: none !important;
}
body.${CLASS.enabled} #${ROOT_ID} .zbai-mobile-toast.is-visible {
opacity: 1 !important;
transform: translateY(0) !important;
}
body.${CLASS.enabled} .${CLASS.compose}.${CLASS.composeOpen} {
position: fixed !important;
z-index: 2147483601 !important;
inset: auto 0 0 0 !important;
width: auto !important;
min-width: 0 !important;
max-width: none !important;
height: min(88dvh, 860px) !important;
max-height: calc(100dvh - 12px) !important;
margin: 0 !important;
padding-bottom: calc(88px + env(safe-area-inset-bottom)) !important;
border-radius: 18px 18px 0 0 !important;
overflow: auto !important;
overscroll-behavior: contain !important;
background: #fff !important;
box-shadow: 0 -16px 42px rgba(19, 24, 33, .24) !important;
transform: none !important;
-webkit-overflow-scrolling: touch !important;
}
body.${CLASS.enabled} .${CLASS.compose}.${CLASS.composeOpen} textarea {
min-height: 108px !important;
}
body.${CLASS.enabled} .${CLASS.compose}.${CLASS.composeOpen} button {
min-height: 40px !important;
}
body.${CLASS.enabled} .${CLASS.compose}.${CLASS.composeOpen} .${CLASS.formatLabel} {
flex: 0 0 auto !important;
min-width: 48px !important;
white-space: nowrap !important;
word-break: keep-all !important;
writing-mode: horizontal-tb !important;
}
body.${CLASS.enabled} .${CLASS.compose}.${CLASS.composeOpen} .${CLASS.formatRow} {
width: 100% !important;
min-width: 0 !important;
display: flex !important;
flex-wrap: wrap !important;
align-items: center !important;
gap: 8px !important;
pointer-events: auto !important;
}
body.${CLASS.enabled} .${CLASS.compose}.${CLASS.composeOpen} .${CLASS.formatRow} .${CLASS.formatChoice} {
flex: 0 0 auto !important;
min-width: 72px !important;
min-height: 40px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
touch-action: manipulation !important;
pointer-events: auto !important;
}
body.${CLASS.enabled} .${CLASS.duplicateTitle} {
display: none !important;
}
body.${CLASS.enabled} .${CLASS.templateTextMode} {
display: none !important;
}
body.${CLASS.enabled} .ant-select-dropdown,
body.${CLASS.enabled} .ant-dropdown,
body.${CLASS.enabled} .arco-select-popup,
body.${CLASS.enabled} .el-select-dropdown,
body.${CLASS.enabled} [role="listbox"],
body.${CLASS.enabled} [data-radix-popper-content-wrapper] {
z-index: 2147483605 !important;
}
body.${CLASS.enabled} .${CLASS.history}.${CLASS.historyOpen} {
display: block !important;
visibility: visible !important;
position: fixed !important;
z-index: 2147483601 !important;
inset: 0 auto 0 0 !important;
width: min(40vw, 148px) !important;
min-width: 92px !important;
height: 100dvh !important;
margin: 0 !important;
padding: 12px 8px calc(92px + env(safe-area-inset-bottom)) !important;
border-radius: 0 18px 18px 0 !important;
overflow: auto !important;
overscroll-behavior: contain !important;
pointer-events: auto !important;
background: #f8f8fb !important;
box-shadow: 10px 0 34px rgba(19, 24, 33, .22) !important;
transform: none !important;
-webkit-overflow-scrolling: touch !important;
}
body.${CLASS.enabled} .${CLASS.history}.${CLASS.historyOpen} [class*="PictureEditorHistoryDrawer"],
body.${CLASS.enabled} .${CLASS.history}.${CLASS.historyOpen} [class*="TaskSide_record"] {
display: block !important;
visibility: visible !important;
position: relative !important;
inset: auto !important;
left: auto !important;
right: auto !important;
top: auto !important;
bottom: auto !important;
width: 100% !important;
min-width: 0 !important;
max-width: 100% !important;
height: auto !important;
min-height: 0 !important;
margin: 0 !important;
padding: 0 !important;
border-radius: 0 !important;
overflow: visible !important;
pointer-events: auto !important;
background: transparent !important;
box-shadow: none !important;
transform: none !important;
}
body.${CLASS.enabled} .${CLASS.history}.${CLASS.historyOpen} * {
visibility: visible !important;
pointer-events: auto !important;
}
body.${CLASS.enabled} .${CLASS.historyParent} {
display: block !important;
visibility: visible !important;
}
body.${CLASS.enabled} .${CLASS.history}.${CLASS.historyOpen} img {
max-width: 100% !important;
height: auto !important;
pointer-events: auto !important;
}
body.${CLASS.enabled} .${CLASS.history}.${CLASS.historyOpen} a,
body.${CLASS.enabled} .${CLASS.history}.${CLASS.historyOpen} button,
body.${CLASS.enabled} .${CLASS.history}.${CLASS.historyOpen} [role="button"],
body.${CLASS.enabled} .${CLASS.history}.${CLASS.historyOpen} [onclick] {
pointer-events: auto !important;
}
html[data-zbai-mobile="1"] #${WORK_DETAIL_FALLBACK_ID} {
position: fixed !important;
inset: 0 !important;
z-index: 2147483650 !important;
display: flex !important;
flex-direction: column !important;
width: 100vw !important;
height: 100dvh !important;
padding: calc(10px + env(safe-area-inset-top, 0px)) 10px calc(12px + env(safe-area-inset-bottom, 0px)) !important;
box-sizing: border-box !important;
background: #0d0f12 !important;
color: #fff !important;
pointer-events: auto !important;
}
html[data-zbai-mobile="1"] #${WORK_DETAIL_FALLBACK_ID} .zbai-work-detail-bar {
flex: 0 0 44px !important;
display: flex !important;
align-items: center !important;
justify-content: space-between !important;
gap: 10px !important;
}
html[data-zbai-mobile="1"] #${WORK_DETAIL_FALLBACK_ID} button {
min-width: 72px !important;
height: 38px !important;
border: 0 !important;
border-radius: 999px !important;
background: rgba(255, 255, 255, .92) !important;
color: #101217 !important;
font-size: 14px !important;
font-weight: 700 !important;
pointer-events: auto !important;
}
html[data-zbai-mobile="1"] #${WORK_DETAIL_FALLBACK_ID} .zbai-work-detail-stage {
flex: 1 1 auto !important;
min-height: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
overflow: auto !important;
-webkit-overflow-scrolling: touch !important;
}
html[data-zbai-mobile="1"] #${WORK_DETAIL_FALLBACK_ID} img {
max-width: 100% !important;
max-height: 100% !important;
object-fit: contain !important;
border-radius: 0 !important;
}
}
#${ROOT_ID} {
display: none;
}
`;
if (shouldAppend) {
const target = document.head || document.documentElement;
if (!target) {
window.setTimeout(injectStyles, 0);
return;
}
target.appendChild(style);
}
}
})();