diff --git a/index.html b/index.html index 5b14121..0edfe94 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,8 @@ - + + @@ -95,36 +96,38 @@ --accent-warn:#f59e0b; /* amber-500 */ --accent-danger:#ef4444; /* red-500 */ - /* Surface & Text */ - /* 背景改为淡色 #f2f5c4 */ - --bg-grad-start:#f2f5c4; - --bg-grad-end:#f1f5bb; + /* Surface & Text:中性 indigo/slate,替代旧黄绿背景 */ + --bg-grad-start:#eef2ff; + --bg-grad-end:#f8fafc; --surface:#ffffff; - --surface-alpha:92%; + --surface-alpha:94%; --text-primary:#1f2937; /* slate-800 */ --text-secondary:#374151; /* gray-700 */ --text-muted:#6b7280; /* gray-500 */ /* Radius & Elevation & Spacing */ --radius-lg:18px; - --shadow-sm:0 4px 10px rgba(0,0,0,.08); - --shadow-md:0 10px 30px rgba(0,0,0,.10); - --shadow-lg:0 18px 40px rgba(0,0,0,.15); + --shadow-sm:0 4px 12px rgba(15,23,42,.06); + --shadow-md:0 10px 28px rgba(15,23,42,.08); + --shadow-lg:0 22px 50px rgba(15,23,42,.10); --ring:0 0 0 3px rgba(99,102,241,.35); --s1:8px; --s2:12px; --s3:16px; --s4:20px; --s5:24px; --s6:32px; - /* Motion */ + /* Motion:与 design-system 对齐 */ --ease-standard:cubic-bezier(.2,.8,.2,1); - --duration-fast:.18s; - --duration-normal:.28s; + --duration-fast:160ms; + --duration-normal:240ms; } *{margin:0;padding:0;box-sizing:border-box} body{ - font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, 'Helvetica Neue', Arial, 'Noto Sans', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; - background: linear-gradient(135deg, var(--bg-grad-start) 0%, var(--bg-grad-end) 100%); + font-family: system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Helvetica Neue", Arial, "Noto Sans", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; + background: + radial-gradient(1100px 560px at 8% -8%, rgba(99,102,241,.14), transparent 58%), + radial-gradient(900px 480px at 100% 0%, rgba(139,92,246,.10), transparent 52%), + linear-gradient(160deg, var(--bg-grad-start) 0%, var(--bg-grad-end) 100%); min-height: 100vh; display: flex; align-items: center; @@ -135,15 +138,15 @@ .main-container{ background: color-mix(in oklab, white var(--surface-alpha), transparent); - -webkit-backdrop-filter: blur(8px); - backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(14px); + backdrop-filter: blur(14px); border-radius: var(--radius-lg); padding: clamp(20px, 4vw, 40px); box-shadow: var(--shadow-lg); max-width: 960px; width: 100%; text-align: center; - border: 1px solid color-mix(in oklab, white 70%, transparent); + border: 1px solid color-mix(in oklab, white 78%, rgba(99,102,241,.18)); } .language-switcher{ @@ -159,15 +162,18 @@ padding:6px 16px; border-radius:999px; border:1px solid rgba(99,102,241,.25); - background: rgba(255,255,255,.7); + background: rgba(255,255,255,.75); color: var(--text-primary); font-weight:600; cursor:pointer; - transition: transform var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard); + transition: transform var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard); } - .lang-switch:hover{ - transform: translateY(-1px); - box-shadow: var(--shadow-sm); + @media (hover: hover){ + .lang-switch:hover{ + transform: translateY(-1px); + box-shadow: var(--shadow-sm); + background: rgba(255,255,255,.92); + } } .lang-switch:focus-visible{ outline:none; @@ -205,19 +211,19 @@ align-items:center; justify-content:center; border-radius: 999px; - transition: transform var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard), background var(--duration-fast) var(--ease-standard); + transition: transform var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard); gap: 8px; box-shadow: var(--shadow-sm); text-decoration: none !important; outline: none; - will-change: transform; border: 1px solid rgba(255,255,255,.18); } .github-btn{ background: #24292e; color:#fff !important; } - .github-btn:hover{ transform: translateY(-1px); box-shadow: var(--shadow-md); background:#111; } - .kofi-btn{ background: #FF5E5B; color:#fff !important; } - .kofi-btn:hover{ transform: translateY(-1px); box-shadow: var(--shadow-md); background:#e64a47; } + @media (hover: hover){ + .github-btn:hover{ transform: translateY(-1px); box-shadow: var(--shadow-md); background:#111; } + .kofi-btn:hover{ transform: translateY(-1px); box-shadow: var(--shadow-md); background:#e64a47; } + } .github-btn:focus-visible, .kofi-btn:focus-visible{ outline: none; box-shadow: var(--ring); } @@ -230,15 +236,14 @@ @media (max-width: 720px){ .tools-grid{ grid-template-columns: 1fr; } } .tool-card{ - background: var(--surface); + background: color-mix(in oklab, var(--surface) 96%, transparent); border-radius: var(--radius-lg); padding: clamp(20px, 3.2vw, 28px); box-shadow: var(--shadow-md); - transition: transform var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard); - border: 2px solid transparent; + transition: transform var(--duration-normal) var(--ease-standard), box-shadow var(--duration-normal) var(--ease-standard), border-color var(--duration-normal) var(--ease-standard); + border: 1px solid color-mix(in oklab, var(--primary) 12%, transparent); position: relative; overflow: hidden; - will-change: transform, box-shadow; } .tool-card::before{ content: ''; @@ -247,12 +252,14 @@ height: 4px; background: linear-gradient(90deg, var(--primary), var(--secondary)); } - .tool-card:hover{ - transform: translateY(-6px); - box-shadow: var(--shadow-lg); - border-color: color-mix(in oklab, var(--primary) 40%, transparent); + @media (hover: hover){ + .tool-card:hover{ + transform: translateY(-4px); + box-shadow: var(--shadow-lg); + border-color: color-mix(in oklab, var(--primary) 36%, transparent); + } } - .tool-card:focus-within{ box-shadow: var(--ring); } + .tool-card:focus-within{ box-shadow: var(--ring), var(--shadow-md); } /* 品牌色注入 */ .giffgaff-card{ --primary:#ff6b00; --secondary:#ff8c42; } @@ -304,12 +311,14 @@ align-items: center; gap: 10px; transition: transform var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard), filter var(--duration-fast) var(--ease-standard); - box-shadow: 0 6px 16px color-mix(in oklab, var(--primary) 35%, transparent); + box-shadow: 0 6px 16px color-mix(in oklab, var(--primary) 32%, transparent); outline: none; - will-change: transform, filter; } - .tool-btn:hover{ transform: translateY(-2px); text-decoration: none; color: #fff; filter: brightness(1.03); } + @media (hover: hover){ + .tool-btn:hover{ transform: translateY(-2px); text-decoration: none; color: #fff; filter: brightness(1.04); box-shadow: 0 10px 22px color-mix(in oklab, var(--primary) 38%, transparent); } + } .tool-btn:focus-visible{ box-shadow: var(--ring); } + .tool-btn:active{ transform: scale(0.98); } .footer{ margin-top: var(--s6); @@ -322,13 +331,13 @@ } /* 入场动画:IntersectionObserver 触发,避免 setTimeout 抖动 */ - .reveal{ opacity: 0; transform: translateY(12px); transition: opacity .5s var(--ease-standard), transform .5s var(--ease-standard); will-change: opacity, transform; } + .reveal{ opacity: 0; transform: translateY(12px); transition: opacity var(--duration-normal) var(--ease-standard), transform var(--duration-normal) var(--ease-standard); } .reveal.show{ opacity: 1; transform: translateY(0); } /* 降低动效:尊重用户的减少动态偏好 */ @media (prefers-reduced-motion: reduce){ .reveal{ transition: none; transform: none; opacity: 1; } - .tool-card, .github-btn, .tool-btn{ transition: none!important; } + .tool-card, .github-btn, .kofi-btn, .tool-btn, .lang-switch{ transition: none!important; } } /* 小屏进一步优化 */ @@ -524,6 +533,12 @@ background: var(--surface); border-radius: var(--radius-lg); overflow: hidden; + border: 1px solid color-mix(in oklab, var(--brand-start) 10%, transparent); + transition: box-shadow var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard); + } + .faq-item[open]{ + border-color: color-mix(in oklab, var(--brand-start) 22%, transparent); + box-shadow: var(--shadow-sm); } .faq-item summary{ padding: var(--s3) var(--s4); @@ -531,16 +546,25 @@ font-size: .95rem; color: var(--text-primary); cursor: pointer; - transition: background var(--duration-fast) var(--ease-standard); + list-style: none; + transition: background-color var(--duration-fast) var(--ease-standard); } - .faq-item summary:hover{ - background: color-mix(in oklab, var(--brand-start) 5%, transparent); + .faq-item summary::-webkit-details-marker{ display: none; } + @media (hover: hover){ + .faq-item summary:hover{ + background: color-mix(in oklab, var(--brand-start) 6%, transparent); + } } .faq-item p{ padding: 0 var(--s4) var(--s3); font-size: .9rem; color: var(--text-secondary); line-height: 1.6; + animation: section-enter var(--duration-normal) var(--ease-standard) both; + } + @keyframes section-enter{ + from{ opacity: 0; transform: translateY(8px); } + to{ opacity: 1; transform: translateY(0); } } /* 安全声明 */ @@ -580,11 +604,94 @@ margin: 0; line-height: 1.5; } + + /* ===== 暗色模式 ===== */ + @media (prefers-color-scheme: dark){ + :root{ + --bg-grad-start:#0f1220; + --bg-grad-end:#0b0d14; + --surface:#1a1d2a; + --surface-alpha:88%; + --text-primary:#e8eaf0; + --text-secondary:#c4c9d4; + --text-muted:#9aa3b2; + --shadow-sm:0 4px 12px rgba(0,0,0,.35); + --shadow-md:0 10px 28px rgba(0,0,0,.4); + --shadow-lg:0 22px 50px rgba(0,0,0,.5); + --ring:0 0 0 3px rgba(129,140,248,.4); + } + + body{ + background: + radial-gradient(1100px 560px at 8% -8%, rgba(99,102,241,.18), transparent 58%), + radial-gradient(900px 480px at 100% 0%, rgba(139,92,246,.12), transparent 52%), + linear-gradient(160deg, var(--bg-grad-start) 0%, var(--bg-grad-end) 100%); + color: var(--text-primary); + } + + .main-container{ + background: color-mix(in oklab, #161926 var(--surface-alpha), transparent); + border-color: color-mix(in oklab, white 12%, rgba(99,102,241,.2)); + } + + .lang-switch{ + background: rgba(255,255,255,.08); + border-color: rgba(165,180,252,.3); + color: var(--text-primary); + } + @media (hover: hover){ + .lang-switch:hover{ background: rgba(255,255,255,.12); } + } + + .tool-card{ + background: color-mix(in oklab, var(--surface) 96%, transparent); + border-color: color-mix(in oklab, var(--primary) 22%, transparent); + } + + .compat-card, + .tutorial-card, + .region-card, + .glossary-item, + .faq-item, + .disclaimer-item{ + background: var(--surface); + border-color: color-mix(in oklab, var(--brand-start) 18%, transparent); + } + + .compat-check{ + background: color-mix(in oklab, var(--accent-ok) 14%, transparent); + } + .compat-check code{ + background: rgba(255,255,255,.08); + color: #e2e8f0; + } + + .compare-table{ + background: var(--surface); + } + .compare-table th{ + background: color-mix(in oklab, var(--brand-start) 18%, transparent); + color: var(--text-primary); + } + .compare-table td{ + border-bottom-color: rgba(255,255,255,.06); + color: var(--text-secondary); + } + + .footer{ + border-top-color: rgba(255,255,255,.08); + color: #94a3b8; + } + + .tutorial-note{ + background: color-mix(in oklab, var(--accent-warn) 14%, transparent); + } + } - +
diff --git a/src/giffgaff/giffgaff_modular.html b/src/giffgaff/giffgaff_modular.html index 4226f8c..b553395 100644 --- a/src/giffgaff/giffgaff_modular.html +++ b/src/giffgaff/giffgaff_modular.html @@ -2,7 +2,7 @@ - + diff --git a/src/giffgaff/js/modules/ui-controller.js b/src/giffgaff/js/modules/ui-controller.js index 86bc484..b3f8148 100644 --- a/src/giffgaff/js/modules/ui-controller.js +++ b/src/giffgaff/js/modules/ui-controller.js @@ -97,18 +97,40 @@ export class UIController { * 更新步骤指示器 */ updateSteps(currentStep) { - this.elements.steps.forEach((step, index) => { + const steps = this.elements.steps; + const total = steps.length; + + steps.forEach((step, index) => { const stepNo = index + 1; - if (stepNo <= currentStep) { + step.classList.remove('active', 'completed'); + + if (stepNo < currentStep) { + step.classList.add('completed'); + step.style.cursor = 'pointer'; + step.title = tl('点击返回此步骤'); + step.setAttribute('aria-current', 'false'); + } else if (stepNo === currentStep) { step.classList.add('active'); step.style.cursor = 'pointer'; - step.title = stepNo < currentStep ? tl('点击返回此步骤') : ''; + step.title = ''; + step.setAttribute('aria-current', 'step'); } else { - step.classList.remove('active'); step.style.cursor = 'not-allowed'; step.title = tl('请按顺序完成前序步骤'); + step.setAttribute('aria-current', 'false'); } }); + + // 进度轨:已完成区间占比(1 步时为 0,末步为 1) + const indicator = steps[0] && steps[0].closest + ? steps[0].closest('.step-indicator') + : null; + if (indicator && total > 1) { + const progress = Math.max(0, Math.min(1, (currentStep - 1) / (total - 1))); + indicator.style.setProperty('--step-progress', String(progress)); + } else if (indicator) { + indicator.style.setProperty('--step-progress', currentStep > 0 ? '1' : '0'); + } } /** diff --git a/src/giffgaff/styles/giffgaff-animations.css b/src/giffgaff/styles/giffgaff-animations.css index 2065bcc..3413f0b 100644 --- a/src/giffgaff/styles/giffgaff-animations.css +++ b/src/giffgaff/styles/giffgaff-animations.css @@ -45,96 +45,68 @@ 60% { transform: translateY(-3px); } } -/* ===== 服务时间动画 ===== */ +/* ===== 服务时间动画(仅 opacity / 阴影,禁止 scale 以降低干扰与耗电) ===== */ @keyframes breathing-light { 0%, 100% { - box-shadow: 0 0 5px rgba(220, 38, 38, 0.3); - transform: scale(1); + opacity: 1; + box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.25); } 50% { - box-shadow: 0 0 20px rgba(220, 38, 38, 0.6); - transform: scale(1.05); + opacity: 0.92; + box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.12); } } @keyframes breathing-light-orange { 0%, 100% { - box-shadow: 0 0 5px rgba(245, 158, 11, 0.3); - transform: scale(1); + opacity: 1; + box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.25); } 50% { - box-shadow: 0 0 20px rgba(245, 158, 11, 0.6); - transform: scale(1.05); + opacity: 0.92; + box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.12); } } @keyframes breathing-light-green { - 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); - } + 0%, 100% { opacity: 1; } + 50% { opacity: 0.88; } } @keyframes breathing-light-purple { - 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); - } + 0%, 100% { opacity: 1; } + 50% { opacity: 0.88; } } +/* 警告:轻微 opacity,不再缩放 */ @keyframes warning-pulse { - 0%, 100% { - transform: scale(1); - } - 50% { - transform: scale(1.05); - } + 0%, 100% { opacity: 1; } + 50% { opacity: 0.85; } } @keyframes success-bounce { - 0%, 100% { - transform: scale(1); - } - 25% { - transform: scale(1.02); - } - 75% { - transform: scale(1.02); - } + 0%, 100% { opacity: 1; } + 50% { opacity: 0.9; } } @keyframes pulse-glow { 0%, 100% { - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } 50% { - box-shadow: 0 4px 16px rgba(99, 102, 241, 0.3); + box-shadow: 0 4px 14px rgba(99, 102, 241, 0.18); } } +/* 时间数字更新:一次轻触反馈,由 JS 触发类名时使用 */ @keyframes time-update { - 0% { - transform: scale(1); - } - 50% { - transform: scale(1.1); - } - 100% { - transform: scale(1); - } + 0% { opacity: 0.7; } + 100% { opacity: 1; } } @keyframes time-breathe { - 0%, 100% { transform: translateZ(0) scale(1); } - 50% { transform: translateZ(0) scale(1.02); } + 0%, 100% { opacity: 1; } + 50% { opacity: 0.92; } } @keyframes modalSlideIn { @@ -144,5 +116,20 @@ /* ===== 服务时间图标过渡 ===== */ #serviceTimeIcon { - transition: all 0.3s ease; + transition: + color var(--transition-base, 240ms ease), + transform var(--transition-base, 240ms ease), + opacity var(--transition-base, 240ms ease); +} + +/* 步骤内容进入(与 design-system / animations 对齐的本地兜底) */ +@keyframes section-enter { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } } \ No newline at end of file diff --git a/src/giffgaff/styles/giffgaff-base.css b/src/giffgaff/styles/giffgaff-base.css index 1499653..d0a472f 100644 --- a/src/giffgaff/styles/giffgaff-base.css +++ b/src/giffgaff/styles/giffgaff-base.css @@ -29,7 +29,7 @@ body { color: #212529; min-height: 100vh; padding: 20px; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-family: var(--font-sans, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif); margin: 0; } @@ -61,7 +61,10 @@ textarea, input { background: rgba(255, 255, 255, 0.9); color: #212529; font-size: 16px; - transition: all 0.3s ease; + transition: + border-color var(--transition-base, 240ms ease), + box-shadow var(--transition-base, 240ms ease), + background-color var(--transition-base, 240ms ease); box-sizing: border-box; } @@ -77,21 +80,24 @@ textarea:focus, input:focus { background: rgba(255, 255, 255, 1); } -/* 卡片样式 */ +/* 卡片样式:内容卡仅微调阴影,避免填表时大幅上浮 */ .card { - background: rgba(255, 255, 255, 0.9); + background: rgba(255, 255, 255, 0.92); border-radius: 16px; border: 1px solid rgba(255, 204, 0, 0.3); - box-shadow: 0 4px 25px rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); margin-bottom: 30px; overflow: hidden; - transition: all 0.3s ease; + transition: + box-shadow var(--transition-base, 240ms ease), + border-color var(--transition-base, 240ms ease); } -.card:hover { - transform: translateY(-5px); - box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15); - border: 1px solid rgba(255, 204, 0, 0.5); +@media (hover: hover) { + .card:hover { + box-shadow: 0 6px 24px rgba(0, 0, 0, 0.1); + border-color: rgba(255, 204, 0, 0.45); + } } .card-header { @@ -132,4 +138,107 @@ textarea:focus, input:focus { display: flex; align-items: center; gap: 8px; +} + +/* ===== 暗色模式 ===== */ +@media (prefers-color-scheme: dark) { + :root { + --secondary: #1e1e1e; + --light: #1a1a1a; + --dark: #e9ecef; + --gray: #adb5bd; + --st-muted: #94a3b8; + --st-strong: #f1f5f9; + --st-card-shadow: 0 8px 28px rgba(0, 0, 0, 0.4); + --st-soft-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); + --st-border: 1px solid rgba(255, 255, 255, 0.08); + } + + body { + background: linear-gradient(135deg, #1a1610 0%, #12100c 45%, #0c0b09 100%); + color: #e9ecef; + } + + textarea, + input { + background: rgba(30, 30, 30, 0.92); + color: #e9ecef; + border-color: rgba(255, 204, 0, 0.28); + } + + textarea:focus, + input:focus { + background: rgba(36, 36, 36, 1); + border-color: var(--primary); + box-shadow: 0 0 0 4px rgba(255, 204, 0, 0.15); + } + + .card { + background: rgba(30, 30, 30, 0.92); + border-color: rgba(255, 204, 0, 0.22); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.35); + } + + @media (hover: hover) { + .card:hover { + box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45); + border-color: rgba(255, 204, 0, 0.38); + } + } + + .card-header { + background: rgba(255, 204, 0, 0.1); + color: #f8f9fa; + border-bottom-color: rgba(255, 204, 0, 0.18); + } + + .status-panel { + background: linear-gradient(135deg, rgba(36, 32, 20, 0.95) 0%, rgba(28, 26, 18, 0.95) 100%); + border-color: rgba(255, 255, 255, 0.08); + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35); + } + + .status-panel h3 { + color: #e9ecef; + } + + /* Bootstrap 工具类在暗色下的可读性 */ + .text-dark { + color: #e9ecef !important; + } + + .bg-light { + background-color: #2a2a2a !important; + color: #e9ecef; + } + + .bg-warning.text-dark, + .card-header.bg-warning { + color: #111 !important; + } + + .alert-danger { + background-color: rgba(127, 29, 29, 0.35) !important; + border-color: rgba(248, 113, 113, 0.35) !important; + color: #fecaca !important; + } + + .alert-warning { + background-color: rgba(120, 53, 15, 0.35) !important; + border-color: rgba(251, 191, 36, 0.35) !important; + color: #fde68a !important; + } + + .alert-info { + background-color: rgba(30, 58, 138, 0.35) !important; + border-color: rgba(96, 165, 250, 0.35) !important; + color: #bfdbfe !important; + } + + pre.bg-light, + .border.bg-light { + background-color: #242424 !important; + border-color: rgba(255, 255, 255, 0.1) !important; + color: #e2e8f0; + } } \ No newline at end of file diff --git a/src/giffgaff/styles/giffgaff-components.css b/src/giffgaff/styles/giffgaff-components.css index 0fa21a9..87783b6 100644 --- a/src/giffgaff/styles/giffgaff-components.css +++ b/src/giffgaff/styles/giffgaff-components.css @@ -50,7 +50,11 @@ align-items: center; justify-content: center; border-radius: 30px; - transition: all 0.3s; + transition: + transform var(--transition-fast, 160ms ease), + box-shadow var(--transition-fast, 160ms ease), + border-color var(--transition-base, 240ms ease), + background var(--transition-base, 240ms ease); gap: 8px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); text-decoration: none; @@ -59,11 +63,13 @@ flex-shrink: 0; } -.login-btn:hover { - background: linear-gradient(to right, #ffffff, #ffcc00); - transform: translateY(-2px); - box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15); - border-color: rgba(255, 204, 0, 0.5); +@media (hover: hover) { + .login-btn:hover { + background: linear-gradient(to right, #ffffff, #ffcc00); + transform: translateY(-2px); + box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15); + border-color: rgba(255, 204, 0, 0.5); + } } .github-btn { @@ -77,7 +83,12 @@ align-items: center; justify-content: center; border-radius: 30px; - transition: all 0.3s; + transition: + transform var(--transition-fast, 160ms ease), + box-shadow var(--transition-fast, 160ms ease), + border-color var(--transition-base, 240ms ease), + background var(--transition-base, 240ms ease), + color var(--transition-fast, 160ms ease); gap: 8px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); text-decoration: none; @@ -87,12 +98,14 @@ flex-shrink: 0; } -.github-btn:hover { - background: linear-gradient(to right, #666666, #333333); - transform: translateY(-2px); - box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15); - border-color: rgba(51, 51, 51, 0.5); - color: #ffffff; +@media (hover: hover) { + .github-btn:hover { + background: linear-gradient(to right, #666666, #333333); + transform: translateY(-2px); + box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15); + border-color: rgba(51, 51, 51, 0.5); + color: #ffffff; + } } .btn-group { @@ -108,7 +121,11 @@ font-size: 17px; font-weight: 600; cursor: pointer; - transition: all 0.3s; + transition: + transform var(--transition-fast, 160ms ease), + box-shadow var(--transition-fast, 160ms ease), + background var(--transition-base, 240ms ease), + opacity var(--transition-fast, 160ms ease); display: flex; align-items: center; justify-content: center; @@ -121,10 +138,12 @@ color: #212529; } -.btn-primary:hover { - background: linear-gradient(to right, #ffffff, #ffcc00); - transform: translateY(-3px); - box-shadow: 0 8px 15px rgba(0, 0, 0, 0.15); +@media (hover: hover) { + .btn-primary:hover { + background: linear-gradient(to right, #ffffff, #ffcc00); + transform: translateY(-2px); + box-shadow: 0 8px 15px rgba(0, 0, 0, 0.12); + } } .btn:disabled { @@ -142,15 +161,20 @@ border-radius: 6px; font-size: 14px; cursor: pointer; - transition: all 0.3s ease; + transition: + transform var(--transition-fast, 160ms ease), + box-shadow var(--transition-fast, 160ms ease), + background var(--transition-base, 240ms ease); display: flex; align-items: center; gap: 6px; } -.clear-session-btn:hover { - background: linear-gradient(135deg, #c82333 0%, #a71e2a 100%); - transform: translateY(-1px); +@media (hover: hover) { + .clear-session-btn:hover { + background: linear-gradient(135deg, #c82333 0%, #a71e2a 100%); + transform: translateY(-1px); + } } /* ===== 页面标题 ===== */ @@ -189,7 +213,7 @@ content: counter(list-item) ".\00A0\00A0"; } -/* ===== 步骤指示器 ===== */ +/* ===== 步骤指示器(进度轨见 design-system) ===== */ .step-indicator { display: flex; justify-content: space-between; @@ -211,6 +235,7 @@ z-index: 2; width: 120px; position: relative; + transition: opacity var(--transition-fast, 160ms ease); } .step-number { @@ -227,20 +252,45 @@ color: #212529; border: 2px solid rgba(255, 193, 7, 0.55); box-shadow: inset 0 0 0 2px rgba(255, 193, 7, 0.18); + transition: + background var(--transition-base, 240ms ease), + border-color var(--transition-base, 240ms ease), + box-shadow var(--transition-base, 240ms ease), + color var(--transition-base, 240ms ease), + transform var(--transition-fast, 160ms ease); +} + +.step.completed .step-number { + background: linear-gradient(135deg, #f0c000, #e6b800); + color: #212529; + border-color: rgba(230, 184, 0, 0.9); + box-shadow: 0 4px 12px rgba(255, 193, 7, 0.35); +} + +.step.completed .step-text { + color: #854d0e; + font-weight: 600; } .step.active .step-number { background: linear-gradient(135deg, #ffcc00, #ffd84d); - color: white; + color: #212529; box-shadow: 0 6px 16px rgba(255, 193, 7, 0.45); - border-color: rgba(255, 193, 7, 0.8); + border-color: rgba(255, 193, 7, 0.85); + transform: scale(1.06); +} + +.step.active .step-text { + color: #713f12; + font-weight: 700; } .step-text { font-size: 14px; text-align: center; - color: #212529; + color: #64748b; font-weight: 500; + transition: color var(--transition-fast, 160ms ease); } /* ===== 状态显示 ===== */ @@ -378,7 +428,8 @@ margin: 20px 0; padding: 16px 20px; box-shadow: 0 4px 15px rgba(255, 204, 0, 0.3); - animation: subtle-pulse 3s ease-in-out infinite; + /* 静态展示,避免持续脉冲 */ + animation: none; } .promo-content { @@ -392,7 +443,7 @@ .promo-icon { color: #212529; font-size: 20px; - animation: bounce 2s ease-in-out infinite; + animation: none; } .promo-text { @@ -406,7 +457,7 @@ color: #212529; text-decoration: underline; font-weight: 600; - transition: all 0.3s ease; + transition: color var(--transition-fast, 160ms ease), text-shadow var(--transition-fast, 160ms ease); } .promo-link:hover { @@ -415,14 +466,14 @@ text-shadow: 0 0 8px rgba(0, 0, 0, 0.2); } -/* ===== 章节控制 ===== */ +/* ===== 章节控制:进入时淡入上移 ===== */ .section { display: none; - animation: slideIn 0.5s ease; } .section.active { display: block; + animation: section-enter 0.35s var(--ease-out, cubic-bezier(.2, .8, .2, 1)) both; } .verification-section { @@ -432,7 +483,14 @@ .verification-section.active { display: block; - animation: fadeIn 0.5s ease; + animation: section-enter 0.35s var(--ease-out, cubic-bezier(.2, .8, .2, 1)) both; +} + +@media (prefers-reduced-motion: reduce) { + .section.active, + .verification-section.active { + animation: none !important; + } } /* ===== 表单标签 ===== */ @@ -612,3 +670,77 @@ display: block; animation: fadeIn 0.4s ease; } + +/* ===== 暗色模式:组件层 ===== */ +@media (prefers-color-scheme: dark) { + .page-title h1 { + color: #f8fafc; + } + + .page-title p { + color: rgba(233, 236, 239, 0.72); + } + + .step-indicator { + background: linear-gradient(135deg, #1e1e1e, #252525); + border-color: rgba(255, 255, 255, 0.08); + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35); + } + + .step-number { + background: #2a2618; + color: #f8fafc; + border-color: rgba(255, 193, 7, 0.45); + box-shadow: inset 0 0 0 2px rgba(255, 193, 7, 0.12); + } + + .step-text { + color: #94a3b8; + } + + .step.completed .step-text { + color: #fde68a; + } + + .step.active .step-text { + color: #fef3c7; + } + + .status { + background: rgba(30, 30, 30, 0.92); + color: #e9ecef; + } + + .status-item { + background: rgba(42, 42, 42, 0.9); + border-color: rgba(255, 255, 255, 0.08); + } + + .status-label { + color: #adb5bd; + } + + .status-value { + color: #d0d0d0; + } + + .form-label { + color: #e9ecef; + } + + .new-user-notice { + background: linear-gradient(135deg, rgba(127, 29, 29, 0.35), rgba(69, 10, 10, 0.45)); + border-color: rgba(248, 113, 113, 0.35); + color: #fecaca; + } + + .promo-banner { + box-shadow: 0 4px 18px rgba(255, 204, 0, 0.18); + animation: none; + } + + .login-btn { + color: #111; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35); + } +} diff --git a/src/giffgaff/styles/giffgaff-service-time.css b/src/giffgaff/styles/giffgaff-service-time.css index d2fc4f2..7bd3288 100644 --- a/src/giffgaff/styles/giffgaff-service-time.css +++ b/src/giffgaff/styles/giffgaff-service-time.css @@ -117,32 +117,35 @@ } #serviceTimeIcon.warning { - background: #e6f6ee; - color: #10b981; - box-shadow: inset 0 0 0 2px rgba(16,185,129,0.15); - animation: breathing-light-green 2s ease-in-out infinite; + 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; } #serviceTimeIcon.success { background: #e6f6ee; color: #10b981; box-shadow: inset 0 0 0 2px rgba(16,185,129,0.15); - animation: breathing-light-green 2s ease-in-out infinite; + animation: none; } -#serviceTimeIcon:hover { - transform: scale(1.1); +@media (hover: hover) { + #serviceTimeIcon:hover { + transform: scale(1.04); + } } .alert-warning #serviceTimeIcon { - animation: warning-pulse 2s ease-in-out infinite; + animation: warning-pulse 2.8s 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: breathing-light-green 2s ease-in-out infinite; + animation: none; background: #e0f6eb; color: #10b981; box-shadow: inset 0 0 0 2px rgba(16,185,129,0.15); @@ -157,7 +160,11 @@ line-height: 1.2; white-space: nowrap; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); - transition: all 0.3s ease; + transition: + transform var(--transition-fast, 160ms ease), + box-shadow var(--transition-base, 240ms ease), + background-color var(--transition-base, 240ms ease), + border-color var(--transition-base, 240ms ease); z-index: 10; flex-shrink: 0; text-align: center; @@ -167,7 +174,7 @@ background: #f0fdf4; color: #16a34a; border: 2px solid #16a34a; - animation: breathing-light-green 2s ease-in-out infinite; + animation: none; } .service-time-action-badge.warning { @@ -176,7 +183,8 @@ border: 2px solid #dc2626; font-weight: 800; text-shadow: none; - animation: breathing-light 2s ease-in-out infinite; + /* 仅警告徽章保留轻量脉动 */ + animation: breathing-light 3s ease-in-out infinite; } .service-time-action-badge:hover { @@ -199,7 +207,8 @@ position: relative; overflow: hidden; isolation: isolate; - animation: pulse-glow 2s ease-in-out infinite; + /* 时间卡静态展示,避免持续 glow 动画 */ + animation: none; } .current-time-card::after { @@ -219,7 +228,7 @@ display: grid; place-items: center; box-shadow: inset 0 0 0 1px rgba(99,102,241,0.22); - animation: breathing-light-purple 2s ease-in-out infinite; + animation: none; } .time-card-icon i { @@ -247,11 +256,92 @@ letter-spacing: 1.2px; color: #0b1324; text-shadow: 0 1px 0 rgba(255,255,255,0.6); - animation: time-breathe 3s ease-in-out infinite; + /* 静态可读,避免持续呼吸动画 */ + animation: none; } .time-card-hint { font-size: 12px; color: #6b7280; margin-top: 6px; +} + +/* 服务时间:暗色模式 */ +@media (prefers-color-scheme: dark) { + .service-time-grid { + background: linear-gradient(135deg, #2a2418 0%, #1f1b14 100%); + border-color: rgba(255, 193, 7, 0.22); + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4); + } + + .service-time-left { + background: linear-gradient(135deg, #322b1c 60%, #241f16 60%); + border-color: rgba(255, 193, 7, 0.2); + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.35); + } + + .service-time-title { + color: #f8fafc; + } + + .service-time-message { + color: #cbd5e1; + } + + .current-time-card { + background: linear-gradient(135deg, #2a2418 60%, #322b1c 60%); + box-shadow: 0 8px 28px rgba(0, 0, 0, 0.4); + } + + .current-time-card::after { + background: linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0)); + } + + .time-card-value { + color: #f8fafc; + text-shadow: none; + } + + .time-card-hint { + color: #94a3b8; + } + + #serviceTimeIcon { + background: rgba(16, 185, 129, 0.15); + color: #34d399; + box-shadow: inset 0 0 0 2px rgba(16, 185, 129, 0.25); + } + + .alert-warning #serviceTimeIcon, + #serviceTimeIcon.warning { + background: rgba(245, 158, 11, 0.18); + color: #fbbf24; + box-shadow: inset 0 0 0 2px rgba(245, 158, 11, 0.28); + } + + .service-time-action-badge.success { + background: rgba(22, 163, 74, 0.15); + color: #4ade80; + border-color: #16a34a; + } + + .service-time-action-badge.warning { + background: rgba(220, 38, 38, 0.15); + color: #f87171; + border-color: #dc2626; + } + + .time-card-icon { + background: linear-gradient(135deg, #1e1b2e, #252038); + box-shadow: inset 0 0 0 1px rgba(129, 140, 248, 0.28); + } +} + +/* 尊重减少动态偏好:去掉剩余状态脉动 */ +@media (prefers-reduced-motion: reduce) { + #serviceTimeIcon.warning, + .alert-warning #serviceTimeIcon, + .service-time-action-badge.warning { + animation: none !important; + } } \ No newline at end of file diff --git a/src/simyo/js/modules/ui-controller.js b/src/simyo/js/modules/ui-controller.js index af44ba5..3f6c2e8 100644 --- a/src/simyo/js/modules/ui-controller.js +++ b/src/simyo/js/modules/ui-controller.js @@ -85,18 +85,40 @@ export class UIController { * 更新步骤指示器 */ updateSteps(currentStep) { - this.elements.steps.forEach((step, index) => { + const steps = this.elements.steps; + const total = steps.length; + + steps.forEach((step, index) => { const stepNo = index + 1; - if (stepNo <= currentStep) { + step.classList.remove('active', 'completed'); + + if (stepNo < currentStep) { + step.classList.add('completed'); + step.style.cursor = 'pointer'; + step.title = tl('点击返回此步骤'); + step.setAttribute('aria-current', 'false'); + } else if (stepNo === currentStep) { step.classList.add('active'); step.style.cursor = 'pointer'; - step.title = stepNo < currentStep ? tl('点击返回此步骤') : ''; + step.title = ''; + step.setAttribute('aria-current', 'step'); } else { - step.classList.remove('active'); step.style.cursor = 'not-allowed'; step.title = tl('请按顺序完成前序步骤'); + step.setAttribute('aria-current', 'false'); } }); + + // 进度轨:已完成区间占比(1 步时为 0,末步为 1) + const indicator = steps[0] && steps[0].closest + ? steps[0].closest('.step-indicator') + : null; + if (indicator && total > 1) { + const progress = Math.max(0, Math.min(1, (currentStep - 1) / (total - 1))); + indicator.style.setProperty('--step-progress', String(progress)); + } else if (indicator) { + indicator.style.setProperty('--step-progress', currentStep > 0 ? '1' : '0'); + } } /** diff --git a/src/simyo/simyo_modular.html b/src/simyo/simyo_modular.html index 5d11a2c..5766422 100644 --- a/src/simyo/simyo_modular.html +++ b/src/simyo/simyo_modular.html @@ -2,7 +2,7 @@ - + diff --git a/src/simyo/styles/simyo-animations.css b/src/simyo/styles/simyo-animations.css index 39b0be6..0abfc9a 100644 --- a/src/simyo/styles/simyo-animations.css +++ b/src/simyo/styles/simyo-animations.css @@ -54,8 +54,9 @@ .reveal { opacity: 0; transform: translateY(12px); - transition: opacity 0.5s ease, transform 0.5s ease; - will-change: opacity, transform; + transition: + opacity var(--transition-base, 240ms ease), + transform var(--transition-base, 240ms ease); } .reveal.show { @@ -63,6 +64,18 @@ transform: translateY(0); } +/* 步骤内容进入 */ +@keyframes section-enter { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + /* 降低动效:尊重用户的减少动态偏好 */ @media (prefers-reduced-motion: reduce) { .reveal { diff --git a/src/simyo/styles/simyo-base.css b/src/simyo/styles/simyo-base.css index 44241e9..134ecea 100644 --- a/src/simyo/styles/simyo-base.css +++ b/src/simyo/styles/simyo-base.css @@ -19,7 +19,7 @@ body { color: #212529; min-height: 100vh; padding: 20px; - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-family: var(--font-sans, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif); margin: 0; } @@ -83,7 +83,7 @@ body { margin-top: 8px; } -/* 步骤指示器 */ +/* 步骤指示器(进度轨见 design-system .step-indicator::before/::after) */ .step-indicator { display: flex; justify-content: space-between; @@ -105,6 +105,7 @@ body { z-index: 2; width: 120px; position: relative; + transition: opacity var(--transition-fast, 160ms ease); } .step-number { @@ -121,20 +122,45 @@ body { color: #212529; border: 2px solid rgba(255, 107, 0, 0.55); box-shadow: inset 0 0 0 2px rgba(255, 107, 0, 0.18); + transition: + background var(--transition-base, 240ms ease), + border-color var(--transition-base, 240ms ease), + box-shadow var(--transition-base, 240ms ease), + color var(--transition-base, 240ms ease), + transform var(--transition-fast, 160ms ease); +} + +.step.completed .step-number { + background: linear-gradient(135deg, #ff8a2b, #ff6b00); + color: #fff; + border-color: rgba(255, 107, 0, 0.9); + box-shadow: 0 4px 12px rgba(255, 107, 0, 0.3); +} + +.step.completed .step-text { + color: #c2410c; + font-weight: 600; } .step.active .step-number { background: linear-gradient(135deg, #ff7a1a, #ffb066); - color: white; + color: #fff; + border-color: rgba(255, 107, 0, 0.85); box-shadow: 0 6px 16px rgba(255, 107, 0, 0.45); - border-color: rgba(255, 107, 0, 0.8); + transform: scale(1.06); +} + +.step.active .step-text { + color: #9a3412; + font-weight: 700; } .step-text { font-size: 14px; text-align: center; - color: #212529; + color: #64748b; font-weight: 500; + transition: color var(--transition-fast, 160ms ease); } /* 表单元素 */ @@ -154,7 +180,10 @@ textarea, input { background: rgba(255, 255, 255, 0.9); color: #212529; font-size: 16px; - transition: all 0.3s ease; + transition: + border-color var(--transition-base, 240ms ease), + box-shadow var(--transition-base, 240ms ease), + background-color var(--transition-base, 240ms ease); box-sizing: border-box; } @@ -184,13 +213,14 @@ textarea:focus-visible { outline-offset: 2px; } -/* 区块显示控制 */ +/* 区块显示控制:进入时淡入上移 */ .section { display: none; } .section.active { display: block; + animation: section-enter 0.35s var(--ease-out, cubic-bezier(.2, .8, .2, 1)) both; } #resultContainer { @@ -199,6 +229,14 @@ textarea:focus-visible { #resultContainer.active { display: block; + animation: section-enter 0.35s var(--ease-out, cubic-bezier(.2, .8, .2, 1)) both; +} + +@media (prefers-reduced-motion: reduce) { + .section.active, + #resultContainer.active { + animation: none !important; + } } /* 加载动画 */ @@ -239,6 +277,100 @@ textarea:focus-visible { opacity: 1; } +/* ===== 暗色模式 ===== */ +@media (prefers-color-scheme: dark) { + :root { + --secondary: #2a1f16; + --light: #1a1a1a; + --dark: #e9ecef; + --gray: #adb5bd; + } + + body { + background: linear-gradient(135deg, #1a1410 0%, #12100e 50%, #0c0b0a 100%); + color: #e9ecef; + } + + .app-title { + background: linear-gradient(to right, #ff8a3d, #6eb0ff); + background-clip: text; + -webkit-background-clip: text; + } + + .page-title h1 { + color: #f1f5f9; + } + + .page-title p { + color: rgba(233, 236, 239, 0.72); + } + + .step-indicator { + background: linear-gradient(135deg, #1e1e1e, #252525); + border-color: rgba(255, 255, 255, 0.08); + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35); + } + + .step-number { + background: #2a2118; + color: #f8fafc; + border-color: rgba(255, 107, 0, 0.45); + box-shadow: inset 0 0 0 2px rgba(255, 107, 0, 0.12); + } + + .step-text { + color: #94a3b8; + } + + .step.completed .step-text { + color: #fdba74; + } + + .step.active .step-text { + color: #ffedd5; + } + + textarea, + input { + background: rgba(30, 30, 30, 0.92); + color: #e9ecef; + border-color: rgba(255, 107, 0, 0.28); + } + + textarea:focus, + input:focus { + background: rgba(36, 36, 36, 1); + box-shadow: 0 0 0 4px rgba(255, 107, 0, 0.18); + } + + .text-dark { + color: #e9ecef !important; + } + + .bg-light { + background-color: #2a2a2a !important; + color: #e9ecef; + } + + .alert-danger { + background-color: rgba(127, 29, 29, 0.35) !important; + border-color: rgba(248, 113, 113, 0.35) !important; + color: #fecaca !important; + } + + .alert-warning { + background-color: rgba(120, 53, 15, 0.35) !important; + border-color: rgba(251, 191, 36, 0.35) !important; + color: #fde68a !important; + } + + .alert-info { + background-color: rgba(30, 58, 138, 0.35) !important; + border-color: rgba(96, 165, 250, 0.35) !important; + color: #bfdbfe !important; + } +} + /* Toast通知样式 */ .toast-notification { position: fixed; diff --git a/src/simyo/styles/simyo-components.css b/src/simyo/styles/simyo-components.css index a0daa9d..df31a7b 100644 --- a/src/simyo/styles/simyo-components.css +++ b/src/simyo/styles/simyo-components.css @@ -3,21 +3,24 @@ * 定义卡片、按钮、状态面板等组件样式 */ -/* 卡片样式 */ +/* 卡片样式:内容卡仅微调阴影,避免填表时大幅上浮 */ .card { - background: rgba(255, 255, 255, 0.9); + background: rgba(255, 255, 255, 0.92); border-radius: 16px; border: 1px solid rgba(255, 107, 0, 0.3); - box-shadow: 0 4px 25px rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); margin-bottom: 30px; overflow: hidden; - transition: all 0.3s ease; + transition: + box-shadow var(--transition-base, 240ms ease), + border-color var(--transition-base, 240ms ease); } -.card:hover { - transform: translateY(-5px); - box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15); - border: 1px solid rgba(255, 107, 0, 0.5); +@media (hover: hover) { + .card:hover { + box-shadow: 0 6px 24px rgba(0, 0, 0, 0.1); + border-color: rgba(255, 107, 0, 0.45); + } } .card-header { @@ -55,7 +58,11 @@ font-size: 17px; font-weight: 600; cursor: pointer; - transition: all 0.3s; + transition: + transform var(--transition-fast, 160ms ease), + box-shadow var(--transition-fast, 160ms ease), + background var(--transition-base, 240ms ease), + opacity var(--transition-fast, 160ms ease); display: flex; align-items: center; justify-content: center; @@ -68,10 +75,12 @@ color: #212529; } -.btn-primary:hover { - background: linear-gradient(to right, #ffffff, #ff6b00); - transform: translateY(-3px); - box-shadow: 0 8px 15px rgba(0, 0, 0, 0.15); +@media (hover: hover) { + .btn-primary:hover { + background: linear-gradient(to right, #ffffff, #ff6b00); + transform: translateY(-2px); + box-shadow: 0 8px 15px rgba(0, 0, 0, 0.12); + } } .btn:disabled { @@ -92,7 +101,11 @@ align-items: center; justify-content: center; border-radius: 30px; - transition: all 0.3s; + transition: + transform var(--transition-fast, 160ms ease), + box-shadow var(--transition-fast, 160ms ease), + border-color var(--transition-base, 240ms ease), + background var(--transition-base, 240ms ease); gap: 8px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); text-decoration: none; @@ -101,11 +114,13 @@ flex-shrink: 0; } -.help-btn:hover { - background: linear-gradient(to right, #ffffff, #ff6b00); - transform: translateY(-2px); - box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15); - border-color: rgba(255, 107, 0, 0.5); +@media (hover: hover) { + .help-btn:hover { + background: linear-gradient(to right, #ffffff, #ff6b00); + transform: translateY(-2px); + box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15); + border-color: rgba(255, 107, 0, 0.5); + } } .github-btn { @@ -119,7 +134,12 @@ align-items: center; justify-content: center; border-radius: 30px; - transition: all 0.3s; + transition: + transform var(--transition-fast, 160ms ease), + box-shadow var(--transition-fast, 160ms ease), + border-color var(--transition-base, 240ms ease), + background var(--transition-base, 240ms ease), + color var(--transition-fast, 160ms ease); gap: 8px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); text-decoration: none; @@ -129,12 +149,14 @@ flex-shrink: 0; } -.github-btn:hover { - background: linear-gradient(to right, #666666, #333333); - transform: translateY(-2px); - box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15); - border-color: rgba(51, 51, 51, 0.5); - color: #ffffff; +@media (hover: hover) { + .github-btn:hover { + background: linear-gradient(to right, #666666, #333333); + transform: translateY(-2px); + box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15); + border-color: rgba(51, 51, 51, 0.5); + color: #ffffff; + } } /* 状态显示 */ @@ -259,15 +281,19 @@ border-radius: 6px; font-size: 14px; cursor: pointer; - transition: all 0.3s ease; + transition: + transform var(--transition-fast, 160ms ease), + background var(--transition-base, 240ms ease); display: flex; align-items: center; gap: 6px; } -.clear-session-btn:hover { - background: linear-gradient(135deg, #c82333 0%, #a71e2a 100%); - transform: translateY(-1px); +@media (hover: hover) { + .clear-session-btn:hover { + background: linear-gradient(135deg, #c82333 0%, #a71e2a 100%); + transform: translateY(-1px); + } } /* 信息卡片 */ @@ -298,7 +324,7 @@ margin: 20px 0; padding: 16px 20px; box-shadow: 0 4px 15px rgba(255, 107, 0, 0.3); - animation: subtle-pulse 3s ease-in-out infinite; + animation: none; } .promo-content { @@ -312,7 +338,7 @@ .promo-icon { color: white; font-size: 20px; - animation: bounce 2s ease-in-out infinite; + animation: none; } .promo-text { @@ -326,7 +352,7 @@ color: white; text-decoration: underline; font-weight: 600; - transition: all 0.3s ease; + transition: color var(--transition-fast, 160ms ease), text-shadow var(--transition-fast, 160ms ease); } .promo-link:hover { @@ -344,4 +370,63 @@ .qrcode-container:hover .tooltip { opacity: 1; +} + +/* ===== 暗色模式:组件层 ===== */ +@media (prefers-color-scheme: dark) { + .card { + background: rgba(30, 30, 30, 0.92); + border-color: rgba(255, 107, 0, 0.28); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.35); + } + + @media (hover: hover) { + .card:hover { + box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45); + border-color: rgba(255, 107, 0, 0.42); + } + } + + .card-header { + background: rgba(255, 107, 0, 0.12); + color: #f8f9fa; + border-bottom-color: rgba(255, 107, 0, 0.2); + } + + .status { + background: rgba(30, 30, 30, 0.92); + color: #e9ecef; + } + + .status-item { + background: rgba(42, 42, 42, 0.9); + border-color: rgba(255, 255, 255, 0.08); + } + + .status-label { + color: #adb5bd; + } + + .status-value { + color: #d0d0d0; + } + + .form-label { + color: #e9ecef; + } + + .info-card { + background: linear-gradient(135deg, #2a1f16 0%, #3d2818 100%); + color: #f8fafc; + border-color: rgba(255, 107, 0, 0.3); + } + + .promo-banner { + animation: none; + box-shadow: 0 4px 18px rgba(255, 107, 0, 0.22); + } + + .promo-icon { + animation: none; + } } \ No newline at end of file diff --git a/src/styles/animations.css b/src/styles/animations.css index 9cb35fc..b392738 100644 --- a/src/styles/animations.css +++ b/src/styles/animations.css @@ -1,70 +1,48 @@ -/* 微交互动画和触摸反馈 */ +/* 微交互动画与触摸反馈 + 依赖 design-system.css 的 motion tokens,禁止在此重新定义 --transition-* 时长格式 +*/ -/* 基础动画变量 */ +/* 本地缓动别名(与 design-system 对齐,作兜底) */ :root { - --transition-fast: 0.15s ease-out; - --transition-normal: 0.3s ease-out; - --transition-slow: 0.5s ease-out; --bounce-ease: cubic-bezier(0.68, -0.55, 0.265, 1.55); - --smooth-ease: cubic-bezier(0.4, 0, 0.2, 1); + --smooth-ease: var(--ease-out, cubic-bezier(0.22, 0.61, 0.36, 1)); } -/* 按钮点击反馈动画 */ +/* 按钮点击反馈 */ .btn { position: relative; overflow: hidden; - transition: all var(--transition-normal) var(--smooth-ease); - transform: translateZ(0); - will-change: transform; + transition: + transform var(--transition-fast, 120ms var(--smooth-ease)), + box-shadow var(--transition-fast, 120ms var(--smooth-ease)), + background-color var(--transition-base, 200ms var(--smooth-ease)), + border-color var(--transition-base, 200ms var(--smooth-ease)), + color var(--transition-fast, 120ms var(--smooth-ease)), + opacity var(--transition-fast, 120ms var(--smooth-ease)); } .btn:active { - transform: scale(0.95) translateZ(0); + transform: scale(0.98); } -.btn::before { - content: ''; - position: absolute; - top: 50%; - left: 50%; - width: 0; - height: 0; - background: rgba(255, 255, 255, 0.3); - border-radius: 50%; - transform: translate(-50%, -50%); - transition: width 0.6s, height 0.6s; -} - -.btn:active::before { - width: 300px; - height: 300px; -} - -/* 卡片悬停效果 */ +/* 卡片:仅声明过渡,上浮由可导航卡片自行定义 */ .card { - transition: all var(--transition-normal) var(--smooth-ease); - transform: translateZ(0); - will-change: transform, box-shadow; + transition: + transform var(--transition-base, 200ms var(--smooth-ease)), + box-shadow var(--transition-base, 200ms var(--smooth-ease)), + border-color var(--transition-base, 200ms var(--smooth-ease)); } -.card:hover { - transform: translateY(-2px) translateZ(0); - box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); -} - -.card:active { - transform: translateY(0) translateZ(0); -} - -/* 输入框焦点动画 */ +/* 输入框焦点:ring 即可,避免 scale 造成布局抖动 */ .form-control { - transition: all var(--transition-normal) var(--smooth-ease); - transform: translateZ(0); + transition: + border-color var(--transition-base, 200ms var(--smooth-ease)), + box-shadow var(--transition-base, 200ms var(--smooth-ease)), + background-color var(--transition-base, 200ms var(--smooth-ease)); } .form-control:focus { - transform: scale(1.02) translateZ(0); - box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1); + box-shadow: 0 0 0 3px var(--ring-color, rgba(0, 123, 255, 0.15)); } /* 加载状态指示器 */ @@ -95,7 +73,7 @@ /* 淡入动画 */ .fade-in { - animation: fadeIn 0.5s ease-out; + animation: fadeIn 0.35s var(--smooth-ease); } @keyframes fadeIn { @@ -103,14 +81,26 @@ to { opacity: 1; transform: translateY(0); } } +/* 步骤内容区进入动画 */ +@keyframes section-enter { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + /* 滑入动画 */ .slide-in { - animation: slideIn 0.3s ease-out; + animation: slideIn 0.3s var(--smooth-ease); } @keyframes slideIn { - from { transform: translateX(-100%); } - to { transform: translateX(0); } + from { transform: translateX(-100%); opacity: 0; } + to { transform: translateX(0); opacity: 1; } } /* 弹跳动画 */ @@ -131,18 +121,9 @@ -webkit-tap-highlight-color: transparent; touch-action: manipulation; } - + .btn:active { transform: scale(0.98); - background-color: rgba(0, 0, 0, 0.1); - } - - .card:active { - transform: scale(0.98); - } - - .form-control:focus { - transform: none; } } @@ -177,7 +158,9 @@ z-index: 9999; opacity: 0; visibility: hidden; - transition: all var(--transition-normal); + transition: + opacity var(--transition-base, 200ms var(--smooth-ease)), + visibility var(--transition-base, 200ms var(--smooth-ease)); } .loading-overlay.show { @@ -188,7 +171,7 @@ /* 进度条动画 */ .progress-bar { height: 4px; - background: linear-gradient(90deg, #007bff, #28a745); + background: linear-gradient(90deg, var(--primary, #007bff), #28a745); border-radius: 2px; overflow: hidden; position: relative; @@ -210,10 +193,12 @@ 100% { left: 100%; } } -/* 工具提示动画优化 */ +/* 工具提示动画 */ .tooltip { - transition: opacity var(--transition-fast), transform var(--transition-fast); - transform: scale(0.9); + transition: + opacity var(--transition-fast, 120ms var(--smooth-ease)), + transform var(--transition-fast, 120ms var(--smooth-ease)); + transform: scale(0.96); transform-origin: center bottom; } @@ -223,30 +208,34 @@ /* 状态切换动画 */ .status-transition { - transition: all var(--transition-normal) var(--smooth-ease); + transition: + color var(--transition-base, 200ms var(--smooth-ease)), + transform var(--transition-base, 200ms var(--smooth-ease)); } .status-transition.connected { color: #28a745; - transform: scale(1.05); } .status-transition.disconnected { color: #dc3545; - transform: scale(0.95); } - /* 骨架屏动画 */ @keyframes skeleton-loading { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } -/* 步骤脉冲环动画 */ +/* 步骤脉冲环(颜色跟随品牌 --primary) */ @keyframes pulse-ring { - 0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(13, 110, 253, 0.7); } - 70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(13, 110, 253, 0); } - 100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(13, 110, 253, 0); } + 0% { + box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary, #0d6efd) 55%, transparent); + } + 70% { + box-shadow: 0 0 0 10px color-mix(in srgb, var(--primary, #0d6efd) 0%, transparent); + } + 100% { + box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary, #0d6efd) 0%, transparent); + } } - \ No newline at end of file diff --git a/src/styles/design-system.css b/src/styles/design-system.css index a9c42e0..79f5a6b 100644 --- a/src/styles/design-system.css +++ b/src/styles/design-system.css @@ -1,9 +1,8 @@ /* Design System: tokens, utilities, base components Scope: shared across brand pages. Brands override --primary/--secondary in page-level :root. + 字体使用系统栈,避免阻塞式 Google Fonts @import。 */ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); - /* 按钮组与打赏按钮样式 */ .header-actions { display: flex; @@ -22,10 +21,9 @@ justify-content: center; border-radius: 999px; gap: 8px; - transition: transform var(--transition-fast), box-shadow var(--transition-fast), background var(--transition-fast); + transition: transform var(--transition-fast), box-shadow var(--transition-fast), background-color var(--transition-fast); text-decoration: none !important; box-shadow: var(--shadow-sm); - will-change: transform; border: 1px solid rgba(255,255,255,0.1); } @@ -90,12 +88,13 @@ --shadow-md: 0 4px 18px rgba(0,0,0,0.12); --shadow-lg: 0 8px 28px rgba(0,0,0,0.18); - /* Motion */ - --ease-out: cubic-bezier(.22,.61,.36,1); - --ease-in: cubic-bezier(.55,.085,.68,.53); - --transition-fast: 120ms var(--ease-out); - --transition-base: 200ms var(--ease-out); - --transition-slow: 320ms var(--ease-out); + /* Motion:全站唯一时长/缓动源,品牌页与 animations.css 禁止覆盖为其它格式 */ + --ease-out: cubic-bezier(.2, .8, .2, 1); + --ease-in: cubic-bezier(.55, .085, .68, .53); + --ease-standard: var(--ease-out); + --transition-fast: 160ms var(--ease-out); + --transition-base: 240ms var(--ease-out); + --transition-slow: 360ms var(--ease-out); /* Focus ring */ --ring-color: rgba(76,154,255,.35); @@ -110,8 +109,8 @@ --space-6: 24px; --space-8: 32px; - /* Typography */ - --font-sans: 'Inter', system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; + /* Typography:系统字体栈,含中文回退 */ + --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; } /* Dark Mode Support */ @@ -161,10 +160,22 @@ color: var(--text); } + /* body 背景由各品牌页 / 首页自行定义,避免全局硬覆盖品牌色渐变 */ body { - background: linear-gradient(135deg, #1a1a1a 0%, #000000 100%); color: var(--text); } + + .card-header-soft { + color: var(--text); + } + + .status-label { + color: var(--muted); + } + + .loading-overlay { + background: rgba(0, 0, 0, 0.6); + } } /* 2) Accessibility and base resets */ @@ -265,9 +276,8 @@ html[data-locale="en"] .lang-en { display: inline !important; } /* Reveal-on-scroll animation */ .reveal { opacity: 0; - transform: translateY(16px); + transform: translateY(12px); transition: opacity var(--transition-base), transform var(--transition-base); - will-change: opacity, transform; } .reveal.show { opacity: 1; @@ -290,10 +300,6 @@ html[data-locale="en"] .lang-en { display: inline !important; } * { scroll-behavior: auto !important; } } -/* Fine-tune reveal transition to be lightweight and smooth */ -.reveal { will-change: opacity, transform; } -.reveal.show { transition: transform var(--transition-base), opacity var(--transition-base); } - /* 4) Components */ .card-elevated { background: var(--surface-glass); @@ -461,17 +467,17 @@ html[data-locale="en"] .lang-en { display: inline !important; } } .tooltip.show { opacity: 1; } -/* Promo banner */ +/* Promo banner:静态展示,避免持续 scale 动画 */ .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: subtle-pulse 3s ease-in-out infinite; + animation: none; } @keyframes subtle-pulse { - 0%,100% { transform: scale(1); } - 50% { transform: scale(1.02); } + 0%,100% { opacity: 1; } + 50% { opacity: 0.92; } } /* Reveal helpers for QR preview hovers may reuse .tooltip from above */ @@ -528,10 +534,65 @@ html[data-locale="en"] .lang-en { display: inline !important; } } } -/* Step Indicator Pulse */ +/* Step Indicator:当前步脉冲(颜色跟随 --primary) */ .step.active .step-number { - box-shadow: 0 0 0 0 rgba(13, 110, 253, 0.7); - animation: pulse-ring 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + animation: pulse-ring 2s var(--ease-out) infinite; +} + +/* 步骤指示器进度轨(--step-progress: 0~1,由 JS 写入) */ +.step-indicator { + --step-progress: 0; +} + +.step-indicator::before, +.step-indicator::after { + content: ''; + position: absolute; + top: calc(16px + 28px); + left: 10%; + right: 10%; + height: 3px; + border-radius: 999px; + z-index: 1; + pointer-events: none; +} + +.step-indicator::before { + background: color-mix(in srgb, var(--muted, #6c757d) 22%, transparent); +} + +.step-indicator::after { + right: auto; + width: calc((100% - 20%) * var(--step-progress, 0)); + background: linear-gradient( + 90deg, + color-mix(in srgb, var(--primary) 75%, transparent), + var(--primary) + ); + transition: width var(--transition-slow); +} + +@media (max-width: 768px) { + .step-indicator::before, + .step-indicator::after { + top: calc(16px + 20px); + } +} + +@media (max-width: 480px) { + .step-indicator::before, + .step-indicator::after { + top: calc(16px + 17px); + } +} + +@media (prefers-reduced-motion: reduce) { + .step.active .step-number { + animation: none !important; + } + .step-indicator::after { + transition: none !important; + } } /* Keyboard focus visibility for custom buttons built on */ diff --git a/src/styles/mobile-responsive.css b/src/styles/mobile-responsive.css index 493c26d..eb6ad9f 100644 --- a/src/styles/mobile-responsive.css +++ b/src/styles/mobile-responsive.css @@ -363,10 +363,9 @@ a:not(.btn) { 性能优化 ======================================== */ -/* GPU加速 */ +/* GPU 加速:仅在动画中临时使用,避免常驻 will-change */ .gpu-accelerated { transform: translateZ(0); - will-change: transform; } /* 减少重绘 */ diff --git a/src/styles/notification.css b/src/styles/notification.css index a0e5370..8fd0ce3 100644 --- a/src/styles/notification.css +++ b/src/styles/notification.css @@ -2,8 +2,8 @@ .notification-container { position: fixed; - top: 20px; - right: 20px; + top: max(16px, calc(env(safe-area-inset-top, 0px) + 12px)); + right: max(16px, calc(env(safe-area-inset-right, 0px) + 12px)); z-index: 10000; display: flex; flex-direction: column; @@ -25,9 +25,8 @@ pointer-events: auto; transform: translateX(calc(100% + 20px)); opacity: 0; - transition: transform var(--transition-base, 200ms) var(--ease-out, cubic-bezier(.22,.61,.36,1)), - opacity var(--transition-base, 200ms) var(--ease-out, cubic-bezier(.22,.61,.36,1)); - will-change: transform, opacity; + transition: transform var(--transition-base, 240ms var(--ease-out, cubic-bezier(.2,.8,.2,1))), + opacity var(--transition-base, 240ms var(--ease-out, cubic-bezier(.2,.8,.2,1))); } .notification.show { @@ -64,10 +63,12 @@ background: none; border: none; padding: 4px; + min-width: 32px; + min-height: 32px; cursor: pointer; color: inherit; opacity: 0.7; - transition: opacity var(--transition-fast, 120ms); + transition: opacity var(--transition-fast, 160ms); flex-shrink: 0; border-radius: 4px; } @@ -102,17 +103,26 @@ color: white; } -/* 响应式设计 */ +/* 响应式 + 安全区 */ @media (max-width: 768px) { .notification-container { - top: 10px; - right: 10px; - left: 10px; + top: max(10px, calc(env(safe-area-inset-top, 0px) + 8px)); + right: max(10px, calc(env(safe-area-inset-right, 0px) + 8px)); + left: max(10px, calc(env(safe-area-inset-left, 0px) + 8px)); max-width: none; } .notification { padding: 14px 16px; + transform: translateY(calc(-100% - 16px)); + } + + .notification.show { + transform: translateY(0); + } + + .notification.hide { + transform: translateY(calc(-100% - 16px)); } .notification-message { @@ -136,11 +146,11 @@ } } -/* 网络状态指示器样式 (Ported from index.html) */ +/* 网络状态指示器 */ .network-status { position: fixed; - top: 20px; - right: 20px; + top: max(16px, calc(env(safe-area-inset-top, 0px) + 12px)); + right: max(16px, calc(env(safe-area-inset-right, 0px) + 12px)); padding: 12px 20px; border-radius: 8px; color: white; @@ -148,7 +158,7 @@ z-index: 10000; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); transform: translateX(100%); - transition: transform 0.3s ease; + transition: transform var(--transition-base, 240ms ease); } .network-status.success { @@ -169,21 +179,15 @@ .update-notification { position: fixed; - bottom: 20px; - right: 20px; + bottom: max(16px, calc(env(safe-area-inset-bottom, 0px) + 12px)); + right: max(16px, calc(env(safe-area-inset-right, 0px) + 12px)); background: linear-gradient(135deg, #6366f1, #8b5cf6); color: white; padding: 16px 20px; border-radius: 12px; box-shadow: 0 8px 25px rgba(99, 102, 241, 0.3); z-index: 10000; - max-width: 300px; -} - -.notification-content { - display: flex; - align-items: center; - justify-content: space-between; + max-width: min(300px, calc(100vw - 32px - env(safe-area-inset-left, 0px) - env(safe-area-inset-right, 0px))); } /* 加载状态样式 */ @@ -201,7 +205,7 @@ align-items: center; opacity: 0; pointer-events: none; - transition: opacity 0.3s ease; + transition: opacity var(--transition-base, 240ms ease); } .loading-overlay.show { @@ -237,3 +241,25 @@ 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } + +/* 暗色模式 */ +@media (prefers-color-scheme: dark) { + .loading-overlay { + background: rgba(0, 0, 0, 0.65); + } + + .loading-content { + background: #1e1e1e; + color: #e9ecef; + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.45); + } + + .loading-content p { + color: #adb5bd; + } + + .loading-spinner { + border-color: #333; + border-top-color: var(--primary, #8b5cf6); + } +}