diff --git a/web/modules/locales.js b/web/modules/locales.js index 640b25b..12ad5fa 100644 --- a/web/modules/locales.js +++ b/web/modules/locales.js @@ -33,6 +33,7 @@ export const i18n = { shortcutDropRelease: "松开,从快捷栏移除", shortcutBarAnchorTip: "固定入口,不可移动", updateGuideStartTour: "🎯 界面按键遮罩导览", + updateGuideTourBanner: "💡 想在主界面实地体验?点击开启按键遮罩导览 ›", updateGuideNavigationTitle: "悬停,看懂每个入口", updateGuideNavigationBody: "左下角的按钮保持原来的大小。鼠标悬停或用键盘选中时,图标会切换成文字;稍作停留可以查看完整用途。依次为工具箱、扫描、模型医生、节点助手、素材库和设置。", updateGuideTransferTitle: "导入导出,统一到工具箱", @@ -1249,6 +1250,7 @@ export const i18n = { shortcutDropRelease: "Release to remove from shortcuts", shortcutBarAnchorTip: "Fixed anchor, cannot be moved", updateGuideStartTour: "🎯 Spotlight Tour", + updateGuideTourBanner: "💡 Prefer to see them in action? Start Spotlight Tour ›", updateGuideNavigationTitle: "Hover to identify each action", updateGuideNavigationBody: "The lower-left buttons keep their compact size. Hover or focus with the keyboard to replace an icon with a short label; pause for its full description. The actions are Toolbox, Scan, Model Doctor, Node Assistant, Material Library and Settings.", updateGuideTransferTitle: "Workflow sharing lives in Toolbox", diff --git a/web/modules/ui_spotlight_tour.js b/web/modules/ui_spotlight_tour.js index 4db72f9..8ccb61d 100644 --- a/web/modules/ui_spotlight_tour.js +++ b/web/modules/ui_spotlight_tour.js @@ -254,47 +254,54 @@ export function ensureTourStyles() { background: #1d4ed8; } .anomalous-btn-tour { - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - gap: 6px !important; - background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%) !important; - color: #ffffff !important; - border: 1px solid rgba(147, 197, 253, 0.7) !important; - border-radius: 6px !important; - padding: 7px 16px !important; - font-size: 12px !important; - font-weight: 600 !important; - cursor: pointer !important; - box-shadow: 0 0 12px rgba(37, 99, 235, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.2) !important; - transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1) !important; - animation: anomalous-tour-btn-pulse 2.6s infinite ease-in-out !important; - letter-spacing: 0.2px !important; - } - @keyframes anomalous-tour-btn-pulse { - 0%, 100% { - box-shadow: 0 0 10px rgba(37, 99, 235, 0.4), 0 0 0 0 rgba(59, 130, 246, 0.4); - } - 50% { - box-shadow: 0 0 18px rgba(37, 99, 235, 0.75), 0 0 0 3px rgba(59, 130, 246, 0.25); - } + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + background: #2563eb; + color: #ffffff; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 6px; + padding: 7px 16px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.15s ease; } .anomalous-btn-tour:hover { - background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important; - border-color: #ffffff !important; - color: #ffffff !important; - box-shadow: 0 0 20px rgba(59, 130, 246, 0.7), 0 4px 14px rgba(0, 0, 0, 0.35) !important; - transform: translateY(-1px) !important; - animation: none !important; + background: #1d4ed8; + border-color: rgba(255, 255, 255, 0.4); } - .anomalous-btn-tour:active { - transform: translateY(0) !important; - box-shadow: 0 0 8px rgba(37, 99, 235, 0.5) !important; + .anomalous-update-guide-tour-banner { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + margin: 2px auto 14px auto; + padding: 6px 16px; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 20px; + color: #94a3b8; + font-size: 11.5px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); + max-width: fit-content; + text-align: center; + user-select: none; + letter-spacing: 0.2px; } - @media (prefers-reduced-motion: reduce) { - .anomalous-btn-tour { - animation: none !important; - } + .anomalous-update-guide-tour-banner:hover { + background: rgba(59, 130, 246, 0.12); + border-color: rgba(96, 165, 250, 0.4); + color: #93c5fd; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); + } + .anomalous-update-guide-tour-banner:active { + transform: translateY(0); + background: rgba(59, 130, 246, 0.2); } `; (document.head || document.body)?.appendChild(style); diff --git a/web/modules/ui_update_guide.js b/web/modules/ui_update_guide.js index baf7092..f15dae7 100644 --- a/web/modules/ui_update_guide.js +++ b/web/modules/ui_update_guide.js @@ -64,25 +64,31 @@ export function showUpdateGuide(owner, { force = false, guide = CURRENT_UPDATE_G title.id = 'anomalous-update-guide-title'; const body = text(dialog, 'p', ''); body.id = 'anomalous-update-guide-body'; + const tourBanner = text(dialog, 'button', t('updateGuideTourBanner') || '💡 想在主界面实地体验?点击开启按键遮罩导览 ›', 'anomalous-update-guide-tour-banner'); + tourBanner.type = 'button'; + tourBanner.id = 'anomalous-update-guide-tour-btn'; + tourBanner.onclick = () => { + closeUpdateGuide(owner, true); + startSpotlightTour(owner); + }; const progress = text(dialog, 'p', '', 'anomalous-update-guide-progress'); progress.setAttribute('aria-live', 'polite'); const footer = text(dialog, 'div', '', 'anomalous-update-guide-footer'); const skip = text(footer, 'button', t('updateGuideSkip'), 'anomalous-btn-ghost'); - const startTour = text(footer, 'button', t('updateGuideStartTour') || '🎯 界面按键遮罩导览', 'anomalous-btn-tour'); - startTour.id = 'anomalous-update-guide-tour-btn'; - startTour.onclick = () => { - closeUpdateGuide(owner, true); - startSpotlightTour(owner); - }; - const back = text(footer, 'button', t('updateGuideBack'), 'anomalous-btn-ghost'); - const next = text(footer, 'button', '', 'anomalous-btn-primary'); - for (const button of [skip, startTour, back, next]) button.type = 'button'; + const navGroup = text(footer, 'div', '', 'anomalous-update-guide-nav-group'); + navGroup.style.display = 'flex'; + navGroup.style.gap = '8px'; + navGroup.style.alignItems = 'center'; + const back = text(navGroup, 'button', t('updateGuideBack'), 'anomalous-btn-ghost'); + const next = text(navGroup, 'button', '', 'anomalous-btn-primary'); + for (const button of [skip, back, next, tourBanner]) button.type = 'button'; let index = 0; const render = () => { const step = guide.steps[index]; icon.textContent = step.icon; title.textContent = t(step.titleKey); body.textContent = t(step.bodyKey); + tourBanner.textContent = t('updateGuideTourBanner') || '💡 想在主界面实地体验?点击开启按键遮罩导览 ›'; progress.textContent = t('updateGuideProgress', { current: index + 1, total: guide.steps.length }); back.disabled = index === 0; next.textContent = t(index === guide.steps.length - 1 ? 'updateGuideDone' : 'updateGuideNext');