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 (
-
);
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;
}
}