feat(timeline): 模式配色区分 + 舞台放大/时间轴可拖高度 + 滚动条美化 + 镜头跟玩家

- 两模式各自背景色调:海选审核=暖棕 / 演出配置=冷青绿(顶栏/背景/列表/边框/滚动条全区分)
- 舞台自适应填充放大(画布提到960×470),时间轴默认200px固定高+中间分隔条可上下拖调高度
- 时间轴滚动条换成纤细主题化样式(替代默认丑滚动条),随模式变色
- 镜头修正:默认锁玩家(模拟真机45°俯视跟随相机),换说话人不再推近,仅显式镜头点才移焦
This commit is contained in:
2026-06-13 20:43:52 +08:00
parent f164ed0a08
commit ebff4e9b3b
3 changed files with 61 additions and 14 deletions

View File

@ -210,11 +210,10 @@
function activeDialogue(M, tau) { return M.clips.find(c => c.kind === "dialogue" && tau >= c.start && tau < c.start + c.dur) || null; }
function activeAnim(M, actor, tau) { return M.clips.find(c => c.kind === "anim" && c.actor === actor && tau >= c.start && tau < c.start + c.dur) || null; }
function focusWorldAt(M, tau) {
// 真机=45°俯视跟随玩家的相机默认锁玩家不会因换说话人而推近仅显式镜头点(camera clip)才移焦。
let fp = null;
M.clips.filter(c => c.kind === "camera").forEach(c => { if (tau >= c.start) fp = c.focus; });
if (fp) { const p = anchorXZ(M.anchors, fp); if (p) return p; }
const dlg = activeDialogue(M, tau);
if (dlg && dlg.actor) return actorPosAt(M, dlg.actor, tau);
if (actorsIn(M).includes("P1")) return actorPosAt(M, "P1", tau);
const b = M.bounds; return { x: (b.minX + b.maxX) / 2, z: (b.minZ + b.maxZ) / 2 };
}
@ -225,7 +224,7 @@
const TEMPLATE =
'<div class="tl-stagepanel">' +
' <div class="tl-mapinfo"></div>' +
' <div class="tl-stagewrap"><canvas class="tl-stage" width="780" height="380"></canvas><div class="tl-choices hidden"></div></div>' +
' <div class="tl-stagewrap"><canvas class="tl-stage" width="960" height="470"></canvas><div class="tl-choices hidden"></div></div>' +
' <div class="tl-controls">' +
' <button class="tl-play primary">▶ 播放</button>' +
' <button class="tl-restart mini">⏮ 重头</button>' +
@ -235,6 +234,7 @@
' <span class="tip">单击节点选中→「从选中处开始」,或双击节点直接开始(位置按途中走位重放) · 遇选择/战斗/随机弹选项</span>' +
' </div>' +
'</div>' +
'<div class="tl-resizer" title="拖动调整时间轴高度"></div>' +
'<div class="tl-timelinepanel"><div class="tl-tracks"></div></div>';
function show(host, IR, DICT, POINTSETS) {
@ -255,6 +255,8 @@
startbtn: host.querySelector(".tl-startbtn"),
fitbtn: host.querySelector(".tl-fitbtn"),
time: host.querySelector(".tl-time"),
resizer: host.querySelector(".tl-resizer"),
timelinepanel: host.querySelector(".tl-timelinepanel"),
playhead: null,
};
els.mapinfo.textContent = "点位集:" + psName + (ps.mapId ? "(地图 " + ps.mapId + "" : "") +
@ -265,6 +267,19 @@
els.restart.onclick = () => restart();
els.startbtn.onclick = () => { if (selNode) startFrom(selNode, true); };
els.fitbtn.onclick = () => { fitMode = !fitMode; els.fitbtn.classList.toggle("on", fitMode); refreshTimeline(); renderFrame(); };
// 拖拽分隔条调时间轴高度(向上拖=时间轴变高/舞台变小,反之)
els.resizer.onmousedown = e => {
e.preventDefault();
const startY = e.clientY, startH = els.timelinepanel.offsetHeight;
document.body.style.cursor = "row-resize";
const mv = ev => {
const h = Math.max(80, Math.min((els.host.clientHeight - 150), startH - (ev.clientY - startY)));
els.timelinepanel.style.height = h + "px";
if (fitMode) { refreshTimeline(); renderFrame(); }
};
const up = () => { document.removeEventListener("mousemove", mv); document.removeEventListener("mouseup", up); document.body.style.cursor = ""; };
document.addEventListener("mousemove", mv); document.addEventListener("mouseup", up);
};
startFrom(firstNode(IR));
}