style: 优化前端 UI 流畅度、视觉层次与暗色模式

统一 motion token,拆分步骤 completed/active 与进度轨,弱化表单卡上浮
与无限呼吸动画;首页改中性 mesh 背景,补齐三页暗色与通知 safe-area。
This commit is contained in:
Abner
2026-07-11 12:46:13 +08:00
parent 653e06a29a
commit aaacbddfa4
16 changed files with 1109 additions and 335 deletions

View File

@@ -2,7 +2,8 @@
<html lang="zh-CN" data-page="home">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="color-scheme" content="light dark">
<meta name="description" content="eSIM 设备管理工具集,支持已有 Giffgaff 与 Simyo 号码的 eSIM 转换、设备更换、二维码生成及验证码处理。">
<!-- Sentry 错误监控配置(必须在其他脚本之前) -->
@@ -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);
}
}
</style>
<!-- Google tag (gtag.js) 延迟加载 -->
<script type="module" src="/src/js/captcha-client.js"></script>
<script defer src="https://www.googletagmanager.com/gtag/js?id=G-HGX38HMNL1" crossorigin="anonymous"></script></script>
<script defer src="https://www.googletagmanager.com/gtag/js?id=G-HGX38HMNL1" crossorigin="anonymous"></script>
</head>
<body aria-label="eSIM 工具选择">
<div class="main-container" role="region" aria-label="eSIM 工具选择">

View File

@@ -2,7 +2,7 @@
<html lang="zh-CN" data-page="giffgaff">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta name="color-scheme" content="light dark" />
<meta name="description" content="Giffgaff eSIM 工具,支持 OAuth 登录、MFA 验证、二维码生成与设备更换。">

View File

@@ -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');
}
}
/**

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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');
}
}
/**

View File

@@ -2,7 +2,7 @@
<html lang="zh-CN" data-page="simyo">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta name="color-scheme" content="light dark" />
<meta name="description" content="Simyo eSIM 工具,支持设备更换、短信验证码、二维码生成与安装确认,帮助您快速管理荷兰 Simyo eSIM。">

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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 <a> */

View File

@@ -363,10 +363,9 @@ a:not(.btn) {
性能优化
======================================== */
/* GPU加速 */
/* GPU 加速:仅在动画中临时使用,避免常驻 will-change */
.gpu-accelerated {
transform: translateZ(0);
will-change: transform;
}
/* 减少重绘 */

View File

@@ -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);
}
}