Skip to content

Simulator

The simulator (simulator/main.tsx) renders the entire wall — controller + every screen — inside one browser window using iframes.

The wall is auto-fit to the viewport, preserving aspect ratio:

const wallAspect = settings.wall.width / settings.wall.height
const availAspect = availW / availH
const wallPx =
availAspect > wallAspect
? { w: availH * wallAspect, h: availH } // viewport wider — fit by height
: { w: availW, h: availW / wallAspect } // taller — fit by width
const ppcm = wallPx.w / settings.wall.width

ppcm (pixels per centimetre) drives every iframe’s size: screen.size.width * ppcm.

settings.simulator.minWidth / minHeight gate the simulator. Below that, a notice is shown with current vs required dimensions.

Each iframe is wrapped in a DraggableScreen component that:

  • Positions itself absolutely on the wall using top: ${pos.top}%, left: ${pos.left}%.
  • Sizes itself in pixels: width = cm × ppcm.
  • In edit mode, overlays a transparent div on top of the iframe to capture pointer events for dragging.

Toggle with the Edit button. While unlocked:

  • Iframes get a dashed blue outline.
  • Pointer over iframe → cursor-grab. Drag → cursor-grabbing.
  • A small live-position badge shows top% · left% above the dragged iframe.
  • Pointer up commits the new position to setSettings(...).

Drag math:

const dxPct = (dx / wallPx.w) * 100
const dyPct = (dy / wallPx.h) * 100

Positions are clamped to [0, 100] so iframes can’t drift off the wall.

Click Settings in the floating controls to open the slide-in panel. See Settings for the data model.