feat(client): RMIC::2026 界面重做、可计算的版本机制与固件版本联动
构建客户端 / 检查(类型 + 单元测试) (push) Canceled after 0s
构建客户端 / Linux x64(deb + AppImage) (push) Canceled after 0s
构建客户端 / Windows x64(NSIS 安装包) (push) Canceled after 0s
构建客户端 / macOS 通用(.app + .dmg) (push) Canceled after 0s

- feat(client): 界面换成墨黑蓝底 + 单支红的矩形语言,切角全部去掉,仅装甲条保留平行四边形
- feat(client): 自托管 HarmonyOS Sans SC 与 JetBrains Mono,中文与拉丁数字逐字回退
- feat(client): 新增启动序列、首启门、用户端 HUD、裁判端控制台、设置抽屉与固件维护页
- feat(client): 裁判端中缝与下方控制台可拖,鼠标落在哪一路下方就看哪一路
- feat(client): 设置抽屉常驻实时预览,与画面共用同一份准星绘制代码
- feat(client): 动效改用 Material 3 的缓动曲线与时长,逐条替换掉自造的贝塞尔
- feat(version): 版本号与 CHANGELOG 由 scripts/version.mjs 从 git 历史算出,不再手写
- feat(firmware): Hello 报文携带语义化版本、构建号与自报协议号,负载向后兼容
- feat(client): 固件版本显示在绑定列表与裁判端每一格;协议不匹配时给出具体原因
- feat(firmware): 固件包版本与客户端同源,都从 version.json 读
- fix(client): getCurrentWindow() 在没有 Tauri 注入时同步抛异常,浏览器预览整棵树被卸载
- fix(client): 启动序列的淡出 effect 清掉了自己刚设的 setTimeout,界面永远停在启动页
- fix(client): 设置抽屉左侧导航的锚点没挂到 section 上,且用错了坐标系
- fix(protocol): Hello 改用宽松帧头解析,让协议不匹配的板子能被解释而不是静默消失
- fix(build): Windows 上 bindgen 加载不了 libclang 的同目录依赖,构建脚本补上搜索路径
- fix(build): esp-idf-sys 在长路径下拒绝构建,固件构建改用短的目标目录
- build(client): 队标图标,含光学尺寸分级与手工拼的多尺寸 ICO
- build(ci): 守门 job 增加版本一致性检查,产物名改为 rmic-2026-*
- docs: 补充界面语言、版本机制与协议通告布局的说明
This commit is contained in:
2026-10-19 02:40:08 +08:00
parent cc6b82d7ec
commit 896b21fe44
332 changed files with 16729 additions and 2765 deletions
+30 -10
View File
@@ -1,11 +1,22 @@
/**
* 命中反馈的整屏层:闪红 + 顶部一条文字。
* 命中反馈的整屏层:边沿泛红 + 条上的读数。
*
* # 大号数字刻意不放在画面正中
*
* 第一版把 `-7 / 100 → 93` 放在正中,结果它正好压在瞄准点上——被打中的那一下恰恰是最
* 需要看着准星的时候。现在数字由 `VideoSurface` 画在**准星旁边**(跟着瞄准点走,不挡
* 目标),这里只留闪红与顶部一条信息。
* 目标),这里只留边沿泛红与顶部一条读数。
*
* # 泛红为什么贴着边沿
*
* 旧版是一整块径向渐变盖在画面上,等于在最要紧的一瞬间把画面调暗。现在颜色只在四条边上
* 用 `inset` 阴影渗进来,中间保持原样——"被打到"这件事从四周传来,而不是挡在眼前。
*
* # 不给方向
*
* 命中事件里的 `index` 是**哪一条上报先发现了掉血**,不是"从哪一侧打来的"(主板一次广播
* 四条、血值相同,谁先到达取决于 UDP)。把它画成方向箭头会是一条看起来很专业、实际上
* 完全错误的线索,比不画糟得多。所以这一层是对称的。
*
* `key={hit.seq}` 是让 CSS 动画每次都从头开始的关键:同一个元素上换属性不会重启动画,
* 于是第二下命中就再也闪不出来了——一个"第一次有效"的 bug。
@@ -25,17 +36,26 @@ interface Props {
export function HitFlash({ hit, config, relevant, label }: Props) {
if (!hit || !relevant || !config.flash) return null;
const strength = Math.min(0.55, 0.16 * config.intensity);
// 强度换算成"边沿渗进来多深"。上限压在 70px:再深就开始吃掉画面。
const depth = Math.round(38 + 34 * Math.min(2, Math.max(0.2, config.intensity)));
return (
<div key={hit.seq} className="hit-flash" style={{ ["--flash" as string]: strength }}>
<div className="hit-flash__banner">
{label && <span className="hit-flash__label">{label}</span>}
<span className="hit-flash__value">-{hit.damage}</span>
<span className="hit-flash__sub">
{hit.from} → {hit.to}
<div
key={hit.seq}
className="hit"
style={{ ["--hit-depth" as string]: `${depth}px` }}
aria-hidden="true"
>
<span className="hit__edge" />
<span className="hit__tick">
{label && <span className="hit__tick-label">{label}</span>}
<span className="hit__tick-damage num">−{hit.damage}</span>
<span className="hit__tick-hp num">
{hit.from}
<span className="hit__tick-arrow">→</span>
{hit.to}
</span>
</div>
</span>
</div>
);
}