v0.1.13: 金碧山河(SVG/Canvas 素材 + 动效系统 + 舞美重装)

【素材生成(零外部资源)】
- scripts/gen-art.mjs 程序化产出 5 枚 SVG 资产:金碧山水全景(山脊/金乌/雁阵/水纹)、
  祥云纹带、回纹角饰、仙鹤、星辰层 → src/renderer/assets/gen/,npm run gen:art 可重跑

【Canvas 动效系统(单 RAF)】
- ui/fx-canvas.ts:灵雾粒子流(常驻随季色)/金砂喷发(spark)/刀光(blade)/涟漪(ripple),
  单实例防重入、防膨胀 cap、GPU 友好、visibility 自停
- ui/fx.ts + core/fxqueue.ts:FxGate 统一发射闸——档位过滤(柔/标/全)、
  reduced-motion 降级、单帧批处理防竞态、deltas 纯函数(漂字差分)

【舞美】
- 金碧背景层 bg-scene(全页面,随季节 hue-rotate 色温渐变)
- 顶栏时辰司:季节印(春·熏风/夏·蝉鸣/秋·霜叶/冬·雪静)+年月进度环(SVG 弧段)+节气名
- 成员卡:闭关 aura 三点浮动 + 修为流光 shimmer
- 消息流:good 事件(突破/获宝/落槌)→金砂喷发,战事→刀光扫过战报
- Boot 卷轴展开过渡;drift 漂字层(独立节点队列不覆盖,8 条裁剪)
- 设置页动效三档(柔和/标准/全开)

【竞态规避】
- RAF 单循环 guard、particles cap 裁剪、FxGate draining 互斥、漂字节点附加不覆盖、
  visibility 自动降级、advance in-flight 闸沿用

【测试】937→967(timesense 矩阵 15 + FxGate 竞态/档位/deltas 15)
全量 35 套件通过;金钟罩零漂移(纯 UI/素材层);typecheck/build 通过
This commit is contained in:
2026-08-23 12:50:18 +08:00
parent 607624695d
commit c52ecffcd9
20 changed files with 904 additions and 6 deletions
+34
View File
@@ -0,0 +1,34 @@
import { useEffect, useState } from 'react'
interface DriftItem { id: number; text: string; cls: string }
/** 资源漂字层:每次 fx:drift 事件追加独立节点(互不覆盖),超 8 条自动修剪尾部 */
export function DriftLayer() {
const [items, setItems] = useState<DriftItem[]>([])
useEffect(() => {
const onDrift = (e: Event) => {
const detail = (e as CustomEvent).detail as Record<string, number>
const now = Date.now()
const next: DriftItem[] = []
for (const [k, v] of Object.entries(detail)) {
if (v === 0 || !Number.isFinite(v)) continue
next.push({ id: now + Math.random() + k.length, text: `${v > 0 ? '+' : ''}${v}`, cls: v > 0 ? 'pos' : 'neg' })
}
if (next.length === 0) return
setItems((old) => [...old, ...next].slice(-8))
}
window.addEventListener('fx:drift', onDrift)
const timer = setInterval(() => setItems((old) => old.slice(0)), 2600) // 到期清理由 CSS fade 承担
return () => {
window.removeEventListener('fx:drift', onDrift)
clearInterval(timer)
}
}, [])
return (
<div className="drift-layer">
{items.map((it) => (
<span key={it.id} className={`drift ${it.cls}`}>{it.text}</span>
))}
</div>
)
}
+2 -1
View File
@@ -47,9 +47,10 @@ export function MemberCard({ c }: { c: Character }) {
</div>
{!dead && (
<div className="mc-foot">
<div className="bar" title="灵气修为" style={{ height: 6 }}>
<div className={`bar ${c.state === 'meditation' ? 'shimmer' : ''}`} title="灵气修为" style={{ height: 6 }}>
<div style={{ width: `${c.realmProgress}%` }} />
</div>
{c.state === 'meditation' && <div className="aura-dots"><i></i><i></i><i></i></div>}
</div>
)}
</div>