From f8e8bb8acb54a51696bb32af40c0659297798140 Mon Sep 17 00:00:00 2001 From: JustMe Date: Fri, 9 Oct 2026 03:09:14 +0800 Subject: [PATCH] feat(display): pointer-sway background with a dark variant; acrylic glass tokens [CI] --- web/display/scripts/prepare-bg.py | 36 +++++++++++ web/display/src/App.tsx | 12 ++-- web/display/src/assets/bg-dark.jpg | Bin 0 -> 191939 bytes web/display/src/assets/bg.jpg | Bin 159532 -> 188379 bytes web/display/src/display.css | 100 +++++++++++++++++++++-------- web/display/src/parallax.ts | 81 +++++++++++++++++++++++ web/shared/theme/tokens.css | 14 ++++ 7 files changed, 212 insertions(+), 31 deletions(-) create mode 100644 web/display/scripts/prepare-bg.py create mode 100644 web/display/src/assets/bg-dark.jpg create mode 100644 web/display/src/parallax.ts diff --git a/web/display/scripts/prepare-bg.py b/web/display/scripts/prepare-bg.py new file mode 100644 index 0000000..2b3281a --- /dev/null +++ b/web/display/scripts/prepare-bg.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +"""把一张背景图处理成展示页用的背景:缩到最长边不超过 3840 像素、预先模糊、存成渐进式 JPEG。 +用法:python web/display/scripts/prepare-bg.py <原图> <输出,例如 web/display/src/assets/bg.jpg> [--sigma 10] + +为什么预先模糊:背景层会随鼠标移动,如果让浏览器用 CSS filter 现场模糊这么大的一层,每次移动都要重新算, +和卡片自己的 backdrop-filter 叠在一起很吃 GPU(低配机器上会出现闪烁、层叠错乱)。图片本身已经是模糊的,移动就只是搬一块贴图。 +模糊半径默认按宽度的 0.6% 算(1672 像素宽约 10),和原来 CSS 里 blur(10px) 的效果相近;想更糊或更清楚用 --sigma。 +""" +import argparse +import os + +from PIL import Image, ImageFilter + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("source") + parser.add_argument("target") + parser.add_argument("--sigma", type=float, default=None, help="模糊半径(像素,按处理后的图);默认宽度的 0.6%%") + parser.add_argument("--max-side", type=int, default=3840) + parser.add_argument("--quality", type=int, default=92) + args = parser.parse_args() + + image = Image.open(args.source).convert("RGB") + longest = max(image.size) + if longest > args.max_side: + scale = args.max_side / longest + image = image.resize((round(image.width * scale), round(image.height * scale)), Image.LANCZOS) + sigma = args.sigma if args.sigma is not None else image.width * 0.006 + image = image.filter(ImageFilter.GaussianBlur(sigma)) + image.save(args.target, quality=args.quality, optimize=True, progressive=True, subsampling=0) + print(f"{args.target}: {image.width}x{image.height}, blur sigma {sigma:.1f}, {os.path.getsize(args.target) // 1024} KB") + + +if __name__ == "__main__": + main() diff --git a/web/display/src/App.tsx b/web/display/src/App.tsx index 3450991..e946799 100644 --- a/web/display/src/App.tsx +++ b/web/display/src/App.tsx @@ -6,6 +6,7 @@ import { SourceButton, ThemeToggle } from '@shared/ui' import { Compare } from './pages/Compare' import { Line } from './pages/Line' import { Overview } from './pages/Overview' +import { useBackgroundParallax } from './parallax' function LineRoute() { const { id } = useParams() @@ -13,21 +14,24 @@ function LineRoute() { } export function App() { + const background = useRef(null) + useBackgroundParallax(background) const location = useLocation() const ipv6 = location.pathname.endsWith('~ipv6') || new URLSearchParams(location.search).get('ip_version') === 'ipv6' const familyQuery = ipv6 ? '?ip_version=ipv6' : '' return ( <> +