diff --git a/README.md b/README.md
index 9acf43ca..07b8ecd3 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,8 @@ A browser-based tool "one step before a game engine": create **assets** (scenes,
sprites), place them into scenes, tweak their properties in an inspector and press **Play** โ
all in one program, all drawn inside a single WebGL canvas.
+
+
Live: ยท Widget playground:
**The documentation is the wiki:** โ every concept,
@@ -40,7 +42,7 @@ npm run build # writes dist/ and regenerates index.html; the folder is wh
The browser suite and the three example games need a machine with a GPU:
```bash
-node tools/seed-e2e.mjs && node tools/e2e.mjs # the editor, 1147 checks (GM_STEPS=20 for one step)
+node tools/seed-e2e.mjs && node tools/e2e.mjs # the editor, 1148 checks (GM_STEPS=20 for one step)
# every tool signs in first โ see the Accounts page
node tools/seed-jumprun.mjs && node tools/smoke-jumprun.mjs
node tools/seed-tetris.mjs && node tools/smoke-tetris.mjs
diff --git a/screenshot.png b/screenshot.png
new file mode 100644
index 00000000..f3191383
Binary files /dev/null and b/screenshot.png differ
diff --git a/src/editor/panels/Toolbar.ts b/src/editor/panels/Toolbar.ts
index 30015827..01a65732 100644
--- a/src/editor/panels/Toolbar.ts
+++ b/src/editor/panels/Toolbar.ts
@@ -148,6 +148,24 @@ export class Toolbar extends HStack {
this.status.setStyle(state.saveStatus === 'error' || state.saveStatus === 'conflict' ? 'body' : 'small');
}
+ /**
+ * The row lays itself out first, then the two run buttons are moved onto the middle of the bar.
+ * The row alone cannot put them there: the two spacers around them share the free space with the
+ * flexible notice, so each gets a third of it and the pair ends up left of centre. Middle means
+ * the middle of the bar here, not the middle of what the row had left over. It never travels
+ * past the notice, so a narrow window pushes the pair back rather than one label over another.
+ */
+ override layout(): void {
+ super.layout();
+ const width = this.pauseButton.x + this.pauseButton.w - this.playButton.x;
+ const leftMost = this.redoButton.x + this.redoButton.w + this.gap;
+ const rightMost = this.notice.x - this.gap - width;
+ const x = Math.round(Math.max(leftMost, Math.min((this.w - width) / 2, rightMost)));
+ const shift = x - this.playButton.x;
+ this.playButton.position.set(this.playButton.x + shift, this.playButton.y);
+ this.pauseButton.position.set(this.pauseButton.x + shift, this.pauseButton.y);
+ }
+
override redraw(): void {
this.bg.clear();
this.bg.rect(0, 0, this.w, this.h).fill(Theme.colors.panelHeader);
diff --git a/src/editor/panels/icons/redo.svg b/src/editor/panels/icons/redo.svg
index 9d9d33fb..56b4ccb4 100644
--- a/src/editor/panels/icons/redo.svg
+++ b/src/editor/panels/icons/redo.svg
@@ -1,5 +1,5 @@
diff --git a/src/editor/panels/icons/undo.svg b/src/editor/panels/icons/undo.svg
index 045e92c2..99d3d6d1 100644
--- a/src/editor/panels/icons/undo.svg
+++ b/src/editor/panels/icons/undo.svg
@@ -1,6 +1,8 @@
diff --git a/tools/e2e/46-topbar.mjs b/tools/e2e/46-topbar.mjs
index fc683e4a..f22e50fa 100644
--- a/tools/e2e/46-topbar.mjs
+++ b/tools/e2e/46-topbar.mjs
@@ -34,6 +34,17 @@ export async function topbar(t) {
buttons.pauseEnabled === false,
JSON.stringify(buttons),
);
+ // The pair is centred on the bar itself. The row cannot do that on its own: the spacers around
+ // the two buttons share the free space with the flexible notice and so get a third of it each,
+ // which used to leave the pair well left of the middle.
+ const play = await json('__gm.toolbarAt("play")');
+ const pause = await json('__gm.toolbarAt("pause")');
+ const barCentre = await json('window.innerWidth / 2');
+ check(
+ 'play and pause sit on the middle of the bar, not on the middle of what is left over',
+ Math.abs((play.x + pause.x) / 2 - barCentre) <= 2,
+ JSON.stringify({ middle: (play.x + pause.x) / 2, barCentre }),
+ );
await shot('46-topbar');
// 46b. The one menu. Four entries, and the separator between what opens a project and what