diff --git a/src/components/MediaMuteAndSwitchButton.module.css b/src/components/MediaMuteAndSwitchButton.module.css
index 91282cea4..a8b313b74 100644
--- a/src/components/MediaMuteAndSwitchButton.module.css
+++ b/src/components/MediaMuteAndSwitchButton.module.css
@@ -436,9 +436,12 @@ Please see LICENSE in the repository root for full details.
* frame's corner, because a video paints on a layer of its own and would
* otherwise draw over the outline the menu is framed with.
*
- * Sixteen by nine and never taller than its budget, cropped rather than
- * letterboxed: a face cut at the edge reads as a self-view, a face in bars
- * reads as a broken one.
+ * A fixed height rather than a ratio: sixteen by nine at the menu's narrowest,
+ * and wider as a long device name widens the menu, cropped rather than
+ * letterboxed — a face cut at the edge reads as a self-view, a face in bars
+ * reads as a broken one. Not aspect-ratio with a height cap: once the cap
+ * holds, Safari keeps the ratio by narrowing the box instead of the picture,
+ * and the preview stops short of the menu's right edge.
*/
.selfPreview {
position: relative;
@@ -450,8 +453,7 @@ Please see LICENSE in the repository root for full details.
);
margin-inline: var(--cpd-border-width-1);
margin-block-end: var(--cpd-space-2x);
- aspect-ratio: 16 / 9;
- max-block-size: 176px;
+ block-size: 176px;
overflow: hidden;
border-start-start-radius: calc(
var(--cpd-space-3x) - var(--cpd-border-width-1)
diff --git a/src/components/MediaMuteAndSwitchButton.stories.tsx b/src/components/MediaMuteAndSwitchButton.stories.tsx
index 34350432b..a29deb3b1 100644
--- a/src/components/MediaMuteAndSwitchButton.stories.tsx
+++ b/src/components/MediaMuteAndSwitchButton.stories.tsx
@@ -1168,6 +1168,36 @@ export const BackgroundEffectsWithALongDeviceName: Story = {
},
};
+/**
+ * A long device name widens the menu past the width at which the preview is
+ * sixteen by nine. The preview still spans it: Safari, given a ratio and a
+ * height cap, kept the ratio by narrowing the box and left a strip of menu at
+ * its right. Checked here in Chromium, which never did — no test tier here
+ * runs WebKit — so this holds the requirement, not the engine.
+ */
+export const BackgroundEffectsWithPreviewInAWideMenu: Story = {
+ args: {
+ ...BackgroundEffectsWithALongDeviceName.args,
+ selfPreview:
,
+ },
+ parameters: { callAreaHeight: 720 },
+ play: async ({ canvasElement }) => {
+ const canvas = within(canvasElement);
+ await userEvent.click(canvas.getByRole("button", { name: "Camera" }));
+ await within(document.body).findByRole("menuitemradio", { name: "Blur" });
+
+ const menu = document.body.querySelector("[role='menu']")!;
+ const preview = menu.querySelector(`.${styles.selfPreview}`)!;
+ const frame = menu.getBoundingClientRect();
+ const box = preview.getBoundingClientRect();
+ // Wider than the preview's sixteen-by-nine width, or this proves nothing.
+ await expect(frame.width).toBeGreaterThan((176 * 16) / 9 + 8);
+ await expect(frame.right - box.right).toBeLessThanOrEqual(2);
+ await expect(box.left - frame.left).toBeLessThanOrEqual(2);
+ await expect(Math.round(box.height)).toBe(176);
+ },
+};
+
/**
* Backgrounds the user added are theirs to remove — except the one in force,
* which is what they are wearing.
diff --git a/src/components/MediaMuteAndSwitchButton.tsx b/src/components/MediaMuteAndSwitchButton.tsx
index 12d8ec3c5..2cd6a9d5e 100644
--- a/src/components/MediaMuteAndSwitchButton.tsx
+++ b/src/components/MediaMuteAndSwitchButton.tsx
@@ -188,11 +188,11 @@ const MIN_LIST_HEIGHT = 160;
/**
* The most the self-preview may take, which is also what it costs the list.
*
- * Drawn at sixteen by nine across the full width of the menu, capped here so
- * that a menu widened by a long device name cannot make it taller than its
- * budget. The preview is paid for out of the list's share rather than on top
- * of it, so the menu is never taller for having one: where the list could not
- * keep its floor after paying, there is no preview at all.
+ * Its fixed height: sixteen by nine across the menu at its narrowest, and
+ * cropped wider as the menu widens, so it never costs more than this. The
+ * preview is paid for out of the list's share rather than on top of it, so the
+ * menu is never taller for having one: where the list could not keep its floor
+ * after paying, there is no preview at all.
*/
const PREVIEW_BLOCK = 176;