mirror of
https://github.com/Silentely/eSIM-Tools.git
synced 2026-09-03 06:24:20 +08:00
- 新增 giffgaff-token-exchange.js 函数,用于交换 authorization code 和 access token - 更新 verify-cookie.js 函数,增加对 cookie 中 access token 的提取和验证 - 修改 giffgaff-graphql.js、giffgaff-mfa-challenge.js 和 giffgaff-mfa-validation.js 函数,支持使用 access token 或 cookie 进行身份验证 - 更新 README.md 和 COOKIE_LOGIN_SETUP.md 文档,说明新的身份验证流程和安全注意事项
390 lines
17 KiB
HTML
390 lines
17 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; font-src 'self' data:; connect-src 'self' https://api.qrserver.com https://appapi.simyo.nl https://api.giffgaff.com https://id.giffgaff.com https://publicapi.giffgaff.com; img-src 'self' data: https:; frame-src 'none';">
|
||
<title>eSIM工具 - Giffgaff & Simyo</title>
|
||
|
||
<!-- 性能优化:预连接与预加载关键资源 -->
|
||
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
|
||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
|
||
<link rel="preconnect" href="https://www.googletagmanager.com" crossorigin>
|
||
<link rel="preconnect" href="https://api.qrserver.com" crossorigin>
|
||
<link rel="preconnect" href="https://api.giffgaff.com" crossorigin>
|
||
<link rel="preconnect" href="https://appapi.simyo.nl" crossorigin>
|
||
|
||
<!-- 关键CSS优先:preload 后备 rel=stylesheet -->
|
||
<link rel="preload" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" as="style" onload="this.rel='stylesheet'">
|
||
<noscript><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"></noscript>
|
||
<!-- Font Awesome:直接引入,避免 onload 触发器被 CSP 阻断导致样式未生效 -->
|
||
<link rel="stylesheet" href="/src/assets/fontawesome/all.min.css">
|
||
<!-- Font Awesome 资源路径(woff2)在 /src/assets/fontawesome/,确保已发布且 CSP 允许 font-src 'self' -->
|
||
|
||
<!-- 本地样式文件 -->
|
||
<link rel="preload" href="/src/styles/design-system.css" as="style" onload="this.rel='stylesheet'">
|
||
<link rel="preload" href="/src/styles/animations.css" as="style" onload="this.rel='stylesheet'">
|
||
<link rel="stylesheet" href="/src/styles/fa-local.css">
|
||
|
||
<!-- 提供 SW 路径与版本给 performance.js 使用(避免旧 SW 404) -->
|
||
<meta name="sw-path" content="/sw.js">
|
||
<meta name="build-rev" content="v1">
|
||
|
||
<style>
|
||
/* Design System: 语义化令牌与统一规范 */
|
||
:root{
|
||
/* Brand */
|
||
--brand-start:#6366f1; /* indigo-500 */
|
||
--brand-end:#8b5cf6; /* violet-500 */
|
||
--accent-ok:#10b981; /* emerald-500 */
|
||
--accent-warn:#f59e0b; /* amber-500 */
|
||
--accent-danger:#ef4444; /* red-500 */
|
||
|
||
/* Surface & Text */
|
||
--bg-grad-start:var(--brand-start);
|
||
--bg-grad-end:var(--brand-end);
|
||
--surface:#ffffff;
|
||
--surface-alpha:92%;
|
||
--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);
|
||
--ring:0 0 0 3px rgba(99,102,241,.35);
|
||
|
||
--s1:8px; --s2:12px; --s3:16px; --s4:20px; --s5:24px; --s6:32px;
|
||
|
||
/* Motion */
|
||
--ease-standard:cubic-bezier(.2,.8,.2,1);
|
||
--duration-fast:.18s;
|
||
--duration-normal:.28s;
|
||
}
|
||
|
||
*{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%);
|
||
min-height: 100vh;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: var(--s5);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.main-container{
|
||
background: color-mix(in oklab, white var(--surface-alpha), transparent);
|
||
-webkit-backdrop-filter: blur(8px);
|
||
backdrop-filter: blur(8px);
|
||
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);
|
||
}
|
||
|
||
.header{ margin-bottom: var(--s6); }
|
||
.header h1{
|
||
font-size: clamp(1.9rem, 3vw, 2.6rem);
|
||
font-weight: 800;
|
||
color: var(--text-primary);
|
||
margin-bottom: var(--s2);
|
||
letter-spacing: .2px;
|
||
display:flex;align-items:center;justify-content:center;gap:10px;
|
||
}
|
||
.header .subtitle{
|
||
font-size: clamp(.95rem, 1.9vw, 1.12rem);
|
||
color: var(--text-muted);
|
||
margin: 0;
|
||
}
|
||
|
||
.github-btn{
|
||
background: #24292e;
|
||
border: 1px solid rgba(255,255,255,.18);
|
||
font-size: 16px;
|
||
color:#fff;
|
||
cursor:pointer;
|
||
padding: 10px 18px;
|
||
display:inline-flex;
|
||
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);
|
||
gap: 8px;
|
||
box-shadow: var(--shadow-sm);
|
||
text-decoration: none;
|
||
margin-top: var(--s3);
|
||
outline: none;
|
||
will-change: transform;
|
||
}
|
||
.github-btn:hover{ transform: translateY(-1px); box-shadow: var(--shadow-md); text-decoration: none; color:#fff; background:#111; }
|
||
.github-btn:focus-visible{ outline: none; box-shadow: var(--ring); }
|
||
|
||
.tools-grid{
|
||
display:grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: clamp(16px, 3vw, 28px);
|
||
margin-top: var(--s6);
|
||
}
|
||
@media (max-width: 720px){ .tools-grid{ grid-template-columns: 1fr; } }
|
||
|
||
.tool-card{
|
||
background: var(--surface);
|
||
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;
|
||
position: relative;
|
||
overflow: hidden;
|
||
will-change: transform, box-shadow;
|
||
}
|
||
.tool-card::before{
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0 0 auto 0;
|
||
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);
|
||
}
|
||
.tool-card:focus-within{ box-shadow: var(--ring); }
|
||
|
||
/* 品牌色注入 */
|
||
.giffgaff-card{ --primary:#ff6b00; --secondary:#ff8c42; }
|
||
.simyo-card{ --primary:#e74c3c; --secondary:#c0392b; }
|
||
|
||
.tool-icon{
|
||
font-size: clamp(2.2rem, 3.2vw, 2.8rem);
|
||
color: var(--primary);
|
||
margin-bottom: var(--s3);
|
||
}
|
||
.tool-title{
|
||
font-size: clamp(1.2rem, 2.2vw, 1.4rem);
|
||
font-weight: 800;
|
||
color: var(--text-primary);
|
||
margin-bottom: var(--s2);
|
||
}
|
||
.tool-description{
|
||
color: var(--text-muted);
|
||
margin-bottom: var(--s4);
|
||
line-height: 1.65;
|
||
}
|
||
|
||
.tool-features{
|
||
list-style: none;
|
||
padding: 0;
|
||
margin-bottom: var(--s4);
|
||
text-align: left;
|
||
}
|
||
.tool-features li{
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 10px;
|
||
padding: 6px 0;
|
||
color: var(--text-secondary);
|
||
}
|
||
.tool-features li i{ color: var(--primary); flex: 0 0 auto; }
|
||
.tool-features li span{ flex: 1 1 auto; }
|
||
|
||
.tool-btn{
|
||
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
||
color: #fff;
|
||
border: none;
|
||
padding: 12px 22px;
|
||
border-radius: 999px;
|
||
font-size: 1rem;
|
||
font-weight: 800;
|
||
text-decoration: none;
|
||
display: inline-flex;
|
||
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);
|
||
outline: none;
|
||
will-change: transform, filter;
|
||
}
|
||
.tool-btn:hover{ transform: translateY(-2px); text-decoration: none; color: #fff; filter: brightness(1.03); }
|
||
.tool-btn:focus-visible{ box-shadow: var(--ring); }
|
||
|
||
.footer{
|
||
margin-top: var(--s6);
|
||
padding-top: var(--s5);
|
||
border-top: 1px solid color-mix(in oklab, #ecf0f1 85%, transparent);
|
||
color: #6b7280;
|
||
font-size: .94rem;
|
||
line-height: 1.6;
|
||
text-align: center;
|
||
}
|
||
|
||
/* 入场动画:IntersectionObserver 触发,避免 setTimeout 抖动 */
|
||
.reveal{ opacity: 0; transform: translateY(12px); transition: opacity .5s var(--ease-standard), transform .5s var(--ease-standard); will-change: opacity, transform; }
|
||
.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; }
|
||
}
|
||
|
||
/* 小屏进一步优化 */
|
||
@media (max-width: 360px){
|
||
.github-btn{ padding: 9px 14px; font-size: 14px; }
|
||
.tool-btn{ padding: 10px 18px; font-size: .95rem; }
|
||
}
|
||
</style>
|
||
|
||
<!-- Google tag (gtag.js) 延迟加载 -->
|
||
<script defer src="https://www.googletagmanager.com/gtag/js?id=G-HGX38HMNL1"></script>
|
||
</head>
|
||
<body>
|
||
<div class="main-container" role="region" aria-label="eSIM 工具选择">
|
||
<div class="header">
|
||
<h1><i class="fas fa-sim-card" aria-hidden="true"></i> eSIM 工具集</h1>
|
||
<p class="subtitle">轻松管理您的 Giffgaff 与 Simyo eSIM 配置</p>
|
||
<a href="https://github.com/Silentely/esim-tools" target="_blank" rel="noopener noreferrer" class="github-btn" aria-label="在新窗口打开 GitHub 仓库">
|
||
<i class="fab fa-github" aria-hidden="true"></i> GitHub
|
||
</a>
|
||
</div>
|
||
|
||
<div class="tools-grid" aria-live="polite">
|
||
<!-- Giffgaff工具卡片 -->
|
||
<section class="tool-card giffgaff-card reveal" tabindex="-1" aria-labelledby="giffgaff-title" aria-describedby="giffgaff-desc">
|
||
<div class="tool-icon" aria-hidden="true">
|
||
<i class="fas fa-mobile-alt"></i>
|
||
</div>
|
||
<h2 id="giffgaff-title" class="tool-title">Giffgaff eSIM 工具</h2>
|
||
<p id="giffgaff-desc" class="tool-description">
|
||
完整的 Giffgaff eSIM 申请与管理,支持 OAuth 认证、MFA 验证与 GraphQL API 调用
|
||
</p>
|
||
<ul class="tool-features">
|
||
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>OAuth 2.0 PKCE 认证</span></li>
|
||
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>邮件验证码与 MFA 支持</span></li>
|
||
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>GraphQL API 集成</span></li>
|
||
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>自动生成 eSIM 二维码</span></li>
|
||
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>完善的错误处理</span></li>
|
||
</ul>
|
||
<a href="/giffgaff" class="tool-btn" aria-describedby="giffgaff-desc">
|
||
<i class="fas fa-arrow-right" aria-hidden="true"></i><span>使用 Giffgaff 工具</span>
|
||
</a>
|
||
</section>
|
||
|
||
<!-- Simyo工具卡片 -->
|
||
<section class="tool-card simyo-card reveal" tabindex="-1" aria-labelledby="simyo-title" aria-describedby="simyo-desc">
|
||
<div class="tool-icon" aria-hidden="true">
|
||
<i class="fas fa-sim-card"></i>
|
||
</div>
|
||
<h2 id="simyo-title" class="tool-title">Simyo 工具</h2>
|
||
<p id="simyo-desc" class="tool-description">
|
||
专为 Simyo NL 用户的 eSIM 管理,支持设备更换与短信验证码处理
|
||
</p>
|
||
<ul class="tool-features">
|
||
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>简单快捷的登录验证</span></li>
|
||
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>设备更换完整流程</span></li>
|
||
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>短信验证码支持</span></li>
|
||
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>一键生成二维码</span></li>
|
||
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>安装确认功能</span></li>
|
||
</ul>
|
||
<a href="/simyo" class="tool-btn" aria-describedby="simyo-desc">
|
||
<i class="fas fa-arrow-right" aria-hidden="true"></i><span>使用 Simyo 工具</span>
|
||
</a>
|
||
</section>
|
||
</div>
|
||
|
||
<footer class="footer" aria-label="免责声明与隐私说明">
|
||
<p>
|
||
<i class="fas fa-info-circle me-2" aria-hidden="true"></i>
|
||
本工具仅供个人使用,请遵守相关服务条款。<br>
|
||
<i class="fas fa-shield-alt me-2" aria-hidden="true"></i>
|
||
所有数据处理均在本地进行,保护您的隐私安全。
|
||
</p>
|
||
</footer>
|
||
</div>
|
||
|
||
<!-- 将 bootstrap 脚本 defer,减少阻塞 -->
|
||
<script defer src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
||
<!-- 首页逻辑脚本(无内联) -->
|
||
<script defer src="/src/js/home.js"></script>
|
||
|
||
<!-- 性能优化脚本 -->
|
||
<script src="/src/js/performance.js"></script>
|
||
|
||
<!-- 网络状态指示器样式 -->
|
||
<style>
|
||
.network-status {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
padding: 12px 20px;
|
||
border-radius: 8px;
|
||
color: white;
|
||
font-weight: 500;
|
||
z-index: 10000;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
transform: translateX(100%);
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
.network-status.success {
|
||
background: linear-gradient(135deg, #10b981, #059669);
|
||
}
|
||
|
||
.network-status.warning {
|
||
background: linear-gradient(135deg, #f59e0b, #d97706);
|
||
}
|
||
|
||
.network-status.show {
|
||
transform: translateX(0);
|
||
}
|
||
|
||
.update-notification {
|
||
position: fixed;
|
||
bottom: 20px;
|
||
right: 20px;
|
||
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;
|
||
}
|
||
|
||
.touch-active {
|
||
transform: scale(0.98) !important;
|
||
transition: transform 0.1s ease !important;
|
||
}
|
||
|
||
/* 加载状态样式 */
|
||
.loading-content {
|
||
background: white;
|
||
padding: 30px;
|
||
border-radius: 12px;
|
||
text-align: center;
|
||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
.loading-content p {
|
||
margin: 0;
|
||
color: #6b7280;
|
||
font-weight: 500;
|
||
}
|
||
</style>
|
||
</body>
|
||
</html> |