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:
+3
-2
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user