v0.1.33c: UI/动效打磨——过渡动画/停帧节能/季节对齐/反馈闭环
【动效】Modal 淡入+升起缓动(modalFadeIn/modalRise);LogFeed 入场动画(feedIn);
fx std 档放行刀光(战场核心反馈);任务凯旋补 spark/战殁补 ripple
【节能】DriftLayer 2.6s 转 onAnimationEnd 删除节点(消除空转 setInterval×slice 定期 setState);
fx-canvas 粒子空态停帧(空 rAF 取消+spawnBurst 唤醒)——低端机待机零开销
【季节】fx-canvas 季节色改读 data-season(游戏月驱动,不再用系统月——夏季粒子不再飘冬季色)
【性能】transition all 12 处→具体属性(background-color/color/transform/opacity);
backdrop-filter 保留(视觉主)
【UI 修复】MemberModal 装备栏空守卫(未知名器)+职事按钮 disabled={maxed} 统一;
议和按钮灵石不足 disabled+title(不再静默);事件弹窗 RES_NAME 补新材料/增筑显示中文名;
回卷按钮无存点 disabled+title;速度按钮 sel/gel 类名统一;MarketPanel 删死变量 stock;
debug 后门 __SMOKE__/DEV 门控(生产干净,冒烟仍可用——main 注入 __SMOKE__)
【测试】fx-timesense std 刀光断言更新 5030 全绿
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
interface DriftItem { id: number; text: string; cls: string }
|
||||
interface DriftItem { id: number; text: string; cls: string; at: number }
|
||||
|
||||
/** 资源漂字层:每次 fx:drift 事件追加独立节点(互不覆盖),超 8 条自动修剪尾部 */
|
||||
export function DriftLayer() {
|
||||
@@ -12,22 +12,18 @@ export function DriftLayer() {
|
||||
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' })
|
||||
next.push({ id: now + Math.random() + k.length, text: `${v > 0 ? '+' : ''}${v}`, cls: v > 0 ? 'pos' : 'neg', at: now })
|
||||
}
|
||||
if (next.length === 0) return
|
||||
setItems((old) => [...old, ...next].slice(-8))
|
||||
setItems((old) => (next.length + old.length > 8 ? [] : old).concat(next))
|
||||
}
|
||||
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 () => window.removeEventListener('fx:drift', onDrift)
|
||||
}, [])
|
||||
return (
|
||||
<div className="drift-layer">
|
||||
{items.map((it) => (
|
||||
<span key={it.id} className={`drift ${it.cls}`}>{it.text}</span>
|
||||
<span key={it.id} className={`drift ${it.cls}`} onAnimationEnd={() => setItems((old) => old.filter((x) => x.id !== it.id))}>{it.text}</span>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -6,9 +6,14 @@ import { describeRealm } from '../../game/data/realms'
|
||||
import { Character } from '../../game/types/domain'
|
||||
import { combatPowerOf } from '../../game/engine/runtime/Systems/combat'
|
||||
import { FORMATIONS, FormationId } from '../../game/data/formations'
|
||||
import { buildingById } from '../../game/data/buildings'
|
||||
import { ModalShell } from './ModalShell'
|
||||
|
||||
const RES_NAME: Record<string, string> = { lingcao: '灵草', lingkuang: '灵矿', beastcore: '兽核', stones: '灵石' }
|
||||
const RES_NAME: Record<string, string> = {
|
||||
lingcao: '灵草', lingkuang: '灵矿', beastcore: '兽核', stones: '灵石',
|
||||
lingyu: '灵玉', lingmu: '灵木', shoupi: '兽皮', lingguo: '灵果', linglu: '灵露', dansha: '丹砂', fuzhi: '符纸',
|
||||
'talisman-feng': '风符', 'talisman-shan': '山符', 'brew-niang': '灵酿'
|
||||
}
|
||||
|
||||
function describeEff(eff: Record<string, never> | { [k: string]: unknown }): string {
|
||||
const parts: string[] = []
|
||||
@@ -23,7 +28,7 @@ function describeEff(eff: Record<string, never> | { [k: string]: unknown }): str
|
||||
if (e.relation) {
|
||||
for (const v of Object.values(e.relation)) parts.push(v > 0 ? `交好+${v}` : `交恶${v}`)
|
||||
}
|
||||
if (e.addBuilding) parts.push('增筑' + e.addBuilding)
|
||||
if (e.addBuilding) parts.push('增筑「' + (buildingById(e.addBuilding)?.name ?? e.addBuilding) + '」')
|
||||
if (e.pillGain) {
|
||||
for (const v of Object.values(e.pillGain)) parts.push(`得丹+${v}`)
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ export function MemberModal({ member }: { member: Character }) {
|
||||
</b></div>
|
||||
<div className="mm-line"><span>气血</span><b>{Math.round(member.health)}</b></div>
|
||||
<div className="mm-line"><span>功法</span><b>{tech ? `${tech.name}(${gradeShort(tech.grade)}阶)` : member.realm.major === 'mortal' ? '—' : '未习功法'}</b></div>
|
||||
<div className="mm-line"><span>法宝</span><b>{member.equipment ? `${ITEMS[member.equipment].name}(+${Math.round((ARTIFACT_POWER[member.equipment] ?? 0) * 100)}%)` : '—'}</b></div>
|
||||
<div className="mm-line"><span>法宝</span><b>{member.equipment ? `${ITEMS[member.equipment]?.name ?? '未知名器'}(+${Math.round((ARTIFACT_POWER[member.equipment] ?? 0) * 100)}%)` : '—'}</b></div>
|
||||
<div className="mm-line"><span>姻亲</span><b>{member.spouseId ? s.members[member.spouseId]?.name ?? '' : member.spouseHouse ?? '未婚'}</b></div>
|
||||
</div>
|
||||
|
||||
@@ -284,7 +284,7 @@ export function MemberModal({ member }: { member: Character }) {
|
||||
<button
|
||||
key={pid}
|
||||
className={`btn btn-sm ${member.post === pid ? 'btn-primary' : ''}`}
|
||||
disabled={maxed && !!member.post}
|
||||
disabled={maxed}
|
||||
style={maxed && !member.post ? { opacity: 0.45 } : undefined}
|
||||
title={`${def.desc}${w.postCount(pid)}/${def.max} 在任`}
|
||||
onClick={() => {
|
||||
|
||||
Reference in New Issue
Block a user