diff --git a/sundynix-desktop/frontend/src/assets/office/room.webp b/sundynix-desktop/frontend/src/assets/office/room.webp new file mode 100644 index 0000000..4f0f137 Binary files /dev/null and b/sundynix-desktop/frontend/src/assets/office/room.webp differ diff --git a/sundynix-desktop/frontend/src/components/OfficeView.tsx b/sundynix-desktop/frontend/src/components/OfficeView.tsx index 5ddc268..174ebcf 100644 --- a/sundynix-desktop/frontend/src/components/OfficeView.tsx +++ b/sundynix-desktop/frontend/src/components/OfficeView.tsx @@ -1,6 +1,7 @@ import { useMemo, type CSSProperties } from "react"; import { deriveTeam, teamNow, type RunPhase, type TeamSeat } from "../lib/run"; import type { ExecEvent } from "../lib/api"; +import roomSprite from "../assets/office/room.webp"; import leadSprite from "../assets/office/lead.webp"; import deskSprite from "../assets/office/desk.webp"; import a0 from "../assets/office/a0.webp"; @@ -22,17 +23,15 @@ const ACTORS = [a0, a1, a2, a3, a4]; // 复盘时按事件时间戳算出各自的出场/交付时刻,压进 PLAY_MS 内当一出戏放。 // 动效=状态编码,不是装饰;并守 prefers-reduced-motion。 -const VB_W = 720; -const VB_H = 510; -const FLOOR_Y = 200; +// 场景尺寸 = 房间贴图的比例(2760×1504 → 1.835),让背景零拉伸: +// 拉伸 30% 的话墙上那个挂钟会变成椭圆,一眼假。宁可改场景去迁就图。 +const VB_W = 940; +const VB_H = 512; +const FLOOR_Y = 242; // 贴图里量的地平线在 47.3% const OUTLINE = "#2f2a26"; const SKIN = "#f7d9bd"; const PANTS = "#4a5568"; -const WALL = "#f3ede4"; -const FLOOR = "#ddd0be"; -const DESK_TOP = "#ffffff"; -const DESK_FRONT = "#f1ebe2"; const RUN = "#7c5cf6"; const OK = "#34d399"; @@ -43,16 +42,16 @@ const SHIRTS = ["#7c5cf6", "#38bdf8", "#34d399", "#fbbf24", "#fb7185", "#a78bfa" const HAIRS = ["#3f3229", "#6b4a2f", "#c8913f", "#2e3f52", "#8c4a4a", "#4a3a6b", "#5c5148", "#7a4a2a"]; // 门口:出场点。角色从这里走向自己的工位。 -const DOOR_X = 84; -const DOOR_Y = 198; +const DOOR_X = 279; // 贴图里那扇门的中心(29.7%)——角色从画上的真门里走出来 +const DOOR_Y = 250; const DOOR_SCALE = 0.5; // 协调者机位;递简报时专家走到它桌子两侧。 -const LEAD_X = 360; -const LEAD_Y = 232; +const LEAD_X = 470; +const LEAD_Y = 268; // 下限 242(地平线,再高就飘到墙上)、上限 276(再低撞到工位气泡) const LEAD_SCALE = 0.62; const HANDOFF_DX = 86; -const HANDOFF_Y = 250; +const HANDOFF_Y = 288; const HANDOFF_SCALE = 0.66; const ENTER_MS = 950; @@ -271,10 +270,10 @@ function Deskette({ name, sub, tone, glow, w = 200 }: { name: string; sub: strin 掉到挡板外会骑在桌子下边线上、被切成半截。 */} - - {fitText(name, (w - 62) / 16)} + + {fitText(name, (w - 62) / 20)} - + {sub} @@ -376,40 +375,10 @@ function Actor({ cue, reduced, hasLead }: { cue: Cue; reduced: boolean; hasLead: ); } -// 场景道具 +// 场景道具:整间房是一张贴图(AI 生成),门/窗/挂钟/绿植都在图里。 +// 原来这里是手绘 SVG 的墙地门窗,被立绘一衬托就露怯,整段换掉。 function Room() { - return ( - - - - - {/* 地毯 */} - - {/* 门:专家从这里进场 */} - - - - - - {/* 挂钟 */} - - - - - {/* 窗 */} - - - - - - {/* 绿植 */} - - - - - - - ); + return ; } export function OfficeView({ events, output, phase }: { events: ExecEvent[]; output?: string; phase?: RunPhase }) { @@ -426,7 +395,7 @@ export function OfficeView({ events, output, phase }: { events: ExecEvent[]; out // 排位:≤5 人一排(前排),否则拆两排,后排小、前排大 —— 用缩放造纵深。 // 两排时后排不出头顶气泡:气泡很高,会顶到协调者的桌子;状态仍由显示器辉光和铭牌圆点表达。 const rows: TeamSeat[][] = seats.length <= 5 ? [seats] : [seats.slice(0, Math.ceil(seats.length / 2)), seats.slice(Math.ceil(seats.length / 2))]; - const rowCfg = rows.length === 1 ? [{ y: 452, s: 1 }] : [{ y: 330, s: 0.58 }, { y: 460, s: 0.8 }]; + const rowCfg = rows.length === 1 ? [{ y: 470, s: 1 }] : [{ y: 358, s: 0.62 }, { y: 478, s: 0.85 }]; // 档期:直播时事件到哪演到哪(延迟一律 0);复盘时按时间戳排,整段压进 PLAY_MS。 const rate = streaming ? 0 : Math.min(1, PLAY_MS / span); @@ -524,11 +493,11 @@ export function OfficeView({ events, output, phase }: { events: ExecEvent[]; out {/* 白板:协调者的思考/综合过程 */} - - - + + + - + {lead ? streaming ? "综合中…" @@ -543,7 +512,7 @@ export function OfficeView({ events, output, phase }: { events: ExecEvent[]; out