From e2e35fd66d911a5c69d16e78841ccd214e4a9ac4 Mon Sep 17 00:00:00 2001 From: Abner <22141172+Silentely@users.noreply.github.com> Date: Sat, 11 Jul 2026 13:05:43 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20style:=20=E6=81=A2=E5=A4=8D?= =?UTF-8?q?=E8=A3=85=E9=A5=B0=E5=8A=A8=E7=94=BB=E5=8E=9F=E6=9C=89=E6=95=88?= =?UTF-8?q?=E6=9E=9C=E5=B9=B6=E4=BC=98=E5=8C=96=E5=8A=A8=E6=95=88=E8=8A=82?= =?UTF-8?q?=E5=A5=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 恢复推广横幅、服务时间呼吸/脉冲等原有效果与幅度;用统一 时长 token 与更顺缓动微调节奏,悬停与后台标签暂停循环动画。 --- src/giffgaff/js/reveal-animation.js | 18 ++++- src/giffgaff/styles/giffgaff-animations.css | 77 ++++++++++++------- src/giffgaff/styles/giffgaff-components.css | 21 ++++- src/giffgaff/styles/giffgaff-service-time.css | 49 ++++++------ src/js/home.js | 7 ++ src/simyo/js/reveal-animation.js | 18 ++++- src/simyo/styles/simyo-animations.css | 23 +++--- src/simyo/styles/simyo-components.css | 17 +++- src/styles/design-system.css | 40 ++++++++-- 9 files changed, 191 insertions(+), 79 deletions(-) diff --git a/src/giffgaff/js/reveal-animation.js b/src/giffgaff/js/reveal-animation.js index b8741a5..76407ab 100644 --- a/src/giffgaff/js/reveal-animation.js +++ b/src/giffgaff/js/reveal-animation.js @@ -1,9 +1,14 @@ /** * 入场动效初始化 * 使用 IntersectionObserver 实现滚动触发动画 + * 并在页面隐藏时暂停装饰循环动画 */ (function(){ + const syncPageHidden = () => { + document.documentElement.dataset.pageHidden = document.hidden ? 'true' : 'false'; + }; + const initReveal = () => { const nodes = document.querySelectorAll('.reveal'); if (!nodes.length) return; @@ -21,9 +26,16 @@ nodes.forEach(el => el.classList.add('show')); } }; - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initReveal, { once: true }); - } else { + + const init = () => { + syncPageHidden(); initReveal(); + }; + + document.addEventListener('visibilitychange', syncPageHidden); + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init, { once: true }); + } else { + init(); } })(); diff --git a/src/giffgaff/styles/giffgaff-animations.css b/src/giffgaff/styles/giffgaff-animations.css index 3413f0b..54623e7 100644 --- a/src/giffgaff/styles/giffgaff-animations.css +++ b/src/giffgaff/styles/giffgaff-animations.css @@ -33,7 +33,20 @@ animation: fadeInUp 0.6s ease-out; } -/* ===== 推广横幅动画 ===== */ +/* + * 装饰动画时长 token(非静态优化:统一节奏、便于微调) + * --anim-fast: 横幅图标弹跳 + * --anim-base: 呼吸/警告脉冲 + * --anim-slow: 时间卡 glow / 数字呼吸 + */ +:root { + --anim-ease-pulse: cubic-bezier(0.45, 0, 0.55, 1); + --anim-fast: 2s; + --anim-base: 2.75s; + --anim-slow: 3.25s; +} + +/* ===== 推广横幅动画(原有效果:scale 1.02 / bounce 位移) ===== */ @keyframes subtle-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.02); } @@ -45,68 +58,80 @@ 60% { transform: translateY(-3px); } } -/* ===== 服务时间动画(仅 opacity / 阴影,禁止 scale 以降低干扰与耗电) ===== */ +/* ===== 服务时间动画(原有效果幅度,时长用 token 略放缓) ===== */ @keyframes breathing-light { 0%, 100% { - opacity: 1; - box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.25); + box-shadow: 0 0 5px rgba(220, 38, 38, 0.3); + transform: scale(1); } 50% { - opacity: 0.92; - box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.12); + box-shadow: 0 0 20px rgba(220, 38, 38, 0.6); + transform: scale(1.05); } } @keyframes breathing-light-orange { 0%, 100% { - opacity: 1; - box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.25); + box-shadow: 0 0 5px rgba(245, 158, 11, 0.3); + transform: scale(1); } 50% { - opacity: 0.92; - box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.12); + box-shadow: 0 0 20px rgba(245, 158, 11, 0.6); + transform: scale(1.05); } } @keyframes breathing-light-green { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.88; } + 0%, 100% { + box-shadow: 0 0 5px rgba(22, 163, 74, 0.3); + transform: scale(1); + } + 50% { + box-shadow: 0 0 20px rgba(22, 163, 74, 0.6); + transform: scale(1.05); + } } @keyframes breathing-light-purple { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.88; } + 0%, 100% { + box-shadow: 0 0 5px rgba(99, 102, 241, 0.3); + transform: scale(1); + } + 50% { + box-shadow: 0 0 20px rgba(99, 102, 241, 0.6); + transform: scale(1.05); + } } -/* 警告:轻微 opacity,不再缩放 */ @keyframes warning-pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.85; } + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.05); } } @keyframes success-bounce { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.9; } + 0%, 100% { transform: scale(1); } + 25% { transform: scale(1.02); } + 75% { transform: scale(1.02); } } @keyframes pulse-glow { 0%, 100% { - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } 50% { - box-shadow: 0 4px 14px rgba(99, 102, 241, 0.18); + box-shadow: 0 4px 16px rgba(99, 102, 241, 0.3); } } -/* 时间数字更新:一次轻触反馈,由 JS 触发类名时使用 */ @keyframes time-update { - 0% { opacity: 0.7; } - 100% { opacity: 1; } + 0% { transform: scale(1); } + 50% { transform: scale(1.1); } + 100% { transform: scale(1); } } @keyframes time-breathe { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.92; } + 0%, 100% { transform: translateZ(0) scale(1); } + 50% { transform: translateZ(0) scale(1.02); } } @keyframes modalSlideIn { diff --git a/src/giffgaff/styles/giffgaff-components.css b/src/giffgaff/styles/giffgaff-components.css index 87783b6..09dde14 100644 --- a/src/giffgaff/styles/giffgaff-components.css +++ b/src/giffgaff/styles/giffgaff-components.css @@ -428,8 +428,7 @@ margin: 20px 0; padding: 16px 20px; box-shadow: 0 4px 15px rgba(255, 204, 0, 0.3); - /* 静态展示,避免持续脉冲 */ - animation: none; + animation: subtle-pulse var(--anim-slow, 3.25s) var(--anim-ease-pulse, ease-in-out) infinite; } .promo-content { @@ -443,7 +442,15 @@ .promo-icon { color: #212529; font-size: 20px; - animation: none; + animation: bounce var(--anim-fast, 2s) var(--anim-ease-pulse, ease-in-out) infinite; +} + +/* 悬停时暂停装饰动画,阅读更稳 */ +@media (hover: hover) { + .promo-banner:hover, + .promo-banner:hover .promo-icon { + animation-play-state: paused; + } } .promo-text { @@ -736,7 +743,6 @@ .promo-banner { box-shadow: 0 4px 18px rgba(255, 204, 0, 0.18); - animation: none; } .login-btn { @@ -744,3 +750,10 @@ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35); } } + +@media (prefers-reduced-motion: reduce) { + .promo-banner, + .promo-icon { + animation: none !important; + } +} diff --git a/src/giffgaff/styles/giffgaff-service-time.css b/src/giffgaff/styles/giffgaff-service-time.css index 7bd3288..f634492 100644 --- a/src/giffgaff/styles/giffgaff-service-time.css +++ b/src/giffgaff/styles/giffgaff-service-time.css @@ -117,35 +117,35 @@ } #serviceTimeIcon.warning { - background: #fef3c7; - color: #d97706; - box-shadow: inset 0 0 0 2px rgba(245,158,11,0.2); - /* 警告态保留极轻 opacity 脉动,成功态静态 */ - animation: warning-pulse 2.8s ease-in-out infinite; + background: #e6f6ee; + color: #10b981; + box-shadow: inset 0 0 0 2px rgba(16,185,129,0.15); + animation: breathing-light-green var(--anim-base, 2.75s) var(--anim-ease-pulse, ease-in-out) infinite; } #serviceTimeIcon.success { background: #e6f6ee; color: #10b981; box-shadow: inset 0 0 0 2px rgba(16,185,129,0.15); - animation: none; + animation: breathing-light-green var(--anim-base, 2.75s) var(--anim-ease-pulse, ease-in-out) infinite; } +/* hover 用 filter,避免覆盖 animation 的 transform */ @media (hover: hover) { #serviceTimeIcon:hover { - transform: scale(1.04); + filter: brightness(1.06); } } .alert-warning #serviceTimeIcon { - animation: warning-pulse 2.8s ease-in-out infinite; + animation: warning-pulse var(--anim-base, 2.75s) var(--anim-ease-pulse, ease-in-out) infinite; background: #f8ecbb; color: #f59d06; box-shadow: inset 0 0 0 2px rgba(245,158,11,0.15); } .alert-success #serviceTimeIcon { - animation: none; + animation: breathing-light-green var(--anim-base, 2.75s) var(--anim-ease-pulse, ease-in-out) infinite; background: #e0f6eb; color: #10b981; box-shadow: inset 0 0 0 2px rgba(16,185,129,0.15); @@ -174,7 +174,7 @@ background: #f0fdf4; color: #16a34a; border: 2px solid #16a34a; - animation: none; + animation: breathing-light-green var(--anim-base, 2.75s) var(--anim-ease-pulse, ease-in-out) infinite; } .service-time-action-badge.warning { @@ -183,13 +183,14 @@ border: 2px solid #dc2626; font-weight: 800; text-shadow: none; - /* 仅警告徽章保留轻量脉动 */ - animation: breathing-light 3s ease-in-out infinite; + animation: breathing-light var(--anim-base, 2.75s) var(--anim-ease-pulse, ease-in-out) infinite; } -.service-time-action-badge:hover { - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +@media (hover: hover) { + .service-time-action-badge:hover { + filter: brightness(1.04); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + } } /* ===== 时间卡片 ===== */ @@ -207,8 +208,7 @@ position: relative; overflow: hidden; isolation: isolate; - /* 时间卡静态展示,避免持续 glow 动画 */ - animation: none; + animation: pulse-glow var(--anim-slow, 3.25s) var(--anim-ease-pulse, ease-in-out) infinite; } .current-time-card::after { @@ -228,7 +228,7 @@ display: grid; place-items: center; box-shadow: inset 0 0 0 1px rgba(99,102,241,0.22); - animation: none; + animation: breathing-light-purple var(--anim-base, 2.75s) var(--anim-ease-pulse, ease-in-out) infinite; } .time-card-icon i { @@ -256,8 +256,7 @@ letter-spacing: 1.2px; color: #0b1324; text-shadow: 0 1px 0 rgba(255,255,255,0.6); - /* 静态可读,避免持续呼吸动画 */ - animation: none; + animation: time-breathe var(--anim-slow, 3.25s) var(--anim-ease-pulse, ease-in-out) infinite; } .time-card-hint { @@ -337,11 +336,17 @@ } } -/* 尊重减少动态偏好:去掉剩余状态脉动 */ +/* 尊重减少动态偏好 */ @media (prefers-reduced-motion: reduce) { #serviceTimeIcon.warning, + #serviceTimeIcon.success, .alert-warning #serviceTimeIcon, - .service-time-action-badge.warning { + .alert-success #serviceTimeIcon, + .service-time-action-badge.warning, + .service-time-action-badge.success, + .current-time-card, + .time-card-icon, + .time-card-value { animation: none !important; } } \ No newline at end of file diff --git a/src/js/home.js b/src/js/home.js index 99e3953..f096c29 100644 --- a/src/js/home.js +++ b/src/js/home.js @@ -7,6 +7,13 @@ gtag('js', new Date()); gtag('config', 'G-HGX38HMNL1'); document.addEventListener('DOMContentLoaded', () => { + // 页面隐藏时暂停装饰循环动画 + const syncPageHidden = () => { + document.documentElement.dataset.pageHidden = document.hidden ? 'true' : 'false'; + }; + syncPageHidden(); + document.addEventListener('visibilitychange', syncPageHidden); + // 对所有 reveal 元素应用入场动画 const revealElements = document.querySelectorAll('.reveal'); if ('IntersectionObserver' in window) { diff --git a/src/simyo/js/reveal-animation.js b/src/simyo/js/reveal-animation.js index b8741a5..76407ab 100644 --- a/src/simyo/js/reveal-animation.js +++ b/src/simyo/js/reveal-animation.js @@ -1,9 +1,14 @@ /** * 入场动效初始化 * 使用 IntersectionObserver 实现滚动触发动画 + * 并在页面隐藏时暂停装饰循环动画 */ (function(){ + const syncPageHidden = () => { + document.documentElement.dataset.pageHidden = document.hidden ? 'true' : 'false'; + }; + const initReveal = () => { const nodes = document.querySelectorAll('.reveal'); if (!nodes.length) return; @@ -21,9 +26,16 @@ nodes.forEach(el => el.classList.add('show')); } }; - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initReveal, { once: true }); - } else { + + const init = () => { + syncPageHidden(); initReveal(); + }; + + document.addEventListener('visibilitychange', syncPageHidden); + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init, { once: true }); + } else { + init(); } })(); diff --git a/src/simyo/styles/simyo-animations.css b/src/simyo/styles/simyo-animations.css index 0abfc9a..3d46ab8 100644 --- a/src/simyo/styles/simyo-animations.css +++ b/src/simyo/styles/simyo-animations.css @@ -27,26 +27,25 @@ } } -/* 脉冲动画 */ +/* 脉冲 / 弹跳:与原有效果一致 */ @keyframes subtle-pulse { - 0%, 100% { - transform: scale(1); + 0%, 100% { + transform: scale(1); } - 50% { - transform: scale(1.02); + 50% { + transform: scale(1.02); } } -/* 弹跳动画 */ @keyframes bounce { - 0%, 20%, 50%, 80%, 100% { - transform: translateY(0); + 0%, 20%, 50%, 80%, 100% { + transform: translateY(0); } - 40% { - transform: translateY(-5px); + 40% { + transform: translateY(-5px); } - 60% { - transform: translateY(-3px); + 60% { + transform: translateY(-3px); } } diff --git a/src/simyo/styles/simyo-components.css b/src/simyo/styles/simyo-components.css index df31a7b..2048f2e 100644 --- a/src/simyo/styles/simyo-components.css +++ b/src/simyo/styles/simyo-components.css @@ -324,7 +324,7 @@ margin: 20px 0; padding: 16px 20px; box-shadow: 0 4px 15px rgba(255, 107, 0, 0.3); - animation: none; + animation: subtle-pulse var(--anim-slow, 3.25s) var(--anim-ease-pulse, ease-in-out) infinite; } .promo-content { @@ -338,7 +338,14 @@ .promo-icon { color: white; font-size: 20px; - animation: none; + animation: bounce var(--anim-fast, 2s) var(--anim-ease-pulse, ease-in-out) infinite; +} + +@media (hover: hover) { + .promo-banner:hover, + .promo-banner:hover .promo-icon { + animation-play-state: paused; + } } .promo-text { @@ -422,11 +429,13 @@ } .promo-banner { - animation: none; box-shadow: 0 4px 18px rgba(255, 107, 0, 0.22); } +} +@media (prefers-reduced-motion: reduce) { + .promo-banner, .promo-icon { - animation: none; + animation: none !important; } } \ No newline at end of file diff --git a/src/styles/design-system.css b/src/styles/design-system.css index 79f5a6b..7f6fdda 100644 --- a/src/styles/design-system.css +++ b/src/styles/design-system.css @@ -96,6 +96,12 @@ --transition-base: 240ms var(--ease-out); --transition-slow: 360ms var(--ease-out); + /* 装饰循环动画(保留原有效果,时长略柔和) */ + --anim-ease-pulse: cubic-bezier(0.45, 0, 0.55, 1); + --anim-fast: 2s; + --anim-base: 2.75s; + --anim-slow: 3.25s; + /* Focus ring */ --ring-color: rgba(76,154,255,.35); --ring-outline: #4c9aff; @@ -467,17 +473,41 @@ html[data-locale="en"] .lang-en { display: inline !important; } } .tooltip.show { opacity: 1; } -/* Promo banner:静态展示,避免持续 scale 动画 */ +/* Promo banner:原有 subtle-pulse 效果,时长/缓动用 token 统一 */ .promo-banner { border-radius: var(--radius-md); margin: 20px 0; padding: 16px 20px; box-shadow: 0 4px 15px color-mix(in srgb, var(--primary) 30%, transparent); - animation: none; + animation: subtle-pulse var(--anim-slow, 3.25s) var(--anim-ease-pulse, cubic-bezier(0.45, 0, 0.55, 1)) infinite; } @keyframes subtle-pulse { - 0%,100% { opacity: 1; } - 50% { opacity: 0.92; } + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.02); } +} + +@media (hover: hover) { + .promo-banner:hover { + animation-play-state: paused; + } +} + +@media (prefers-reduced-motion: reduce) { + .promo-banner { + animation: none !important; + } +} + +/* 页面不可见时暂停装饰循环,省电且不改视觉设计 */ +html[data-page-hidden="true"] .promo-banner, +html[data-page-hidden="true"] .promo-icon, +html[data-page-hidden="true"] #serviceTimeIcon, +html[data-page-hidden="true"] .service-time-action-badge, +html[data-page-hidden="true"] .current-time-card, +html[data-page-hidden="true"] .time-card-icon, +html[data-page-hidden="true"] .time-card-value, +html[data-page-hidden="true"] .step.active .step-number { + animation-play-state: paused !important; } /* Reveal helpers for QR preview hovers may reuse .tooltip from above */ @@ -536,7 +566,7 @@ html[data-locale="en"] .lang-en { display: inline !important; } /* Step Indicator:当前步脉冲(颜色跟随 --primary) */ .step.active .step-number { - animation: pulse-ring 2s var(--ease-out) infinite; + animation: pulse-ring var(--anim-base, 2.75s) var(--anim-ease-pulse, var(--ease-out)) infinite; } /* 步骤指示器进度轨(--step-progress: 0~1,由 JS 写入) */