- **The camera wall is a plan, like the two other boards.** Cameras are placed by hand on a surface you pan and zoom instead of flowing in a grid, and they are RESIZED FREELY by a grip in the corner — the ½× / 1× / 2× presets are gone, three answers to a question the user can now answer exactly. A camera's width travels with its position (`camWallPlan: {x, y, z, w}`, a field of its own since the printer board's camera widget already owns `camPlan` and the two are placed independently); only the width is stored, because the picture is 16:9 and the head is fixed, so one number describes the card. It has an **Arrange mode** like the other two, with the storage plan's own 24 px mesh scaled by the zoom: a card's HEAD is a handle in both modes — it is the part that is not the picture, so taking hold of it can never be mistaken for a click on the video, and no separate grip button is needed; arranging then extends the handle to the whole card, keeps the head visible and gives the LEFT BUTTON to the rubber band — sweeping and panning both start on empty surface, so one has to yield, and arranging is precisely when you mean to pick cards rather than travel; the wheel button pans in both modes, so the view is never trapped. Outside it the wall is a wall — pictures, heads on hover, and a small handle for the occasional move. Its plan bar is the other plans' bar to the character — same classes, same order, same numeric field with its own `%` beside it. Written differently it LOOKED different: a text field carrying the percent inside it takes the default width, which is why this one sprawled while theirs stayed a neat pill. Three views that behave alike have to be built alike. Its zoom is the other plans' zoom, down to the arithmetic: the same bounds, the same round-ten ladder, and the point under the cursor held still — a zoom that lets the surface slide under the pointer reads as something having MOVED, which on a view whose whole purpose is where things are is the wrong impression entirely. The wheel zooms with no modifier, as it does everywhere else, and ignores the stray notches a held-down wheel produces while panning. Moving a camera no longer opens its machine's side panel: the click that ends a drag lands on the card, and a panel sliding open over the wall you are laying out is the opposite of what you came for. Arranging suppresses it outright — the whole card is a handle there — and outside it the guard goes up the instant the gesture becomes a move, not when it ends: raised at the end it was raised after the writes were awaited, and the click following a release fires long before a round-trip to the database resolves, so it was always too late. The mesh appears the moment anything is taken or resized, in or out of Arrange: outside it nothing else says a gesture has become an edit — the card simply starts following the pointer — and a grid arriving under your hand explains the snapping before it happens. Dragging uses the storage plan's own magnetism and its guides: a card snaps flush to a neighbour's edge, or lines its edges and centres up with one, and a thin line says WHY it clicked into place — without it the magnetism reads as the card resisting. In a group the card actually grabbed is the one the magnetism speaks for and the rest follow at their own offsets, so the set keeps its spacing exactly. A card taken in the hand comes to the FRONT, and dropped last means dropped on top for good — the depth is written inline by the layout, which no stylesheet rule can outrank, so the lift is done on the element itself and restored from the record on release. A swept selection is ringed in the accent, 2 px at the same offset as the storage and printer boards — a selection has to look identical wherever it is made. Its states are written as one ordered block carrying the view's id, because the plain border arranging draws also carries it: at lower weight the selection was silently overruled, and hovering a selected card overruled it again. It reuses the shared primitives — the rubber band, the panning, the free-spot search — so the three views cannot drift apart in feel. A camera sent fullscreen now fills the SCREEN. `position: fixed` only ever covered the app window, so the title bar, the menu bar and the dock stayed in front of a view whose entire point is to fill the screen; the card is handed to the Fullscreen API instead, which puts it in the top layer at screen size and MOVES NO ELEMENT, so the stream inside it is not interrupted. The window-filling card remains as the fallback when the platform refuses. Leaving is believed rather than assumed: Escape, the green button and the system all end fullscreen behind the app's back, so the card's state is written from what the browser actually did — otherwise the key would go on claiming fullscreen over a card that had come back down. Widths are applied in their own pass before any position is computed: the free-spot search measures a card to find room for it, and a card not yet given its width measures whatever the stylesheet last left it, which dealt every camera a slot the wrong size and piled them on top of each other. ONE RULE GOVERNS IT: a card holds a live stream, so it is never recreated, only moved — every path positions existing elements, the drag writes the element and only the release touches the record, and the wall's existing patch mode is what keeps a size or state change from rebuilding anything. The drag-to-reorder went with the grid it served; `camSortIndex` now only orders cameras that have never been placed, on their first render. **A placed camera keeps its place when its machine goes quiet** — drawn at the same spot and the same size, dimmed and empty, and still movable and resizable while the printer is off. A wall that dropped a card the moment a machine slept destroyed the arrangement it exists to hold, and the hole it left was indistinguishable from a camera never placed. Only a camera that has never been placed is absent: there is nothing of it to preserve. Fullscreen still leaves the plan entirely: the layout stops writing inline geometry on a card while it is up there and clears what it wrote, because position, size and depth are written inline and no stylesheet can outrank that — a card sent fullscreen simply grew inside the surface instead of covering the window. The detached window is untouched — `renderer/inventory.js`, `renderer/css/40-printers.css`
0 commit comments