diff --git a/src/App.tsx b/src/App.tsx index 28f068a..b9083ca 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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 (
-
+
- + {panelOpen && }
); diff --git a/src/components/HeaderBar.tsx b/src/components/HeaderBar.tsx index 020f5c7..d99c76b 100644 --- a/src/components/HeaderBar.tsx +++ b/src/components/HeaderBar.tsx @@ -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() { +
); diff --git a/src/store/office-store.ts b/src/store/office-store.ts index 063d768..ee5075a 100644 --- a/src/store/office-store.ts +++ b/src/store/office-store.ts @@ -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()((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()((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 }), diff --git a/src/styles.css b/src/styles.css index cea8791..7fe3e14 100644 --- a/src/styles.css +++ b/src/styles.css @@ -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; } }