RWD:直版改直向堆疊讓辦公室區域最大化

摘要:
直版/窄螢幕時辦公室改為滿寬顯示並鎖定 1200:700 比例,側邊面板移到下方;
另新增面板收合按鈕,桌面版也能全畫面看辦公室。

根本原因:
原本任何螢幕都是「辦公室 + 300px 側欄」左右排,SVG 以 meet 等比縮放,
直版視窗寬度受限時上下大量留白,辦公室被壓得很小。

影響:
直版(手機/平板)辦公室寬度從約一半提升到 100%;
窄橫版維持左右排但側欄縮為 232px;桌面版行為不變,多一顆 ⊞ 收合鈕。

修法:
- styles.css:orientation: portrait 時 main-row 改 column,
  floorplan-wrap 用 aspect-ratio 1200/7 00 撐滿寬度,不再靠 flex 留白;
  ≤900px 壓縮 header(隱藏圖例/副標、按鈕縮小、允許換行)
- store 新增 panelOpen/togglePanel,App 據此條件渲染 SidePanel

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 13:40:38 +08:00
co-authored by Claude Fable 5
parent f935a2ef2d
commit 1dc4920b32
4 changed files with 95 additions and 7 deletions
+3 -2
View File
@@ -7,6 +7,7 @@ import { useOfficeStore } from "./store/office-store";
export default function App() {
const theme = useOfficeStore((s) => s.theme);
const panelOpen = useOfficeStore((s) => s.panelOpen);
useEffect(() => {
startRuntime();
@@ -15,9 +16,9 @@ export default function App() {
return (
<div className={`app ${theme}`}>
<HeaderBar />
<div className="main-row">
<div className={`main-row ${panelOpen ? "" : "panel-closed"}`}>
<FloorPlan />
<SidePanel />
{panelOpen && <SidePanel />}
</div>
</div>
);
+8
View File
@@ -14,6 +14,7 @@ export function HeaderBar() {
const setTheme = useOfficeStore((s) => s.setTheme);
const mode = useOfficeStore((s) => s.mode);
const agentCount = useOfficeStore((s) => s.agents.size);
const panelOpen = useOfficeStore((s) => s.panelOpen);
const isLive = mode === "live";
@@ -65,6 +66,13 @@ export function HeaderBar() {
<button className="btn" onClick={() => setTheme(theme === "light" ? "dark" : "light")} title="切換主題">
{theme === "light" ? "🌙" : "☀️"}
</button>
<button
className="btn"
onClick={() => useOfficeStore.getState().togglePanel()}
title={panelOpen ? "收合面板,放大辦公室" : "展開面板"}
>
{panelOpen ? "⊞" : "⊟"}
</button>
</div>
</header>
);
+5
View File
@@ -24,6 +24,8 @@ interface OfficeState {
speed: number;
/** data source: scripted simulation or live Claude Code transcripts */
mode: "sim" | "live";
/** side panel visibility (collapse to maximise the office view) */
panelOpen: boolean;
/** simulation clock, seconds */
clock: number;
@@ -32,6 +34,7 @@ interface OfficeState {
setPaused: (p: boolean) => void;
setSpeed: (s: number) => void;
setMode: (m: "sim" | "live") => void;
togglePanel: () => void;
clearWorld: () => void;
addEvent: (icon: string, text: string) => void;
@@ -78,6 +81,7 @@ export const useOfficeStore = create<OfficeState>()((set, get) => ({
paused: false,
speed: 1,
mode: "sim",
panelOpen: true,
clock: 0,
selectAgent: (id) => set({ selectedAgentId: id }),
@@ -85,6 +89,7 @@ export const useOfficeStore = create<OfficeState>()((set, get) => ({
setPaused: (p) => set({ paused: p }),
setSpeed: (s) => set({ speed: s }),
setMode: (m) => set({ mode: m }),
togglePanel: () => set((state) => ({ panelOpen: !state.panelOpen })),
clearWorld: () =>
set({ agents: new Map(), links: [], meetings: [], events: [], selectedAgentId: null }),
+79 -5
View File
@@ -557,12 +557,86 @@
}
}
/* narrow screens: hide panel */
@media (max-width: 900px) {
.side-panel {
display: none;
/* ── RWD ──
Portrait / narrow screens: stack vertically. The office locks to its
1200:700 aspect ratio at full width (maximum size, no letterboxing),
and the panel moves below it. */
@media (orientation: portrait) {
.app {
height: 100dvh;
}
.header-legend {
.main-row {
flex-direction: column;
overflow-y: auto;
}
.floorplan-wrap {
flex: none;
width: 100%;
aspect-ratio: 1200 / 700;
padding: 2px;
}
.side-panel {
width: 100%;
flex: 1 0 auto;
border-left: none;
border-top: 1px solid var(--border);
}
.event-feed {
max-height: 40vh;
}
}
/* Narrow landscape: keep the row, slim the panel */
@media (max-width: 900px) and (orientation: landscape) {
.side-panel {
width: 232px;
}
}
/* Compact chrome on any narrow screen */
@media (max-width: 900px) {
.header {
gap: 10px;
padding: 6px 10px;
flex-wrap: wrap;
}
.header-legend,
.header-brand p,
.agent-count {
display: none;
}
.header-mark {
font-size: 22px;
}
.header-brand h1 {
font-size: 17px;
}
.header-controls {
margin-left: auto;
gap: 5px;
}
.btn {
padding: 5px 9px;
font-size: 12px;
}
}
/* Very tight screens: shave header further */
@media (max-width: 480px) {
.header-brand h1 {
font-size: 15px;
}
.btn {
padding: 4px 7px;
font-size: 11px;
}
}