Files
eSIM-Tools/index.html
Abner 093310d25e ♻️ refactor: 优化通知与性能监控模块,加固通知消息 XSS 防护
- 通知服务新增页面可见性监听:标签页隐藏时暂停轮询,回到前台后恢复定时,离开期间超过检查周期则立即补查,节省 Netlify Function 配额
- 性能监控改为批量缓冲写入 sessionStorage,达到 1 秒间隔或 20 条上限自动落盘,并在页面隐藏/卸载时冲刷剩余缓冲,避免逐条读写开销
- 通知管理器改用原生 DOM API 构建节点并以 textContent 渲染消息,移除 innerHTML 与 escapeHtml 方法,从结构上杜绝 XSS 注入
- 新增 notification-service、notification-manager、performance-monitor 三组单元测试,覆盖后台暂停恢复、缓冲落盘、XSS 防护与空消息容错等场景
- 合并共享 Toast 样式至 design-system.css 并支持主题色定制,入场动画改为渐进增强,清理废弃请求日志中间件、SW 预缓存路径等冗余代码
2026-08-09 18:33:34 +08:00

985 lines
44 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN" data-page="home">
<head>
<meta charset="UTF-8">
<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 错误监控配置(必须在其他脚本之前) -->
<!-- 使用外链脚本以符合 CSP 策略 -->
<script src="/src/js/sentry-config.js"></script>
<script src="/src/js/sentry-loader.js"></script>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-eval' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://browser.sentry-cdn.com https://sentry.io https://*.sentry.io https://www.googletagmanager.com https://challenges.cloudflare.com https://www.google.com https://www.gstatic.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://browser.sentry-cdn.com https://www.google-analytics.com https://analytics.google.com https://stats.g.doubleclick.net https://www.googletagmanager.com https://appapi.simyo.nl https://api.giffgaff.com https://id.giffgaff.com https://publicapi.giffgaff.com https://challenges.cloudflare.com https://www.google.com https://www.gstatic.com https://sentry.io https://*.sentry.io; img-src 'self' data: https:; frame-src 'self' https://challenges.cloudflare.com https://www.google.com https://*.sentry.io; worker-src 'self' blob:; child-src 'self' blob:;">
<title>eSIM Tools - Giffgaff &amp; Simyo eSIM 在线管理工具 | 设备更换、激活与二维码生成</title>
<link rel="canonical" href="https://esim.cosr.eu.org/">
<meta name="robots" content="index, follow">
<link rel="manifest" href="/manifest.webmanifest">
<meta name="theme-color" content="#6366f1">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://esim.cosr.eu.org/">
<meta property="og:title" content="eSIM Tools - Giffgaff & Simyo eSIM 在线管理工具">
<meta property="og:description" content="免费在线 eSIM 管理工具,支持已有 Giffgaff 与 Simyo 号码的 eSIM 转换、OAuth 认证、设备更换、QR 码生成及激活,浏览器端操作无需安装 App。">
<meta property="og:image" content="https://esim.cosr.eu.org/og-image.png">
<meta property="og:locale" content="zh_CN">
<meta property="og:site_name" content="eSIM Tools">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="eSIM Tools - Giffgaff & Simyo eSIM 在线管理工具">
<meta name="twitter:description" content="免费在线 eSIM 管理工具,支持已有 Giffgaff 与 Simyo 号码的 eSIM 转换、设备更换、QR 码生成及激活。">
<meta name="twitter:image" content="https://esim.cosr.eu.org/og-image.png">
<!-- JSON-LD 结构化数据 -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "eSIM Tools",
"url": "https://esim.cosr.eu.org",
"description": "免费在线 eSIM 管理工具,支持已有 Giffgaff 与 Simyo 号码的 eSIM 转换、OAuth 认证、设备更换、QR 码生成及激活。",
"applicationCategory": "UtilitiesApplication",
"operatingSystem": "Web Browser",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "GBP"
},
"featureList": [
"Giffgaff eSIM OAuth 认证与激活",
"Simyo eSIM 设备更换与管理",
"自动生成 eSIM QR 码",
"支持 MFA 多因素认证",
"浏览器端本地处理,保护隐私"
],
"inLanguage": ["zh", "en"],
"softwareHelp": {
"@type": "CreativeWork",
"url": "https://github.com/Silentely/eSIM-Tools"
}
}
</script>
<!-- 性能优化:预连接与预加载关键资源 -->
<link rel="preconnect" href="https://browser.sentry-cdn.com" crossorigin>
<link rel="preconnect" href="https://www.googletagmanager.com" crossorigin>
<link rel="preconnect" href="https://api.giffgaff.com" crossorigin>
<link rel="preconnect" href="https://appapi.simyo.nl" crossorigin>
<!-- Favicon -->
<link rel="icon" href="/src/assets/favicon.ico" type="image/x-icon">
<!-- 关键CSS优先直接加载避免 onload 违反 CSP -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
<!-- 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="stylesheet" href="/src/styles/design-system.css">
<link rel="stylesheet" href="/src/styles/animations.css">
<link rel="stylesheet" href="/src/styles/fa-local.css">
<link rel="stylesheet" href="/src/styles/notification.css">
<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中性 indigo/slate替代旧黄绿背景 */
--bg-grad-start:#eef2ff;
--bg-grad-end:#f8fafc;
--surface:#ffffff;
--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 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与 design-system 对齐 */
--ease-standard:cubic-bezier(.2,.8,.2,1);
--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:
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;
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(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 78%, rgba(99,102,241,.18));
}
.language-switcher{
display:flex;
justify-content:flex-end;
width:100%;
margin-bottom: var(--s3);
}
.lang-switch{
display:inline-flex;
align-items:center;
gap:6px;
padding:6px 16px;
border-radius:999px;
border:1px solid rgba(99,102,241,.25);
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), background-color var(--duration-fast) var(--ease-standard);
}
@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;
box-shadow: var(--ring);
}
.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;
}
.header-actions{
display: flex;
gap: var(--s2);
justify-content: center;
flex-wrap: wrap;
margin-top: var(--s3);
}
.github-btn, .kofi-btn{
font-size: 16px;
font-weight: 600;
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-color var(--duration-fast) var(--ease-standard);
gap: 8px;
box-shadow: var(--shadow-sm);
text-decoration: none !important;
outline: none;
border: 1px solid rgba(255,255,255,.18);
}
.github-btn{ background: #24292e; color:#fff !important; }
.kofi-btn{ background: #FF5E5B; color:#fff !important; }
@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); }
.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: 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-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;
}
.tool-card::before{
content: '';
position: absolute;
inset: 0 0 auto 0;
height: 4px;
background: linear-gradient(90deg, var(--primary), var(--secondary));
}
@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), var(--shadow-md); }
/* 品牌色注入 */
.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) 32%, transparent);
outline: none;
}
@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);
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 抖动
仅在 JS 正常运行html.js时隐藏避免脚本加载失败导致内容永久不可见 */
html.js .reveal{ opacity: 0; transform: translateY(12px); transition: opacity var(--duration-normal) var(--ease-standard), transform var(--duration-normal) var(--ease-standard); }
html.js .reveal.show{ opacity: 1; transform: translateY(0); }
/* 降低动效:尊重用户的减少动态偏好 */
@media (prefers-reduced-motion: reduce){
html.js .reveal{ transition: none; transform: none; opacity: 1; }
.tool-card, .github-btn, .kofi-btn, .tool-btn, .lang-switch{ transition: none!important; }
}
/* 小屏进一步优化 */
@media (max-width: 360px){
.github-btn{ padding: 9px 14px; font-size: 14px; }
.tool-btn{ padding: 10px 18px; font-size: .95rem; }
}
/* GEO 优化内容区块 */
.info-section{
margin-top: var(--s6);
text-align: left;
}
.section-title{
font-size: clamp(1.3rem, 2.5vw, 1.6rem);
font-weight: 700;
color: var(--text-primary);
margin-bottom: var(--s3);
display: flex;
align-items: center;
gap: var(--s2);
}
.section-title i{ color: var(--primary, var(--brand-start)); }
.section-desc{
color: var(--text-muted);
margin-bottom: var(--s4);
}
/* 设备兼容性 */
.compat-grid{
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--s3);
margin-bottom: var(--s3);
}
@media (max-width: 600px){ .compat-grid{ grid-template-columns: 1fr; } }
.compat-card{
background: color-mix(in oklab, var(--surface) 95%, transparent);
border-radius: var(--radius-lg);
padding: var(--s4);
border: 1px solid color-mix(in oklab, var(--brand-start) 20%, transparent);
}
.compat-card h3{
font-size: 1rem;
font-weight: 700;
margin-bottom: var(--s2);
color: var(--text-primary);
}
.compat-card p{
font-size: .9rem;
color: var(--text-secondary);
line-height: 1.6;
}
.compat-check{
background: color-mix(in oklab, var(--accent-ok) 10%, transparent);
padding: var(--s3);
border-radius: var(--radius-lg);
font-size: .9rem;
}
.compat-check code{
background: rgba(0,0,0,.06);
padding: 2px 6px;
border-radius: 4px;
font-weight: 600;
}
/* 使用教程 */
.tutorial-grid{
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--s4);
}
@media (max-width: 700px){ .tutorial-grid{ grid-template-columns: 1fr; } }
.tutorial-card{
background: var(--surface);
border-radius: var(--radius-lg);
padding: var(--s4);
border-left: 4px solid;
}
.giffgaff-tutorial{ border-left-color: #ff6b00; }
.simyo-tutorial{ border-left-color: #e74c3c; }
.tutorial-card h3{
font-size: 1.05rem;
font-weight: 700;
margin-bottom: var(--s3);
color: var(--text-primary);
}
.tutorial-steps{
padding-left: var(--s4);
margin-bottom: var(--s3);
}
.tutorial-steps li{
font-size: .9rem;
color: var(--text-secondary);
margin-bottom: var(--s1);
line-height: 1.5;
}
.tutorial-note{
font-size: .85rem;
color: var(--text-muted);
background: color-mix(in oklab, var(--accent-warn) 10%, transparent);
padding: var(--s2) var(--s3);
border-radius: var(--radius-lg);
}
/* 对比表格 */
.compare-table-wrapper{
overflow-x: auto;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
}
.compare-table{
width: 100%;
border-collapse: collapse;
background: var(--surface);
font-size: .9rem;
}
.compare-table th{
background: color-mix(in oklab, var(--brand-start) 10%, transparent);
padding: var(--s3);
font-weight: 700;
text-align: left;
color: var(--text-primary);
}
.compare-table td{
padding: var(--s3);
border-bottom: 1px solid color-mix(in oklab, #ecf0f1 80%, transparent);
color: var(--text-secondary);
}
.compare-table tr:last-child td{ border-bottom: none; }
.text-success{ color: var(--accent-ok); }
.text-warning{ color: var(--accent-warn); }
/* 地区限制 */
.region-grid{
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--s3);
}
@media (max-width: 600px){ .region-grid{ grid-template-columns: 1fr; } }
.region-card{
background: var(--surface);
border-radius: var(--radius-lg);
padding: var(--s4);
border: 1px solid color-mix(in oklab, var(--brand-start) 15%, transparent);
}
.region-card h3{
font-size: 1rem;
font-weight: 700;
margin-bottom: var(--s2);
color: var(--text-primary);
}
.region-card p{
font-size: .9rem;
color: var(--text-secondary);
margin-bottom: var(--s1);
}
.region-card strong{ color: var(--text-primary); }
/* 术语表 */
.glossary-grid{
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--s3);
}
@media (max-width: 800px){ .glossary-grid{ grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 500px){ .glossary-grid{ grid-template-columns: 1fr; } }
.glossary-item{
background: var(--surface);
border-radius: var(--radius-lg);
padding: var(--s3) var(--s4);
}
.glossary-term{
font-weight: 700;
font-size: .9rem;
color: var(--brand-start);
margin-bottom: var(--s1);
}
.glossary-desc{
font-size: .85rem;
color: var(--text-muted);
line-height: 1.5;
margin: 0;
}
/* FAQ */
.faq-list{
display: flex;
flex-direction: column;
gap: var(--s2);
}
.faq-item{
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);
font-weight: 600;
font-size: .95rem;
color: var(--text-primary);
cursor: pointer;
list-style: none;
transition: background-color var(--duration-fast) var(--ease-standard);
}
.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); }
}
/* 安全声明 */
.disclaimer-content{
display: flex;
flex-direction: column;
gap: var(--s3);
}
.disclaimer-item{
display: flex;
align-items: flex-start;
gap: var(--s3);
padding: var(--s4);
border-radius: var(--radius-lg);
background: var(--surface);
}
.disclaimer-item i{
font-size: 1.5rem;
flex-shrink: 0;
margin-top: 2px;
}
.disclaimer-item.warning{ border-left: 4px solid var(--accent-warn); }
.disclaimer-item.warning i{ color: var(--accent-warn); }
.disclaimer-item.info{ border-left: 4px solid var(--brand-start); }
.disclaimer-item.info i{ color: var(--brand-start); }
.disclaimer-item.success{ border-left: 4px solid var(--accent-ok); }
.disclaimer-item.success i{ color: var(--accent-ok); }
.disclaimer-item strong{
display: block;
font-size: .95rem;
margin-bottom: var(--s1);
color: var(--text-primary);
}
.disclaimer-item p{
font-size: .9rem;
color: var(--text-secondary);
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>
</head>
<body aria-label="eSIM 工具选择">
<div class="main-container" role="region" aria-label="eSIM 工具选择">
<div class="language-switcher">
<button id="languageSwitcher" class="lang-switch" type="button" aria-label="切换语言到英文">🌐 English</button>
</div>
<div class="header">
<h1><i class="fas fa-sim-card" aria-hidden="true"></i> <span class="header-title-text">eSIM 工具集</span></h1>
<p class="subtitle">轻松管理您的 Giffgaff 与 Simyo eSIM 配置</p>
<div class="header-actions">
<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>
<a href="https://ko-fi.com/debug402" target="_blank" rel="noopener noreferrer" class="kofi-btn" aria-label="Buy me a coffee on Ko-fi">
<i class="fas fa-coffee" aria-hidden="true"></i> <span>Buy Me a Coffee</span>
</a>
</div>
</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"><span class="tool-title-text">Giffgaff eSIM 工具</span></h2>
<p id="giffgaff-desc" class="tool-description">
<span class="tool-description-text">完整的 Giffgaff eSIM 转换与管理,支持 OAuth 认证、MFA 验证与 GraphQL API 调用</span>
</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"><span class="tool-title-text">Simyo eSIM 工具</span></h2>
<p id="simyo-desc" class="tool-description">
<span class="tool-description-text">专为 Simyo NL 用户的 eSIM 管理,支持设备更换与短信验证码处理</span>
</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>
<!-- 设备兼容性 -->
<section class="info-section reveal" aria-labelledby="compat-title">
<h2 id="compat-title" class="section-title"><i class="fas fa-mobile-alt" aria-hidden="true"></i> <span class="compat-title-text">设备兼容性</span></h2>
<p class="section-desc">使用前请确认您的设备支持 eSIM 功能</p>
<div class="compat-grid">
<div class="compat-card">
<h3><i class="fab fa-apple" aria-hidden="true"></i> Apple</h3>
<p>iPhone XS / XR 及更新机型<br>iPhone SE 2 及更新机型</p>
</div>
<div class="compat-card">
<h3><i class="fab fa-android" aria-hidden="true"></i> Android</h3>
<p>Samsung Galaxy S20 及更新<br>Google Pixel 3 及更新<br>部分 OnePlus、华为机型</p>
</div>
</div>
<p class="compat-check"><strong>检查方法:</strong>在手机拨号盘输入 <code>*#06#</code>,如显示 EID 号码则支持 eSIM</p>
</section>
<!-- 使用教程 -->
<section class="info-section reveal" aria-labelledby="tutorial-title">
<h2 id="tutorial-title" class="section-title"><i class="fas fa-book-open" aria-hidden="true"></i> <span class="tutorial-title-text">使用教程</span></h2>
<div class="tutorial-grid">
<div class="tutorial-card giffgaff-tutorial">
<h3>Giffgaff eSIM 激活流程</h3>
<ol class="tutorial-steps">
<li>点击「使用 Giffgaff 工具」进入工具页面</li>
<li>使用 Giffgaff 账号完成 OAuth 登录授权</li>
<li>输入邮箱验证码完成身份验证</li>
<li>完成 MFA 多因素认证(如已启用)</li>
<li>查询会员资格并选择 eSIM 操作</li>
<li>获取 eSIM 下载码或扫描 QR 码激活</li>
</ol>
<p class="tutorial-note"><i class="fas fa-clock" aria-hidden="true"></i> 激活时间窗口UK 时间 4:30am - 9:30pm</p>
</div>
<div class="tutorial-card simyo-tutorial">
<h3>Simyo eSIM 管理流程</h3>
<ol class="tutorial-steps">
<li>点击「使用 Simyo 工具」进入工具页面</li>
<li>使用 Simyo 账号完成登录验证</li>
<li>选择设备更换或 eSIM 激活</li>
<li>通过 SMS 短信或 iDEAL 支付验证身份</li>
<li>确认新设备并生成 eSIM 配置</li>
</ol>
<p class="tutorial-note"><i class="fas fa-info-circle" aria-hidden="true"></i> 本工具支持生成 eSIM QR 码,简化激活流程</p>
</div>
</div>
</section>
<!-- 与官方流程对比 -->
<section class="info-section reveal" aria-labelledby="compare-title">
<h2 id="compare-title" class="section-title"><i class="fas fa-balance-scale" aria-hidden="true"></i> <span class="compare-title-text">与官方 App 对比</span></h2>
<div class="compare-table-wrapper">
<table class="compare-table">
<thead>
<tr>
<th>对比项</th>
<th>本工具</th>
<th>官方 App</th>
</tr>
</thead>
<tbody>
<tr>
<td>操作平台</td>
<td><i class="fas fa-check text-success"></i> 浏览器 Web 端</td>
<td>仅移动端 App</td>
</tr>
<tr>
<td>QR 码生成</td>
<td><i class="fas fa-check text-success"></i> 自动生成</td>
<td>Simyo 不支持(本工具可生成)</td>
</tr>
<tr>
<td>认证方式</td>
<td>OAuth 2.0 PKCE</td>
<td>App 内置登录</td>
</tr>
<tr>
<td>官方支持</td>
<td><i class="fas fa-exclamation-triangle text-warning"></i> 非官方</td>
<td><i class="fas fa-check text-success"></i> 官方支持</td>
</tr>
<tr>
<td>服务条款</td>
<td><i class="fas fa-exclamation-triangle text-warning"></i> 可能违反</td>
<td><i class="fas fa-check text-success"></i> 符合条款</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- 地区与时间限制 -->
<section class="info-section reveal" aria-labelledby="region-title">
<h2 id="region-title" class="section-title"><i class="fas fa-globe-europe" aria-hidden="true"></i> <span class="region-title-text">地区与时间限制</span></h2>
<div class="region-grid">
<div class="region-card">
<h3><i class="fas fa-clock" aria-hidden="true"></i> Giffgaff</h3>
<p>激活时间窗口:<strong>UK 时间 4:30am - 9:30pm</strong></p>
<p>请在非工作时间内尝试激活可能失败</p>
</div>
<div class="region-card">
<h3><i class="fas fa-map-marker-alt" aria-hidden="true"></i> Simyo</h3>
<p>服务地区:<strong>荷兰 (NL)</strong></p>
<p>仅支持荷兰本地 SIM 卡用户</p>
</div>
</div>
</section>
<!-- 技术术语解释 -->
<section class="info-section reveal" aria-labelledby="glossary-title">
<h2 id="glossary-title" class="section-title"><i class="fas fa-book" aria-hidden="true"></i> <span class="glossary-title-text">技术术语说明</span></h2>
<div class="glossary-grid">
<div class="glossary-item">
<p class="glossary-term">OAuth 2.0 PKCE</p>
<p class="glossary-desc">一种安全的授权协议,允许第三方应用在无需存储密钥的情况下访问用户数据</p>
</div>
<div class="glossary-item">
<p class="glossary-term">GraphQL</p>
<p class="glossary-desc">一种 API 查询语言,比 REST 更灵活高效地获取所需数据</p>
</div>
<div class="glossary-item">
<p class="glossary-term">LPA String</p>
<p class="glossary-desc">本地配置文件激活字符串,包含 eSIM 下载所需的服务器地址和激活码</p>
</div>
<div class="glossary-item">
<p class="glossary-term">SM-DP+</p>
<p class="glossary-desc">订阅管理数据准备服务器,负责安全地存储和分发 eSIM 配置文件</p>
</div>
<div class="glossary-item">
<p class="glossary-term">MFA</p>
<p class="glossary-desc">多因素认证,通过多种方式验证用户身份,提高账户安全性</p>
</div>
<div class="glossary-item">
<p class="glossary-term">EID</p>
<p class="glossary-desc">eSIM 的唯一标识号码,可在拨号盘输入 *#06# 查看</p>
</div>
</div>
</section>
<!-- FAQ 常见问题 -->
<section class="info-section reveal" aria-labelledby="faq-title">
<h2 id="faq-title" class="section-title"><i class="fas fa-question-circle" aria-hidden="true"></i> <span class="faq-title-text">常见问题</span></h2>
<div class="faq-list">
<details class="faq-item">
<summary>无法接收 SMS 短信验证码怎么办?</summary>
<p>对于 Simyo 用户,可以选择 iDEAL 支付方式作为替代验证方案。如果仍然无法接收短信,请检查手机号码是否正确,或联系运营商确认短信服务状态。</p>
</details>
<details class="faq-item">
<summary>eSIM 激活需要多长时间?</summary>
<p>通常在几分钟内完成,最长可能需要 1 小时。如果超过 1 小时仍未激活成功,请检查网络连接或重新尝试。</p>
</details>
<details class="faq-item">
<summary>是否支持在海外激活 eSIM</summary>
<p>Giffgaff 的 eSIM 激活有 UK 时间窗口限制4:30am - 9:30pm UK 时间。Simyo 主要面向荷兰本地用户,海外激活可能受限。</p>
</details>
<details class="faq-item">
<summary>Simyo 支持 QR 码激活吗?</summary>
<p>Simyo 官方 App 不支持 QR 码激活,但本工具可以生成 eSIM QR 码,让您通过扫描方式快速完成激活,无需在 App 内操作。</p>
</details>
<details class="faq-item">
<summary>使用这个工具安全吗?</summary>
<p>本工具在本地浏览器运行,所有数据处理均在本地进行。但请注意:使用第三方工具可能违反运营商服务条款,建议使用非主力号码进行测试。</p>
</details>
<details class="faq-item">
<summary>激活失败怎么办?</summary>
<p>请检查1) 设备是否支持 eSIM2) 网络连接是否正常3) 是否在运营商服务时间内4) 账号状态是否正常。如问题持续,请联系运营商客服。</p>
</details>
</div>
</section>
<!-- 安全声明 -->
<section class="info-section reveal" aria-labelledby="disclaimer-title">
<h2 id="disclaimer-title" class="section-title"><i class="fas fa-shield-alt" aria-hidden="true"></i> <span class="disclaimer-title-text">安全声明</span></h2>
<div class="disclaimer-content">
<div class="disclaimer-item warning">
<i class="fas fa-exclamation-triangle" aria-hidden="true"></i>
<div>
<strong>第三方工具风险</strong>
<p>本工具为非官方第三方工具,使用可能违反运营商服务条款。请自行评估风险。</p>
</div>
</div>
<div class="disclaimer-item info">
<i class="fas fa-info-circle" aria-hidden="true"></i>
<div>
<strong>建议使用非主力号码</strong>
<p>为避免影响正常通信,建议使用备用号码进行测试和激活操作。</p>
</div>
</div>
<div class="disclaimer-item success">
<i class="fas fa-lock" aria-hidden="true"></i>
<div>
<strong>数据安全</strong>
<p>所有数据处理均在本地浏览器完成,不上传任何敏感信息到服务器。</p>
</div>
</div>
</div>
</section>
<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 type="module" src="/src/js/bootstrap-footer.js"></script>
<!-- 国际化初始化 -->
<script type="module" src="/src/js/init-home-i18n.js"></script>
<!-- 通知系统初始化 -->
<script type="module" src="/src/js/init-notification.js"></script>
<!-- 性能优化脚本 -->
<script defer src="/src/js/performance.js"></script>
<!-- WebMCP: 向 AI Agent 暴露站点工具 -->
<script src="/src/js/webmcp.js" defer></script>
</body>
</html>