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() {
|
export default function App() {
|
||||||
const theme = useOfficeStore((s) => s.theme);
|
const theme = useOfficeStore((s) => s.theme);
|
||||||
|
const panelOpen = useOfficeStore((s) => s.panelOpen);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
startRuntime();
|
startRuntime();
|
||||||
@@ -15,9 +16,9 @@ export default function App() {
|
|||||||
return (
|
return (
|
||||||
<div className={`app ${theme}`}>
|
<div className={`app ${theme}`}>
|
||||||
<HeaderBar />
|
<HeaderBar />
|
||||||
<div className="main-row">
|
<div className={`main-row ${panelOpen ? "" : "panel-closed"}`}>
|
||||||
<FloorPlan />
|
<FloorPlan />
|
||||||
<SidePanel />
|
{panelOpen && <SidePanel />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export function HeaderBar() {
|
|||||||
const setTheme = useOfficeStore((s) => s.setTheme);
|
const setTheme = useOfficeStore((s) => s.setTheme);
|
||||||
const mode = useOfficeStore((s) => s.mode);
|
const mode = useOfficeStore((s) => s.mode);
|
||||||
const agentCount = useOfficeStore((s) => s.agents.size);
|
const agentCount = useOfficeStore((s) => s.agents.size);
|
||||||
|
const panelOpen = useOfficeStore((s) => s.panelOpen);
|
||||||
|
|
||||||
const isLive = mode === "live";
|
const isLive = mode === "live";
|
||||||
|
|
||||||
@@ -65,6 +66,13 @@ export function HeaderBar() {
|
|||||||
<button className="btn" onClick={() => setTheme(theme === "light" ? "dark" : "light")} title="切換主題">
|
<button className="btn" onClick={() => setTheme(theme === "light" ? "dark" : "light")} title="切換主題">
|
||||||
{theme === "light" ? "🌙" : "☀️"}
|
{theme === "light" ? "🌙" : "☀️"}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn"
|
||||||
|
onClick={() => useOfficeStore.getState().togglePanel()}
|
||||||
|
title={panelOpen ? "收合面板,放大辦公室" : "展開面板"}
|
||||||
|
>
|
||||||
|
{panelOpen ? "⊞" : "⊟"}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ interface OfficeState {
|
|||||||
speed: number;
|
speed: number;
|
||||||
/** data source: scripted simulation or live Claude Code transcripts */
|
/** data source: scripted simulation or live Claude Code transcripts */
|
||||||
mode: "sim" | "live";
|
mode: "sim" | "live";
|
||||||
|
/** side panel visibility (collapse to maximise the office view) */
|
||||||
|
panelOpen: boolean;
|
||||||
/** simulation clock, seconds */
|
/** simulation clock, seconds */
|
||||||
clock: number;
|
clock: number;
|
||||||
|
|
||||||
@@ -32,6 +34,7 @@ interface OfficeState {
|
|||||||
setPaused: (p: boolean) => void;
|
setPaused: (p: boolean) => void;
|
||||||
setSpeed: (s: number) => void;
|
setSpeed: (s: number) => void;
|
||||||
setMode: (m: "sim" | "live") => void;
|
setMode: (m: "sim" | "live") => void;
|
||||||
|
togglePanel: () => void;
|
||||||
clearWorld: () => void;
|
clearWorld: () => void;
|
||||||
|
|
||||||
addEvent: (icon: string, text: string) => void;
|
addEvent: (icon: string, text: string) => void;
|
||||||
@@ -78,6 +81,7 @@ export const useOfficeStore = create<OfficeState>()((set, get) => ({
|
|||||||
paused: false,
|
paused: false,
|
||||||
speed: 1,
|
speed: 1,
|
||||||
mode: "sim",
|
mode: "sim",
|
||||||
|
panelOpen: true,
|
||||||
clock: 0,
|
clock: 0,
|
||||||
|
|
||||||
selectAgent: (id) => set({ selectedAgentId: id }),
|
selectAgent: (id) => set({ selectedAgentId: id }),
|
||||||
@@ -85,6 +89,7 @@ export const useOfficeStore = create<OfficeState>()((set, get) => ({
|
|||||||
setPaused: (p) => set({ paused: p }),
|
setPaused: (p) => set({ paused: p }),
|
||||||
setSpeed: (s) => set({ speed: s }),
|
setSpeed: (s) => set({ speed: s }),
|
||||||
setMode: (m) => set({ mode: m }),
|
setMode: (m) => set({ mode: m }),
|
||||||
|
togglePanel: () => set((state) => ({ panelOpen: !state.panelOpen })),
|
||||||
clearWorld: () =>
|
clearWorld: () =>
|
||||||
set({ agents: new Map(), links: [], meetings: [], events: [], selectedAgentId: null }),
|
set({ agents: new Map(), links: [], meetings: [], events: [], selectedAgentId: null }),
|
||||||
|
|
||||||
|
|||||||
+79
-5
@@ -557,12 +557,86 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* narrow screens: hide panel */
|
/* ── RWD ──
|
||||||
@media (max-width: 900px) {
|
Portrait / narrow screens: stack vertically. The office locks to its
|
||||||
.side-panel {
|
1200:700 aspect ratio at full width (maximum size, no letterboxing),
|
||||||
display: none;
|
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;
|
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