' % (_icon(icon, 22, 1.5), title, who, note, action, req)
)
return "".join(cards)
def _feature_cards() -> str:
out = []
- for icon, title, desc in FEATURES:
+ for index, (icon, title, desc) in enumerate(FEATURES):
+ # reveal 带一点错位延迟,滚动进来时是"依次浮现"而不是整块跳
+ delay = " style=\"--d:%dms\"" % (index % 3 * 70)
out.append(
- '
%s
'
+ '
'
- % (_icon(icon), title, desc)
+ % (delay, _icon(icon), title, desc)
)
return "".join(out)
@@ -205,6 +257,33 @@ def _step_cards() -> str:
return "".join(out)
+def _stat_cards() -> str:
+ out = []
+ for value, label, note in STATS:
+ out.append(
+ '
' % (value, label, note)
+ )
+ return "".join(out)
+
+
+def _testimonials() -> str:
+ out = []
+ stars = ('
'
+ + "\u2605" * 5 + "")
+ for who, initial, text in TESTIMONIALS:
+ out.append(
+ '
'
+ '%s'
+ '%s
'
+ '%s%s'
+ '' % (stars, text, initial, who)
+ )
+ return "".join(out)
+
+
def _faq_items() -> str:
out = []
for question, answer in FAQ:
@@ -224,117 +303,231 @@ def _version(files: list) -> str:
return ""
+# ============================================================================
+# 样式
+# ============================================================================
SITE_CSS = """
*{box-sizing:border-box;margin:0;padding:0}
-html{scroll-behavior:smooth}
-body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",
- "Microsoft YaHei","PingFang SC",sans-serif;
- background:%(BG)s;color:%(TEXT)s;line-height:1.75;
- -webkit-font-smoothing:antialiased}
+html{scroll-behavior:smooth;-webkit-text-size-adjust:100%%}
+
+/* 苹果的字体栈:优先系统 UI 字体,中文落到雅黑/苹方。
+ 字距收紧是"苹果感"最关键的一条 —— 大标题用 -0.03em 左右。 */
+body{font-family:-apple-system,BlinkMacSystemFont,"SF Pro SC","SF Pro Text",
+ "Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
+ background:%(BG)s;color:%(TEXT)s;line-height:1.6;
+ -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
a{color:inherit;text-decoration:none}
-.wrap{max-width:960px;margin:0 auto;padding:0 24px}
+img,svg{display:block}
+.wrap{max-width:1040px;margin:0 auto;padding:0 22px}
+.narrow{max-width:760px;margin:0 auto}
-/* 吸顶导航 */
-nav{position:sticky;top:0;z-index:50;background:rgba(255,255,255,.86);
- backdrop-filter:saturate(180%%) blur(12px);
- border-bottom:1px solid %(BORDER)s}
+/* ---------------- 磨砂吸顶导航(苹果那个味道)----------------
+ .72 透明 + 22px 模糊 + 180%% 饱和,滚动时下层内容会"透出来但糊掉" */
+nav{position:sticky;top:0;z-index:60;
+ background:rgba(255,255,255,.72);
+ -webkit-backdrop-filter:saturate(180%%) blur(22px);
+ backdrop-filter:saturate(180%%) blur(22px);
+ border-bottom:1px solid rgba(0,0,0,.07)}
.nav-in{display:flex;align-items:center;justify-content:space-between;
- height:60px}
-.brand{display:flex;align-items:center;gap:9px;font-weight:700;
- font-size:17px;letter-spacing:-.2px}
-.brand img{width:26px;height:26px;border-radius:7px}
-.nav-links{display:flex;gap:26px;font-size:14px;color:%(MUTED)s}
+ height:48px}
+.brand{display:flex;align-items:center;gap:8px;font-weight:600;
+ font-size:15px;letter-spacing:-.01em}
+.nav-links{display:flex;gap:26px;font-size:12.5px;color:rgba(0,0,0,.62)}
+.nav-links a{transition:color .2s}
.nav-links a:hover{color:%(TEXT)s}
+@media(max-width:700px){.nav-links{display:none}}
-/* Hero */
-.hero{padding:88px 0 72px;text-align:center}
-.hero h1{font-size:46px;line-height:1.2;letter-spacing:-1.2px;
- font-weight:800;margin-bottom:18px}
-.hero p{font-size:18px;color:%(MUTED)s;max-width:600px;margin:0 auto 34px}
-.cta{display:flex;gap:12px;justify-content:center;flex-wrap:wrap}
-.btn{display:inline-flex;align-items:center;gap:8px;
- padding:12px 26px;border-radius:10px;font-weight:600;font-size:15px;
- transition:all .15s}
+/* ---------------- Hero ----------------
+ clamp 让字号在手机上也不至于爆掉。
+ 背后一层极淡的径向渐变当"光",不用图片。 */
+.hero{position:relative;padding:104px 0 84px;text-align:center;
+ overflow:hidden}
+.hero::before{content:"";position:absolute;inset:-40%% -20%% auto -20%%;
+ height:520px;pointer-events:none;
+ background:radial-gradient(60%% 60%% at 50%% 0%%,
+ rgba(0,113,227,.10),rgba(0,113,227,0) 70%%)}
+.hero>*{position:relative}
+.hero h1{font-size:clamp(34px,6.4vw,68px);line-height:1.06;
+ letter-spacing:-.032em;font-weight:700;margin-bottom:20px}
+.hero h1 .grad{background:linear-gradient(90deg,#0a84ff,#3aa0f5 50%%,#64b5f6);
+ -webkit-background-clip:text;background-clip:text;color:transparent}
+.hero .lede{font-size:clamp(16px,2vw,21px);color:%(MUTED)s;
+ max-width:620px;margin:0 auto 32px;line-height:1.55;
+ letter-spacing:-.01em}
+.cta{display:flex;gap:14px;justify-content:center;flex-wrap:wrap}
+.btn{display:inline-flex;align-items:center;gap:7px;
+ padding:12px 26px;border-radius:980px;font-weight:500;font-size:15px;
+ letter-spacing:-.01em;transition:all .22s cubic-bezier(.4,0,.2,1)}
.btn-p{background:%(ACCENT)s;color:#fff}
-.btn-p:hover{background:#1d4ed8;transform:translateY(-1px)}
-.btn-s{background:%(SURFACE)s;color:%(TEXT)s;
- border:1px solid %(BORDER)s}
-.btn-s:hover{border-color:#d1d5db;background:#f3f4f6}
-.badge{display:inline-block;margin-top:22px;padding:4px 14px;
- border-radius:999px;background:#eff6ff;color:%(ACCENT)s;
- font-size:13px;font-weight:600}
+.btn-p:hover{background:%(ACCENT_DARK)s;transform:translateY(-1px);
+ box-shadow:0 8px 22px rgba(0,113,227,.28)}
+.btn-s{background:rgba(0,0,0,.05);color:%(TEXT)s}
+.btn-s:hover{background:rgba(0,0,0,.09)}
-/* 通用区段 */
-section{padding:72px 0;border-top:1px solid %(BORDER)s}
-.sec-head{text-align:center;margin-bottom:48px}
-.sec-head h2{font-size:30px;letter-spacing:-.6px;font-weight:700;
- margin-bottom:10px}
-.sec-head p{color:%(MUTED)s;font-size:16px}
+/* 平台行(UU远程那种"支持这些端"的一排) */
+.plats{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;
+ margin-top:34px}
+.plat{display:inline-flex;align-items:center;gap:7px;
+ padding:7px 15px;border-radius:980px;background:%(SURFACE2)s;
+ color:%(MUTED)s;font-size:13px;font-weight:500}
+.plat svg{width:15px;height:15px}
+.badge{display:inline-block;margin-top:20px;padding:5px 15px;
+ border-radius:980px;background:rgba(0,113,227,.08);
+ color:%(ACCENT)s;font-size:12.5px;font-weight:600}
-/* 功能网格 */
-.feats{display:grid;gap:1px;background:%(BORDER)s;
- border:1px solid %(BORDER)s;border-radius:14px;overflow:hidden}
-@media(min-width:640px){.feats{grid-template-columns:repeat(3,1fr)}}
-.feat{background:%(BG)s;padding:28px 24px}
-.feat-icon{color:%(ACCENT)s;margin-bottom:14px}
-.feat-title{font-weight:650;font-size:16px;margin-bottom:6px}
-.feat-desc{color:%(MUTED)s;font-size:14px;line-height:1.7}
+/* 滚动提示 —— 做明显一点,不然等于没做 */
+.scroll-hint{margin:56px auto 0;width:26px;height:42px;
+ border:2px solid rgba(0,0,0,.22);border-radius:14px;
+ position:relative}
+.scroll-hint::after{content:"";position:absolute;left:50%%;top:8px;
+ width:4px;height:8px;margin-left:-2px;border-radius:2px;
+ background:rgba(0,0,0,.42);animation:sd 1.7s ease-in-out infinite}
+@keyframes sd{0%%{opacity:0;transform:translateY(0)}
+ 40%%{opacity:1}
+ 80%%{opacity:0;transform:translateY(13px)}
+ 100%%{opacity:0}}
-/* 三步 */
-.steps{display:grid;gap:20px}
-@media(min-width:640px){.steps{grid-template-columns:repeat(3,1fr)}}
-.step{padding:26px 22px;border:1px solid %(BORDER)s;border-radius:14px;
- background:%(SURFACE)s}
-.step-num{width:30px;height:30px;border-radius:50%%;background:%(ACCENT)s;
- color:#fff;display:flex;align-items:center;justify-content:center;
- font-weight:700;font-size:14px;margin-bottom:14px}
-.step-title{font-weight:650;font-size:16px;margin-bottom:6px}
-.step-desc{color:%(MUTED)s;font-size:14px;line-height:1.7}
+/* ---------------- 通用区段 ---------------- */
+section{padding:88px 0}
+.alt{background:%(SURFACE2)s}
+.sec-head{text-align:center;margin-bottom:52px}
+.sec-head h2{font-size:clamp(26px,3.6vw,40px);letter-spacing:-.025em;
+ font-weight:700;margin-bottom:12px;line-height:1.15}
+.sec-head p{color:%(MUTED)s;font-size:clamp(15px,1.7vw,18px);
+ letter-spacing:-.01em;max-width:560px;margin:0 auto}
-/* 下载 */
-.dls{display:grid;gap:16px}
+/* ---------------- 数据带 ---------------- */
+.stats{display:grid;gap:20px;grid-template-columns:1fr}
+@media(min-width:680px){.stats{grid-template-columns:repeat(3,1fr)}}
+.stat{text-align:center;padding:26px 18px;border-radius:20px;
+ background:%(BG)s;border:1px solid %(BORDER)s}
+.stat-v{font-size:clamp(30px,4.4vw,44px);font-weight:700;
+ letter-spacing:-.03em;color:%(ACCENT)s;line-height:1.1}
+.stat-l{font-weight:600;font-size:14.5px;margin-top:8px}
+.stat-n{color:%(FAINT)s;font-size:12.5px;margin-top:4px}
+
+/* ---------------- 功能网格 ----------------
+ 卡片默认无阴影(苹果不爱堆阴影),hover 才浮起来一点 */
+.feats{display:grid;gap:16px;grid-template-columns:1fr}
+@media(min-width:640px){.feats{grid-template-columns:repeat(2,1fr)}}
+@media(min-width:900px){.feats{grid-template-columns:repeat(3,1fr)}}
+.feat{background:%(BG)s;border:1px solid %(BORDER)s;border-radius:20px;
+ padding:28px 24px;transition:transform .28s cubic-bezier(.4,0,.2,1),
+ box-shadow .28s,border-color .28s}
+.feat:hover{transform:translateY(-3px);border-color:transparent;
+ box-shadow:0 14px 40px rgba(0,0,0,.09)}
+.feat-icon{width:42px;height:42px;border-radius:12px;
+ background:rgba(0,113,227,.08);color:%(ACCENT)s;
+ display:flex;align-items:center;justify-content:center;
+ margin-bottom:16px}
+.feat-title{font-weight:600;font-size:16.5px;margin-bottom:7px;
+ letter-spacing:-.015em}
+.feat-desc{color:%(MUTED)s;font-size:14px;line-height:1.65}
+
+/* ---------------- 三步 ----------------
+ 中间拉一条渐变线,像"流程" */
+.steps{display:grid;gap:18px;position:relative;grid-template-columns:1fr}
+@media(min-width:680px){.steps{grid-template-columns:repeat(3,1fr)}}
+.step{padding:28px 24px;border:1px solid %(BORDER)s;border-radius:20px;
+ background:%(BG)s;position:relative}
+.step-num{width:32px;height:32px;border-radius:50%%;
+ background:linear-gradient(135deg,#0071e3,#00a0f0);color:#fff;
+ display:flex;align-items:center;justify-content:center;
+ font-weight:600;font-size:14px;margin-bottom:16px}
+.step-title{font-weight:600;font-size:16.5px;margin-bottom:7px;
+ letter-spacing:-.015em}
+.step-desc{color:%(MUTED)s;font-size:14px;line-height:1.65}
+
+/* ---------------- 用户评价 ---------------- */
+.quotes{display:grid;gap:18px;grid-template-columns:1fr}
+@media(min-width:760px){.quotes{grid-template-columns:repeat(3,1fr)}}
+.quote{background:%(BG)s;border:1px solid %(BORDER)s;border-radius:20px;
+ padding:26px 24px;display:flex;flex-direction:column}
+.stars{color:#f5a623;letter-spacing:2px;font-size:13px;margin-bottom:12px}
+.quote blockquote{font-size:14.5px;line-height:1.7;color:%(TEXT)s;
+ flex:1;margin-bottom:18px}
+.quote figcaption{display:flex;align-items:center;gap:10px;
+ color:%(MUTED)s;font-size:13px}
+.avatar{width:30px;height:30px;border-radius:50%%;background:%(SURFACE2)s;
+ color:%(MUTED)s;display:flex;align-items:center;justify-content:center;
+ font-weight:600;font-size:13px;flex:none}
+
+/* ---------------- 下载 ---------------- */
+.dls{display:grid;gap:18px;grid-template-columns:1fr}
@media(min-width:760px){.dls{grid-template-columns:repeat(3,1fr)}}
-.dl-card{border:1px solid %(BORDER)s;border-radius:14px;padding:24px 20px;
+.dl-card{border:1px solid %(BORDER)s;border-radius:20px;padding:26px 22px;
display:flex;flex-direction:column;background:%(BG)s;
- transition:border-color .15s}
-.dl-card:hover{border-color:#d1d5db}
-.dl-head{display:flex;align-items:baseline;gap:8px;margin-bottom:4px}
-.dl-tag{font-weight:700;font-size:17px}
-.dl-who{color:%(FAINT)s;font-size:13px}
-.dl-note{color:%(MUTED)s;font-size:13px;margin-bottom:18px;flex:1}
+ transition:transform .28s cubic-bezier(.4,0,.2,1),box-shadow .28s,
+ border-color .28s}
+.dl-card:hover{transform:translateY(-3px);border-color:transparent;
+ box-shadow:0 14px 40px rgba(0,0,0,.09)}
+.dl-ico{width:38px;height:38px;border-radius:11px;background:%(SURFACE2)s;
+ color:%(TEXT)s;display:flex;align-items:center;justify-content:center;
+ margin-bottom:16px}
+.dl-head{display:flex;align-items:baseline;gap:8px;margin-bottom:5px}
+.dl-tag{font-weight:600;font-size:17px;letter-spacing:-.02em}
+.dl-who{color:%(FAINT)s;font-size:12.5px}
+.dl-note{color:%(MUTED)s;font-size:13.5px;margin-bottom:20px;flex:1;
+ line-height:1.6}
.dl-btn{display:block;text-align:center;background:%(ACCENT)s;color:#fff;
- padding:11px 16px;border-radius:9px;font-weight:600;font-size:14px;
- transition:background .15s}
-.dl-btn:hover{background:#1d4ed8}
-.dl-file{color:%(FAINT)s;font-size:11px;margin-top:9px;text-align:center;
+ padding:11px 16px;border-radius:980px;font-weight:500;font-size:14.5px;
+ transition:background .2s}
+.dl-btn:hover{background:%(ACCENT_DARK)s}
+.dl-file{color:%(FAINT)s;font-size:11px;margin-top:10px;text-align:center;
word-break:break-all}
.dl-soon{text-align:center;color:%(FAINT)s;font-size:14px;padding:11px 0;
- border:1px dashed %(BORDER)s;border-radius:9px}
-.dl-req{color:%(FAINT)s;font-size:12px;margin-top:12px;text-align:center}
+ border:1px dashed %(BORDER)s;border-radius:980px}
+.dl-req{color:%(FAINT)s;font-size:12px;margin-top:13px;text-align:center}
-/* FAQ */
+/* ---------------- FAQ ---------------- */
.faqs{max-width:720px;margin:0 auto}
.faq{border-bottom:1px solid %(BORDER)s}
-.faq summary{padding:20px 0;font-weight:600;font-size:16px;cursor:pointer;
- list-style:none;display:flex;justify-content:space-between;
- align-items:center;gap:16px}
+.faq summary{padding:22px 0;font-weight:600;font-size:16.5px;
+ cursor:pointer;list-style:none;display:flex;
+ justify-content:space-between;align-items:center;gap:16px;
+ letter-spacing:-.015em;transition:color .2s}
+.faq summary:hover{color:%(ACCENT)s}
.faq summary::-webkit-details-marker{display:none}
-.faq-mark{width:9px;height:9px;border-right:2px solid %(FAINT)s;
- border-bottom:2px solid %(FAINT)s;transform:rotate(45deg);
- transition:transform .2s;flex:none;margin-right:4px}
+.faq-mark{width:8px;height:8px;border-right:1.8px solid %(FAINT)s;
+ border-bottom:1.8px solid %(FAINT)s;transform:rotate(45deg);
+ transition:transform .25s;flex:none;margin-right:5px}
.faq[open] .faq-mark{transform:rotate(-135deg)}
-.faq-body{color:%(MUTED)s;font-size:15px;padding:0 0 22px;line-height:1.8}
+.faq-body{color:%(MUTED)s;font-size:15px;padding:0 0 24px;line-height:1.75}
-/* 页脚 */
-footer{padding:52px 0 64px;border-top:1px solid %(BORDER)s;
- color:%(MUTED)s;font-size:14px}
+/* ---------------- 收尾 CTA ---------------- */
+.endcta{text-align:center;padding:96px 0}
+.endcta h2{font-size:clamp(26px,4.4vw,44px);letter-spacing:-.03em;
+ font-weight:700;margin-bottom:14px}
+.endcta p{color:%(MUTED)s;font-size:17px;margin-bottom:30px}
+
+/* ---------------- 页脚 ---------------- */
+footer{padding:44px 0 60px;border-top:1px solid %(BORDER)s;
+ color:%(FAINT)s;font-size:12.5px;background:%(SURFACE2)s}
.foot-in{display:flex;justify-content:space-between;gap:24px;
flex-wrap:wrap;align-items:center}
.foot-links{display:flex;gap:22px}
+.foot-links a{transition:color .2s}
.foot-links a:hover{color:%(TEXT)s}
-.copy{color:%(FAINT)s;font-size:13px;margin-top:26px;text-align:center}
-""" % {"BG": BG, "SURFACE": SURFACE, "BORDER": BORDER, "TEXT": TEXT,
- "MUTED": MUTED, "FAINT": FAINT, "ACCENT": ACCENT}
+.copy{margin-top:22px;text-align:center;color:%(FAINT)s}
+
+/* ---------------- 滚动淡入 ----------------
+ ⚠ 关键:默认必须是**可见**的(没有 .reveal 的降级路径),
+ 否则脚本一旦没跑起来(旧浏览器/被拦截),整页就是空白。
+ 所以 .reveal 只在 JS 确认可用之后才由脚本加上。 */
+.reveal{opacity:0;transform:translateY(22px);
+ transition:opacity .7s cubic-bezier(.4,0,.2,1),
+ transform .7s cubic-bezier(.4,0,.2,1);
+ transition-delay:var(--d,0ms)}
+.reveal.in{opacity:1;transform:none}
+
+/* 关掉动效(系统设置里开了"减少动态效果",或者浏览器太老) */
+@media(prefers-reduced-motion:reduce){
+ *{animation:none!important;transition:none!important}
+ .reveal{opacity:1!important;transform:none!important}
+}
+""" % {"BG": BG, "SURFACE": SURFACE, "SURFACE2": SURFACE2, "BORDER": BORDER,
+ "TEXT": TEXT, "MUTED": MUTED, "FAINT": FAINT,
+ "ACCENT": ACCENT, "ACCENT_DARK": ACCENT_DARK}
# 产品展示那段的样式在 showcase.py 里,它自己已经格式化好了,
# 这里直接拼 —— **不能再套一层 %**。
@@ -344,18 +537,68 @@ footer{padding:52px 0 64px;border-top:1px solid %(BORDER)s;
SITE_CSS = SITE_CSS + showcase.SHOWCASE_CSS
+SITE_JS = """
+// ---------------- 滚动淡入 ----------------
+//
+// ⚠ 顺序很重要:先把 .reveal 加上(这一步之后元素才是隐藏的),
+// 再注册观察器。如果反过来,脚本报错时元素会被 CSS 永久隐藏。
+// 所以整个东西包在 try 里,出错就什么都不加 —— 页面照常显示。
+(function () {
+ if (!('IntersectionObserver' in window)) return; // 老浏览器:不做动画
+ try {
+ var reduce = window.matchMedia
+ && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+ if (reduce) return; // 用户要求别动
+ var items = document.querySelectorAll(
+ '.feat, .step, .stat, .quote, .dl-card');
+ if (!items.length) return;
+ for (var i = 0; i < items.length; i++) items[i].classList.add('reveal');
+ var io = new IntersectionObserver(function (entries) {
+ entries.forEach(function (en) {
+ if (en.isIntersecting) {
+ en.target.classList.add('in');
+ io.unobserve(en.target);
+ }
+ });
+ }, { rootMargin: '0px 0px -8% 0px', threshold: 0.08 });
+ for (var j = 0; j < items.length; j++) io.observe(items[j]);
+ // 兜底:1.2 秒后还没进视口的也显示出来(防止观察器失灵留下空白)
+ setTimeout(function () {
+ for (var k = 0; k < items.length; k++) items[k].classList.add('in');
+ }, 1200);
+ } catch (e) { /* 出错就不动,页面保持可见 */ }
+})();
+
+// ---------------- 锚点平滑滚动 ----------------
+document.querySelectorAll('a[href^="#"]').forEach(function (a) {
+ a.addEventListener('click', function (e) {
+ var target = document.querySelector(a.getAttribute('href'));
+ if (target) { e.preventDefault(); target.scrollIntoView({behavior:'smooth'}); }
+ });
+});
+"""
+
+
def render_site(files: list) -> str:
"""渲染官网首页。files 是 _list_downloads() 的结果。"""
version = _version(files)
badge = ("最新版 " + version) if version else ""
+ plats = "".join(
+ '
%s%s' % (_icon(name, 15, 1.7), label)
+ for name, label in (("monitor", "Windows 电脑"),
+ ("watch", "安卓手表"),
+ ("shield", "跨网络中转"))
+ )
+
return ("""
-
-
ykxmz · 教室电脑手表遥控
-
+
+
+
ykxmz · 用手表管教室那台电脑
+
@@ -377,16 +620,27 @@ def render_site(files: list) -> str: