diff --git a/web/src/app/pattern-lab/LabPanels.module.css b/web/src/app/pattern-lab/LabPanels.module.css
index f1ceff358..21d6bccb1 100644
--- a/web/src/app/pattern-lab/LabPanels.module.css
+++ b/web/src/app/pattern-lab/LabPanels.module.css
@@ -499,6 +499,14 @@
.layerControlRow select {
min-height: 44px;
}
+
+ /* A finger has no Alt key, right button or middle button. On a 390×844
+ phone this line wrapped the Pixel status bar to 51px, which left the
+ picture a 372×151 viewport and fitted it at 1×. Without it the viewport
+ is 372×177, and 2× needs 168. */
+ .pixelMouseHint {
+ display: none;
+ }
}
/* ── Pixel editor panel ── */
diff --git a/web/src/app/pattern-lab/labCss.test.ts b/web/src/app/pattern-lab/labCss.test.ts
index de61262e5..9accfc178 100644
--- a/web/src/app/pattern-lab/labCss.test.ts
+++ b/web/src/app/pattern-lab/labCss.test.ts
@@ -6,7 +6,8 @@
// and 44×44px after, in a header 59px tall both times. On a 390×844 touch
// screen the tab strip under Preview was 736px for 333px of room, and every
// tab carried a 27×27px ×. Without the × it is 520px and Knobs shows whole.
-// On the same screen Del was 37×28px, the layer eye 20×17px and Push 40×22px.
+// On the same screen Del was 37×28px, the layer eye 20×17px and Push 40×22px,
+// and the Pixel status bar was 51px, two lines with the mouse gestures.
import { readFileSync } from "node:fs";
import { createRequire } from "node:module";
@@ -168,3 +169,14 @@ describe("Layers and Knobs on a touch screen", () => {
expect(knobs.lastIndexOf("@media (hover: none) {")).toBeGreaterThan(knobs.indexOf("@media (max-width: 980px) {"));
});
});
+
+describe("Pixel status line on a touch screen", () => {
+ const dock = css("LabPanels.module.css");
+ const pixel = css("panels/PixelPanel.tsx");
+
+ it("drops the mouse gestures, and nothing else", () => {
+ expect(mediaRule(dock, "(hover: none)", ".pixelMouseHint")).toMatch(/display: none;/);
+ expect(pixel).toContain("alt-click picks · right-drag erases · middle-drag pans");
+ expect(mediaRule(dock, "(hover: none)", ".pixelStatus")).toBe("");
+ });
+});
diff --git a/web/src/app/pattern-lab/panels/PixelPanel.test.tsx b/web/src/app/pattern-lab/panels/PixelPanel.test.tsx
index e6514806a..dedbb95f3 100644
--- a/web/src/app/pattern-lab/panels/PixelPanel.test.tsx
+++ b/web/src/app/pattern-lab/panels/PixelPanel.test.tsx
@@ -5,6 +5,7 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { act, createEvent, fireEvent, render, screen } from "@testing-library/react";
import PixelPanel from "./PixelPanel";
+import { zoomToFit } from "./pixel/usePixelViewport";
import { useLabStore } from "@/lib/lab/store";
import type { PixelLayer } from "@/lib/lab/types";
@@ -285,13 +286,20 @@ describe("PixelPanel", () => {
};
it("on a phone the picture opens fitted, all of it in view", () => {
- // the Pixel viewport on a 390×844 phone, measured in Chromium
- panelSize(372, 151);
+ // the Pixel viewport on a 390×844 touch phone, measured in Chromium
+ panelSize(372, 177);
mount();
- expect(screen.getByText("zoom 1×")).toBeTruthy();
+ expect(screen.getByText("zoom 2×")).toBeTruthy();
// and the zoom stays the user's to change
fireEvent.click(screen.getByLabelText("Zoom in"));
- expect(screen.getByText("zoom 2×")).toBeTruthy();
+ expect(screen.getByText("zoom 3×")).toBeTruthy();
+ });
+
+ it("the mouse gestures sit apart in the status line, where a touch screen can drop them", () => {
+ mount();
+ const hint = screen.getByText("alt-click picks · right-drag erases · middle-drag pans");
+ expect(hint.className).toBe("pixelMouseHint");
+ expect(hint.parentElement?.textContent).toContain("zoom 6×");
});
it("a desktop panel narrower than the picture at 6× opens fitted too", () => {
@@ -440,3 +448,13 @@ describe("PixelPanel", () => {
expect(screen.queryByTitle(/Stamp the selection down/)).toBeNull();
});
});
+
+describe("zoomToFit", () => {
+ it("fits 128×64 at 2× from a 168px-tall viewport, 64 × 2 and 40 of padding", () => {
+ expect(zoomToFit(372, 167, 128, 64)).toBe(1);
+ expect(zoomToFit(372, 168, 128, 64)).toBe(2);
+ // a 390×844 phone, with and without the mouse gestures under the picture
+ expect(zoomToFit(372, 151, 128, 64)).toBe(1);
+ expect(zoomToFit(372, 177, 128, 64)).toBe(2);
+ });
+});
diff --git a/web/src/app/pattern-lab/panels/PixelPanel.tsx b/web/src/app/pattern-lab/panels/PixelPanel.tsx
index ec6f8708b..9a2430d29 100644
--- a/web/src/app/pattern-lab/panels/PixelPanel.tsx
+++ b/web/src/app/pattern-lab/panels/PixelPanel.tsx
@@ -222,7 +222,7 @@ export default function PixelPanel() {
)}
- alt-click picks · right-drag erases · middle-drag pans
+ alt-click picks · right-drag erases · middle-drag pans
{importOpen && (
diff --git a/web/src/app/pattern-lab/panels/pixel/usePixelViewport.ts b/web/src/app/pattern-lab/panels/pixel/usePixelViewport.ts
index f3d882e44..9c9bacff3 100644
--- a/web/src/app/pattern-lab/panels/pixel/usePixelViewport.ts
+++ b/web/src/app/pattern-lab/panels/pixel/usePixelViewport.ts
@@ -15,7 +15,7 @@ export const ZOOM_MAX = 32;
/**
* The largest whole zoom that shows the picture entire inside a viewport of
* this size: 16 px of padding a side and 4 px of air. Never below 1×. For
- * 128×64 that is 1× in a 390×844 phone's 372×151 viewport, and 4× in the
+ * 128×64 that is 2× in a 390×844 phone's 372×177 viewport, and 4× in the
* 649×648 one at 1440×900.
*/
export function zoomToFit(viewportWidth: number, viewportHeight: number, width: number, height: number) {