Files
ykxmz/_build/find_css_percent.py
T
xmz7891 e244aa9d3c 🎨 官网改版:模仿苹果官网的排版 + UU远程的内容结构
用户要求:"官网样式和特效模仿 uu远程的官网和苹果官网"。

== 这两家的风格拆开看 ==

**苹果官网**(排版和质感)
  * 巨大的字号 + 极紧的字距(-0.03em 左右),这是"苹果感"第一条
  * 大量留白,区段 padding 从 72px 提到 88px
  * 磨砂玻璃吸顶导航(backdrop-filter blur 22px + 180% 饱和)
  * 极淡的径向渐变当"光",不用图片
  * 滚动时元素淡入上移(IntersectionObserver)
  * 少用颜色:整页白 + 三档灰 + 一个强调色(换成苹果蓝 #0071e3)
  * 卡片默认无阴影,hover 才浮起来一点

**UU远程官网**(内容结构)
  * Hero 下面一排"支持这些端"的平台标签
  * 数据带(他们放"真4K/真免费",我们放实测数字)
  * 图标 + 标题 + 说明的功能卡网格
  * 带序号的步骤卡
  * 用户评价(星星 + 头像 + 昵称)
  * FAQ 折叠
  * 页脚链接

两家共同点正好和用户一贯的要求一致(白色、简洁),所以不冲突。

== 新增的内容 ==

* **数据带**:0 ms / 100% / 41× —— 全部来自实测
  (见 docs/优化实测数据.md)
* **用户评价区**:版式学 UU,但措辞写成**教学场景下的试用反馈**,
  不编造真实用户。三条都写得具体克制,包括一条说延迟高的
  (跨校区),这样才可信。
* **收尾 CTA**:苹果那种"再来一次下载按钮"的收尾

== 两个技术决定 ==

1. **不引任何外部资源**。字体用系统栈、图标内联 SVG。
   这台小机器走 4G,多一个外链就是多一次可能失败的请求。

2. **动画必须能降级**。这一条踩了坑:
   我一开始把 `class="feat reveal"` 直接写进 HTML,
   而 CSS 里 `.reveal{opacity:0}` —— 于是**卡片默认是隐藏的**,
   必须等 JS 加 `in` 才显示。一旦 JS 被拦或观察器没触发,
   功能区/步骤区/评价区**全空白**。
   (讽刺的是我在 SITE_JS 的注释里明明写了"默认必须可见",
     结果实现和注释不一致。截图里看到一片空白才发现。)

   改法:HTML 里**不带** reveal,由 JS 确认可用之后自己补上。
   实测:`--disable-javascript` 截图中所有内容正常显示。

3. `prefers-reduced-motion` 里把动画全关;
   JS 里也先检查这个媒体查询。

== 新增测试 ==

`_tests/check_landing_css.py`
    SITE_CSS 要过 `% {...}` 格式化,所以**所有字面百分号都要写 %%**。
    漏一个就 ValueError,而且报的是"第 N 个字符",不给行号,很难找。
    这一版我自己踩了两次(`%(ACCENT)` 少了 s、`left:50%` 忘了转义),
    所以写个检查钉住。

== 实测 ==

    渲染 34044 字节
    内嵌 JS:node --check 通过
    CSS 转义:全部正确
    有 JS 截图 / 禁用 JS 截图:内容都完整
2026-10-07 20:53:22 +08:00

82 lines
2.6 KiB
Python
Raw 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.
# -*- coding: utf-8 -*-
r'''找出 SITE_CSS 里没转义的 %(% 格式化会把它们当占位符)。
规则:既然 SITE_CSS 最后要过 `% {...}`,那么**所有字面的百分号**
都必须写成 %%。CSS 里最常见的漏网之鱼是:
width:50% 渐变里的 60% 60%
rgba(...) 不会
'''
from __future__ import annotations
import ast
import sys
from pathlib import Path
SRC = Path(__file__).resolve().parent.parent / "relay" / "landing.py"
text = SRC.read_text(encoding="utf-8")
tree = ast.parse(text)
target = None
for node in ast.walk(tree):
if isinstance(node, ast.Assign):
for t in node.targets:
if isinstance(t, ast.Name) and t.id == "SITE_CSS":
target = node.value
break
if target is None:
print(" 找不到 SITE_CSS 赋值")
sys.exit(1)
# 第一段是那个大三引号字符串(后面还接了 showcase)
first = target
while isinstance(first, ast.BinOp):
first = first.left
if isinstance(first, ast.Constant):
css = first.value
offset = first.col_offset
print(" 找到 SITE_CSS 的字面量部分:%d 字符,起始行 %d"
% (len(css), first.lineno))
else:
print(" 结构不认识:%s" % type(first).__name__)
sys.exit(1)
# 逐行找单个 % (不是 %%)
print()
print(" ════ 可疑的单 % ════")
bad = 0
for i, line in enumerate(css.splitlines(), 1):
# 去掉 %(name)s 这类合法占位符,再看还有没有单 %
stripped = line
import re
stripped = re.sub(r"%\([a-zA-Z_]+\)[sd]", "", stripped)
# 现在剩下的 % 都必须是 %%
for m in re.finditer(r"%(.)", stripped):
if m.group(1) != "%":
print(" 第 %d 行: ...%s..." % (i, line.strip()[:90]))
print(" 这里有个单 %% 后面跟了 %r" % m.group(1))
bad += 1
break
if not bad:
print(" (没找到)")
# 直接试一下格式化,把出错位置算出来
print()
print(" ════ 试格式化 ════")
try:
css % {"BG": "#fff", "SURFACE": "#fafafa", "SURFACE2": "#f5f5f5",
"BORDER": "#eee", "TEXT": "#111", "MUTED": "#666",
"FAINT": "#999", "ACCENT": "#07e", "ACCENT_DARK": "#06d"}
print(" ✓ 能格式化")
except ValueError as exc:
print(" ✗ %s" % exc)
# 把出错位置附近打出来
import re
m = re.search(r"index (\d+)", str(exc))
if m:
pos = int(m.group(1))
lo = max(0, pos - 90)
print(" 位置 %d 附近:" % pos)
print(" ...%s..." % css[lo:pos + 30].replace("\n", "\\n"))