From ebff4e9b3b306ecad0f1b616732986c09f6d5a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=93=E9=9B=A8=E9=B9=8F?= <846149189@qq.com> Date: Sat, 13 Jun 2026 20:43:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(timeline):=20=E6=A8=A1=E5=BC=8F=E9=85=8D?= =?UTF-8?q?=E8=89=B2=E5=8C=BA=E5=88=86=20+=20=E8=88=9E=E5=8F=B0=E6=94=BE?= =?UTF-8?q?=E5=A4=A7/=E6=97=B6=E9=97=B4=E8=BD=B4=E5=8F=AF=E6=8B=96?= =?UTF-8?q?=E9=AB=98=E5=BA=A6=20+=20=E6=BB=9A=E5=8A=A8=E6=9D=A1=E7=BE=8E?= =?UTF-8?q?=E5=8C=96=20+=20=E9=95=9C=E5=A4=B4=E8=B7=9F=E7=8E=A9=E5=AE=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 两模式各自背景色调:海选审核=暖棕 / 演出配置=冷青绿(顶栏/背景/列表/边框/滚动条全区分) - 舞台自适应填充放大(画布提到960×470),时间轴默认200px固定高+中间分隔条可上下拖调高度 - 时间轴滚动条换成纤细主题化样式(替代默认丑滚动条),随模式变色 - 镜头修正:默认锁玩家(模拟真机45°俯视跟随相机),换说话人不再推近,仅显式镜头点才移焦 --- web/static/app.js | 1 + web/static/style.css | 53 +++++++++++++++++++++++++++++++++--------- web/static/timeline.js | 21 ++++++++++++++--- 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/web/static/app.js b/web/static/app.js index a5eba07..671afca 100644 --- a/web/static/app.js +++ b/web/static/app.js @@ -203,6 +203,7 @@ $("wrap").classList.toggle("hidden", m !== "review"); $("perform-wrap").classList.toggle("hidden", m !== "perform"); $("review-toolbar").style.display = m === "review" ? "" : "none"; + document.body.classList.toggle("perform-mode", m === "perform"); // 切背景色调 Timeline.stop(); if (m === "perform") performLoadList(); } diff --git a/web/static/style.css b/web/static/style.css index 5c1755a..0c8b2b0 100644 --- a/web/static/style.css +++ b/web/static/style.css @@ -230,8 +230,22 @@ button.mini { padding:2px 8px; font-size:12px; } .mode-btn.active { background:#5a4a26; color:#f3dca0; } header .who { margin-left:auto; font-size:12px; color:#9a8f7e; } +/* ---- 模式背景区分:海选审核=暖棕(默认) / 演出配置=冷青绿 ---- */ +body.perform-mode { background:#0c1513; } +body.perform-mode header { background:#10211c; border-bottom-color:#244a3e; } +body.perform-mode .mode-btn.active { background:#1f5040; border-color:#2f7a60; color:#aee6d0; } +body.perform-mode .mode-switch { border-color:#2f7a60; } + /* ---- 演出配置页 ---- */ -#perform-wrap { display:flex; flex:1; min-height:0; } +#perform-wrap { display:flex; flex:1; min-height:0; background:#0c1513; } +#perform-wrap #perform-list-pane { background:#0e1c18; border-right-color:#1c3a30; } +#perform-wrap .perform-listhead { background:#10211c; color:#8fc9b3; border-bottom-color:#1c3a30; } +#perform-wrap .ev { border-bottom-color:#13241f; } +#perform-wrap .ev:hover { background:#13241f; } +#perform-wrap .ev.sel { background:#163027; border-left-color:#3fb98a; } +#perform-wrap .tl-stagewrap { border-color:#1c3a30; } +#perform-wrap .tl-timelinepanel .tl-tracks { border-color:#1c3a30; } +#perform-wrap .tl-mapinfo { color:#7fae9c; } #wrap.hidden, #perform-wrap.hidden { display:none; } #perform-list-pane { width:250px; background:#19150f; border-right:1px solid #3a322a; display:flex; flex-direction:column; flex:none; } @@ -241,13 +255,19 @@ header .who { margin-left:auto; font-size:12px; color:#9a8f7e; } #perform-main { flex:1; min-width:0; display:flex; flex-direction:column; padding:14px 16px; min-height:0; } #perform-main .empty-center { position:static; inset:auto; min-height:240px; } -/* ---- 演出预览:上=舞台面板 / 下=时间轴面板(分开两块)---- */ -.tl-stagepanel { flex:none; } -.tl-mapinfo { font-size:12px; color:#9a8f7e; margin-bottom:8px; } -.tl-stagewrap { position:relative; background:#15130d; border:1px solid #3a322a; border-radius:6px; - overflow:hidden; line-height:0; max-width:640px; } -.tl-stage { width:100%; height:auto; display:block; } -.tl-controls { display:flex; align-items:center; gap:10px; margin:10px 0; flex-wrap:wrap; } +/* ---- 演出预览:上=舞台面板(自适应放大) / 下=时间轴面板(可拖高度) ---- */ +.tl-stagepanel { flex:1 1 auto; min-height:150px; display:flex; flex-direction:column; } +.tl-mapinfo { flex:none; font-size:12px; color:#9a8f7e; margin-bottom:6px; } +.tl-stagewrap { position:relative; flex:1; min-height:0; background:#15130d; border:1px solid #3a322a; + border-radius:6px; overflow:hidden; } +.tl-stage { width:100%; height:100%; object-fit:contain; display:block; } +/* 分隔条:拖动调时间轴高度 */ +.tl-resizer { flex:none; height:10px; margin:5px 0; cursor:row-resize; border-radius:5px; + background:#16120d; position:relative; } +.tl-resizer::after { content:""; position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); + width:48px; height:3px; border-radius:2px; background:#5a4a32; } +.tl-resizer:hover::after { background:#e6c878; } +.tl-controls { flex:none; display:flex; align-items:center; gap:10px; margin:8px 0 0; flex-wrap:wrap; } .tl-controls .tip { font-size:11.5px; color:#7a7264; } .tl-time { font-size:13px; color:#e6c878; font-variant-numeric:tabular-nums; min-width:90px; } .tl-startbtn { max-width:300px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } @@ -263,10 +283,21 @@ header .who { margin-left:auto; font-size:12px; color:#9a8f7e; } border:1px solid #8a7038; border-radius:6px; padding:8px 14px; font-size:13px; cursor:pointer; box-shadow:0 2px 8px rgba(0,0,0,.55); } .tl-choice-btn:hover { background:#5a4a26; border-color:#e6c878; } -/* 时间轴面板:独立、占满剩余高度、自己横向滚动 */ -.tl-timelinepanel { flex:1; min-height:120px; margin-top:8px; display:flex; } +/* 时间轴面板:固定/可拖高度、自己横向滚动 */ +.tl-timelinepanel { flex:none; height:200px; min-height:80px; display:flex; } .tl-tracks { position:relative; flex:1; min-width:0; overflow-x:auto; overflow-y:auto; cursor:grab; - background:#19150f; border:1px solid #3a322a; border-radius:6px; padding-top:20px; } + background:#19150f; border:1px solid #3a322a; border-radius:6px; padding-top:20px; + scrollbar-width:thin; scrollbar-color:#5a4a32 transparent; } +/* 纤细主题化滚动条(替代默认丑滚动条) */ +.tl-tracks::-webkit-scrollbar { height:9px; width:9px; } +.tl-tracks::-webkit-scrollbar-track { background:transparent; } +.tl-tracks::-webkit-scrollbar-thumb { background:#4a4030; border-radius:6px; border:2px solid #19150f; } +.tl-tracks::-webkit-scrollbar-thumb:hover { background:#6a5a3c; } +.tl-tracks::-webkit-scrollbar-corner { background:transparent; } +#perform-wrap .tl-tracks { scrollbar-color:#2f5a48 transparent; } +#perform-wrap .tl-tracks::-webkit-scrollbar-thumb { background:#2a4a3e; border-color:#0e1c18; } +#perform-wrap .tl-resizer::after { background:#2f5a48; } +#perform-wrap .tl-resizer:hover::after { background:#3fb98a; } .tl-ruler { position:relative; height:16px; border-bottom:1px solid #2a2419; } .tl-tick { position:absolute; top:0; height:16px; border-left:1px solid #2a2419; } .tl-tick span { font-size:9px; color:#6a6256; padding-left:3px; } diff --git a/web/static/timeline.js b/web/static/timeline.js index 8cb1da4..56c4812 100644 --- a/web/static/timeline.js +++ b/web/static/timeline.js @@ -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 = '
' + '
' + - '
' + + '
' + '
' + ' ' + ' ' + @@ -235,6 +234,7 @@ ' 单击节点选中→「从选中处开始」,或双击节点直接开始(位置按途中走位重放) · 遇选择/战斗/随机弹选项' + '
' + '
' + + '
' + '
'; 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)); }