Files
story-edit-web/web/static/style.css
邓雨鹏 dbf857769e feat(timeline): P1 白模演出预览——时间线+俯视舞台播放
- /api/pointsets 增 anchors(含 pos/rot/kind/npc 坐标),保留 points 名字数组兼容下拉
- 新增 static/timeline.js:线性化主路径→按时长铺多轨时间线 + 2D 俯视白模舞台播放
  (走位插值/对话打字机/镜头俯视框示意/playhead/点击跳转);战斗/选择/随机仅标点不模拟
- 工具栏加「演出预览」按钮 + 预览遮罩 + 暗金主题样式
- 时长模型:对话=字数×打字机速度,走位=坐标距离÷速度,动画/镜头给缺省时长
- node 离线测试两样张(含 out_ref+fight+skip)逻辑全过;修掉结局重复渲染 bug

设计文档见主仓 docs/plans/2026-06-13-story-timeline-editor-and-whitebox-preview-design.md (P1 期)
2026-06-13 10:52:20 +08:00

258 lines
16 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

* { box-sizing: border-box; }
body { margin:0; font-family:"Microsoft YaHei","PingFang SC",sans-serif;
background:#161310; color:#e8e0d4; height:100vh; overflow:hidden;
display:flex; flex-direction:column; }
/* ---- header / toolbar ---- */
header { padding:10px 18px; background:#1f1a15; border-bottom:1px solid #3a322a;
display:flex; align-items:center; gap:18px; flex:none; }
header h1 { margin:0; font-size:18px; color:#e6c878; white-space:nowrap; }
header h1 .ver { font-size:12px; color:#9a8f7e; }
.toolbar { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.toolbar .sep { width:1px; height:20px; background:#3a322a; margin:0 4px; }
.toolbar .who { margin-left:10px; font-size:12px; color:#9a8f7e; }
button { background:#3a3024; color:#e6c878; border:1px solid #5a4a32;
padding:6px 13px; border-radius:5px; cursor:pointer; font-size:13px; }
button:hover:not(:disabled) { background:#4a3d2c; }
button:disabled { opacity:.4; cursor:not-allowed; }
button.primary { background:#5a4a26; border-color:#8a7038; color:#f3dca0; }
button.mini { padding:2px 8px; font-size:12px; }
/* ---- layout ---- */
#wrap { display:flex; flex:1; min-height:0; }
#list-pane { width:250px; background:#19150f; border-right:1px solid #3a322a;
display:flex; flex-direction:column; flex:none; }
.filters { padding:10px; display:flex; gap:6px; border-bottom:1px solid #3a322a; }
.filters select, .filters input, #login input, #import-text {
background:#241f18; color:#e8e0d4; border:1px solid #4a4030; border-radius:4px;
padding:6px 8px; font-size:13px; }
.filters select { flex:none; }
.filters input { flex:1; min-width:0; }
#event-list { overflow:auto; flex:1; }
.ev { padding:9px 12px; border-bottom:1px solid #241f18; cursor:pointer; }
.ev:hover { background:#221d16; }
.ev.sel { background:#2a2316; border-left:3px solid #e6c878; padding-left:9px; }
.ev .t { font-size:13.5px; color:#ddd3c2; }
.ev .g { font-size:11px; color:#7a7264; margin-top:2px; }
.ev .badge { float:right; font-size:11px; padding:1px 7px; border-radius:9px; }
.b-pending { background:#3a3320; color:#d8c060; }
.b-confirmed{ background:#1f3a24; color:#7ad88a; }
.b-discarded{ background:#3a2020; color:#d88; }
#graph-pane { flex:1; min-width:0; display:flex; flex-direction:column; position:relative; }
#graph-tools { flex:none; display:flex; align-items:center; gap:8px; padding:6px 10px;
background:#19150f; border-bottom:1px solid #3a322a; }
#graph-tools .tip { font-size:11.5px; color:#7a7264; }
#graph-tools .gsep { width:1px; height:16px; background:#3a322a; margin:0 3px; }
#drawflow { position:relative; flex:1; min-height:0; background:#161310;
background-image:radial-gradient(#2a2419 1.1px, transparent 1.1px);
background-size:22px 22px; }
.empty-center { position:absolute; inset:0; display:flex; align-items:center;
justify-content:center; color:#6a6256; font-size:15px; pointer-events:none; }
#edit-pane { width:370px; background:#1c1813; border-left:1px solid #3a322a;
overflow:auto; flex:none; }
.sec-title { padding:10px 14px; font-size:13px; color:#9a8f7e;
border-bottom:1px solid #3a322a; background:#19150f; letter-spacing:1px;
display:flex; justify-content:space-between; align-items:center; }
#meta-edit, #node-edit { padding:12px 14px; }
.empty { color:#6a6256; font-size:13px; padding:8px 0; }
/* ---- nodes (tree) ---- */
.node { background:#262019; border:1.5px solid #4a4030; border-radius:9px;
padding:9px 12px; width:188px; cursor:pointer; transition:.15s;
box-shadow:0 2px 6px rgba(0,0,0,.4); position:absolute; }
.node:hover { border-color:#e6c878; transform:translateY(-2px); }
.node.sel { border-color:#e6c878; box-shadow:0 0 0 2px rgba(230,200,120,.4); z-index:20; }
.node-acts { position:absolute; display:flex; gap:4px; z-index:30; transform:translateX(-100%); }
.nact { border:none; border-radius:5px; font-size:11px; line-height:1; padding:4px 6px;
cursor:pointer; color:#161310; font-weight:bold; box-shadow:0 1px 3px rgba(0,0,0,.5); }
.nact.add { background:#e6c878; }
.nact.add:hover { background:#f0d68a; }
.nact.del { background:#d87878; color:#fff; }
.nact.del:hover { background:#e89090; }
.node .k { font-size:11px; color:#b89a5a; font-weight:bold; }
.node .t { font-size:13px; margin-top:3px; line-height:1.4; color:#ddd3c2; }
.node.kind-ending { background:#3a2a17; border-color:#e0a850; }
.node.kind-ending .k { color:#f2c463; }
.node.kind-ending .rw { font-size:11.5px; color:#c9a86a; margin-top:4px;
border-top:1px dashed #6a5630; padding-top:4px; }
.node.kind-fight { border-color:#7a4a4a; background:#2a1c1c; }
.node.kind-fight .k { color:#d87878; }
.node.kind-out_ref { border-style:dashed; border-color:#7a7ad8; background:#1d1d2a; }
.node.kind-out_ref .k { color:#9e9ef0; }
.node.kind-choice, .node.kind-choice_once {
background:#1d2840; border:none; padding-left:26px; padding-right:26px;
clip-path: polygon(16px 0, calc(100% - 16px) 0, 100% 50%, calc(100% - 16px) 100%, 16px 100%, 0 50%); }
.node.kind-choice .k, .node.kind-choice_once .k { color:#9ec0f0; }
/* ---- Drawflow 节点(拖拽连线版) ---- */
#drawflow .drawflow-node { background:#262019; border:1.5px solid #4a4030; border-radius:9px;
padding:8px 12px; width:192px; color:#e8e0d4; box-shadow:0 2px 6px rgba(0,0,0,.4); }
#drawflow .drawflow-node:hover { border-color:#e6c878; }
#drawflow .drawflow-node.selected { border-color:#e6c878; box-shadow:0 0 0 2px rgba(230,200,120,.45); }
#drawflow .drawflow_content_node { width:100%; }
/* 顶边框标牌legend边框在文字处断开 */
#drawflow .drawflow_content_node { position:relative; }
.drawflow-node .nlabel { position:absolute; top:-18px; left:50%; transform:translateX(-50%);
background:#161310; padding:0 7px; font-size:11.5px; font-weight:bold; white-space:nowrap; color:#b89a5a; }
.drawflow-node .nbody { padding-top:3px; }
.drawflow-node .who { font-size:11px; color:#b89a5a; margin-bottom:2px; }
.drawflow-node .t { font-size:12.5px; line-height:1.35; color:#ddd3c2; word-break:break-all; }
.drawflow-node .rw { font-size:11px; color:#c9a86a; margin-top:4px;
border-top:1px dashed #6a5630; padding-top:4px; }
#drawflow .kind-ending { background:#3a2a17; border-color:#e0a850; }
#drawflow .kind-ending .nlabel { color:#f2c463; }
#drawflow .kind-fight { border-color:#7a4a4a; background:#2a1c1c; }
#drawflow .kind-fight .nlabel { color:#d87878; }
#drawflow .kind-out_ref { border-style:dashed; border-color:#7a7ad8; background:#1d1d2a; }
#drawflow .kind-out_ref .nlabel { color:#9e9ef0; }
#drawflow .kind-choice, #drawflow .kind-choice_once { background:#1d2840; border-color:#3a527a; }
#drawflow .kind-choice .nlabel, #drawflow .kind-choice_once .nlabel { color:#9ec0f0; }
#drawflow .kind-random .nlabel { color:#c0a0e0; }
/* 端口圆点 */
#drawflow .drawflow-node .input, #drawflow .drawflow-node .output {
background:#e6c878; border:2px solid #161310; width:15px; height:15px; }
#drawflow .drawflow-node .input:hover, #drawflow .drawflow-node .output:hover { background:#f0d68a; }
/* 输出端口纵向行距=选项行距:默认是普通块流,相邻端口 margin-bottom/margin-top 会“外边距折叠”→行距仍是 20px
与 .ch-opt 的 25px 错位(越往下越偏)。把 .outputs 改成 flex 纵向列flex 项目外边距不折叠),
再给每个端口 margin:5px 0 → 端口格子 5+15+5=25px、圆点居中与各选项行逐行平齐任意选项数通用。 */
#drawflow .drawflow-node .outputs { display:flex; flex-direction:column; }
#drawflow .drawflow-node .output { margin:5px 0; }
/* 连线 */
#drawflow .connection .main-path { stroke:#7a96c8; stroke-width:2.4px; }
#drawflow .connection .main-path:hover { stroke:#e6c878; }
/* 开头节点标识 */
#drawflow .drawflow-node.isstart { border-color:#7ad88a; }
.drawflow-node .startflag { color:#7ad88a; font-weight:bold; }
/* 多出口节点每出口一行唯一流内容→垂直居中→与居中黄点逐行平齐top:2px 对齐端口偏移 */
.ch-opts { position:relative; top:2px; }
.ch-opt { height:25px; line-height:25px; text-align:right; font-size:12px; color:#dfe7f2;
overflow:hidden; text-overflow:ellipsis; white-space:nowrap; padding-right:3px; }
/* 战斗节点:左右两栏——左栏战斗描述(填满左侧大空间、可换行、垂直居中),右栏胜/败出口行 */
.drawflow-node .fbody { display:flex; align-items:flex-end; gap:10px; }
.drawflow-node .fbody .fdesc { flex:1; min-width:0; align-self:center; font-size:12px;
color:#e6b8a0; line-height:1.45; text-align:left; word-break:break-all; }
.drawflow-node .fbody .ch-opts { flex:none; }
/* 描述使内容变高,故让胜/败两个黄点贴底部两行,与右栏出口行逐行对齐 */
#drawflow .kind-fight .outputs { align-self:stretch; justify-content:flex-end; }
/* ---- toast ---- */
#toast { position:fixed; left:50%; bottom:38px; transform:translateX(-50%) translateY(10px);
background:#2a2316; color:#f3dca0; border:1px solid #6a5630; border-radius:7px;
padding:10px 18px; font-size:13.5px; box-shadow:0 4px 16px rgba(0,0,0,.5);
opacity:0; pointer-events:none; transition:.25s; z-index:9999; }
#toast.show { opacity:1; transform:translateX(-50%) translateY(0); }
/* ---- form ---- */
.fld { margin:9px 0; }
.fld > label { display:block; font-size:12px; color:#9a8f7e; margin-bottom:3px; }
.fld input, .fld select, .fld textarea {
width:100%; background:#241f18; color:#e8e0d4; border:1px solid #4a4030;
border-radius:4px; padding:6px 8px; font-size:13px; }
.fld textarea { min-height:60px; resize:vertical; font-family:inherit; }
.fld.inline { display:flex; gap:6px; align-items:center; }
.fld.inline > label { margin:0; flex:none; }
.subbox { border:1px solid #3a322a; border-radius:6px; padding:8px; margin:8px 0;
background:#19150f; }
.subbox .hd { display:flex; justify-content:space-between; align-items:center;
font-size:12px; color:#b89a5a; margin-bottom:6px; }
.row2 { display:flex; gap:6px; }
.row2 > * { flex:1; }
.tag-pick { display:flex; flex-wrap:wrap; gap:5px; }
.tag-pick label { font-size:12px; background:#241f18; border:1px solid #4a4030;
padding:3px 8px; border-radius:11px; cursor:pointer; }
.tag-pick input { margin-right:4px; }
.node-id { color:#7a7264; font-size:11px; }
.optdet { border:1px solid #3a322a; border-radius:6px; margin:6px 0; background:#19150f; }
.optdet > summary { cursor:pointer; padding:7px 10px; font-size:12.5px; color:#cdbf9a; user-select:none;
overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.optdet[open] > summary { border-bottom:1px solid #3a322a; color:#e6c878; margin-bottom:4px; }
.optbody { padding:0 10px 8px; }
/* ---- overlays / modals ---- */
.overlay { position:fixed; inset:0; background:rgba(0,0,0,.72); z-index:100;
display:flex; align-items:center; justify-content:center; }
.overlay.hidden { display:none; }
.login-box, .modal { background:#221d16; border:1px solid #4a4030; border-radius:10px;
padding:24px; width:380px; box-shadow:0 8px 30px rgba(0,0,0,.6); }
.modal { width:520px; max-height:82vh; overflow:auto; }
.modal.wide { width:760px; }
.login-box h2, .modal h3 { margin:0 0 12px; color:#e6c878; }
.login-box .hint { color:#9a8f7e; font-size:13px; margin:0 0 14px; }
.login-box input { width:100%; margin-bottom:10px; }
.login-box button { width:100%; }
.err { color:#e08a7a; font-size:13px; margin-top:8px; min-height:18px; }
.modal textarea { width:100%; min-height:160px; background:#19150f; color:#e8e0d4;
border:1px solid #4a4030; border-radius:5px; padding:8px;
font-family:monospace; font-size:12px; }
.modal-actions { margin-top:10px; display:flex; gap:8px; }
.drop { border:1.5px dashed #4a4030; border-radius:8px; padding:22px 12px; text-align:center;
cursor:pointer; color:#bdb29c; transition:border-color .15s, background .15s; }
.drop:hover { border-color:#6a5d40; background:#1c1810; }
.drop.over { border-color:#e6c878; background:#241d10; color:#e6c878; }
.drop p { margin:0; }
.drop .hint { margin-top:6px; font-size:12px; color:#8a8068; }
.filelist { margin-top:8px; }
.fileitem { display:flex; align-items:center; gap:8px; padding:5px 8px; margin-top:5px;
background:#19150f; border:1px solid #3a3326; border-radius:5px; font-size:13px; }
.fileitem .fn { flex:1; color:#e8e0d4; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.fileitem .fsz { color:#8a8068; font-size:12px; }
.fileitem .rm { background:none; border:none; color:#e08a7a; cursor:pointer; font-size:13px; padding:0 4px; }
.import-paste { margin-top:10px; }
.import-paste summary { cursor:pointer; color:#bdb29c; font-size:13px; user-select:none; }
.import-paste textarea { margin-top:8px; min-height:110px; }
.v-err { color:#e08a7a; margin:4px 0; font-size:13px; }
.v-warn { color:#d8c060; margin:4px 0; font-size:13px; }
.v-ok { color:#7ad88a; font-size:14px; }
/* ---- playtest ---- */
#pt-layout { display:flex; gap:14px; }
#pt-flow { flex:1; max-height:62vh; overflow:auto; }
#pt-ledger { width:230px; flex:none; background:#19150f; border:1px solid #3a322a;
border-radius:6px; padding:10px; font-size:13px; align-self:flex-start; }
#pt-ledger h4 { margin:0 0 8px; color:#e6c878; font-size:13px; }
#pt-ledger .lg { margin:4px 0; }
.pt-step { background:#241f18; border-left:3px solid #4a4030; padding:7px 10px;
margin:7px 0; border-radius:4px; font-size:13px; }
.pt-step.spk { color:#cdddf0; }
.pt-step.sys { color:#9a8f7e; font-style:italic; }
.pt-step.end { border-left-color:#e0a850; color:#eccf95; }
.pt-choices button { display:block; width:100%; text-align:left; margin:5px 0; }
.pt-choices button.locked { opacity:.55; }
.pt-q { color:#b89a5a; font-size:12px; margin:8px 0 4px; }
/* ---- 演出预览timeline + 白模舞台)---- */
.modal.huge { width:840px; max-width:94vw; }
.tl-mapinfo { font-size:12px; color:#9a8f7e; margin-left:12px; font-weight:normal; }
#tl-stagewrap { background:#15130d; border:1px solid #3a322a; border-radius:6px; overflow:hidden; line-height:0; }
#tl-stage { width:100%; height:auto; display:block; }
#tl-controls { display:flex; align-items:center; gap:10px; margin:10px 0 6px; }
#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-tracks { position:relative; overflow-x:auto; overflow-y:hidden; max-height:34vh;
background:#19150f; border:1px solid #3a322a; border-radius:6px; padding-top:20px; }
.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; }
.tl-lane { position:relative; border-bottom:1px solid #211c15; }
.tl-lane-label { position:sticky; left:0; z-index:3; display:inline-block; min-width:54px;
padding:2px 6px; font-size:11px; color:#b89a5a; background:#211c15;
border-right:1px solid #2a2419; height:100%; box-sizing:border-box; line-height:26px; }
.tl-clip { position:absolute; top:3px; height:22px; line-height:22px; padding:0 5px;
font-size:11px; color:#1a1710; border-radius:3px; overflow:hidden; white-space:nowrap;
text-overflow:ellipsis; cursor:pointer; box-sizing:border-box; box-shadow:0 1px 2px rgba(0,0,0,.4); }
.tl-clip.active { outline:2px solid #fff; outline-offset:-1px; z-index:2; }
.tl-clip.k-dialogue { background:#7ec8e3; }
.tl-clip.k-move { background:#e6c878; }
.tl-clip.k-anim { background:#9ee37e; }
.tl-clip.k-camera { background:#c89ee3; }
.tl-clip.k-branch { background:#9ec0f0; }
.tl-clip.k-fight { background:#e38f7e; }
.tl-clip.k-reward { background:#e0c060; }
.tl-clip.k-ending { background:#e0a850; }
.tl-clip.k-stop { background:#d87878; color:#fff; }
.tl-playhead { position:absolute; top:0; bottom:0; width:2px; background:#ff5a4a;
pointer-events:none; z-index:4; box-shadow:0 0 4px rgba(255,90,74,.7); }