Camera tiles show the whole picture in its own shape
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -411,8 +411,9 @@
|
|||||||
.tile:hover { background: var(--bg-hover); }
|
.tile:hover { background: var(--bg-hover); }
|
||||||
.tile.talking { border-color: var(--speaking); box-shadow: 0 0 0 1px var(--speaking), 0 0 18px -4px var(--speaking); }
|
.tile.talking { border-color: var(--speaking); box-shadow: 0 0 0 1px var(--speaking), 0 0 18px -4px var(--speaking); }
|
||||||
.tile.focused { border-color: var(--accent); }
|
.tile.focused { border-color: var(--accent); }
|
||||||
/* A camera fills its tile; your own is mirrored, the way a mirror shows you */
|
/* A camera keeps its own shape inside the tile, whatever size the tile is: nothing is cut
|
||||||
.tile video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; background: #000; }
|
off, a phone held upright gets bars at the sides. Your own is mirrored, like a mirror. */
|
||||||
|
.tile video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; background: #000; }
|
||||||
.tile video.mirror { transform: scaleX(-1); }
|
.tile video.mirror { transform: scaleX(-1); }
|
||||||
.icon-btn.live, .icon-btn.live:hover { color: var(--speaking); }
|
.icon-btn.live, .icon-btn.live:hover { color: var(--speaking); }
|
||||||
.camgrid { position: relative; display: flex; flex-direction: column; flex: none; }
|
.camgrid { position: relative; display: flex; flex-direction: column; flex: none; }
|
||||||
|
|||||||
@@ -294,6 +294,18 @@ try {
|
|||||||
await camsShown(bob.page, 2);
|
await camsShown(bob.page, 2);
|
||||||
await camsShown(alice.page, 2);
|
await camsShown(alice.page, 2);
|
||||||
assert.equal(await bob.page.locator('.stage .tile.self video.mirror').count(), 1, 'own camera is mirrored');
|
assert.equal(await bob.page.locator('.stage .tile.self video.mirror').count(), 1, 'own camera is mirrored');
|
||||||
|
// The picture keeps its shape in the tile at any tile size: whole, not cropped
|
||||||
|
const shape = () => bob.page.evaluate(() => [...document.querySelectorAll('.stage .tile video')].map(v => {
|
||||||
|
const el = v as HTMLVideoElement, r = el.getBoundingClientRect();
|
||||||
|
const scale = Math.min(r.width / el.videoWidth, r.height / el.videoHeight);
|
||||||
|
return { fit: getComputedStyle(el).objectFit, inside: el.videoWidth * scale <= r.width + 1 && el.videoHeight * scale <= r.height + 1, box: r.width > 50 && r.height > 50 };
|
||||||
|
}));
|
||||||
|
for (const size of [{ width: 1280, height: 800 }, { width: 760, height: 620 }]) {
|
||||||
|
await bob.page.setViewportSize(size);
|
||||||
|
await camsShown(bob.page, 2);
|
||||||
|
assert.ok((await shape()).every(s => s.fit === 'contain' && s.inside && s.box), `cameras whole at ${size.width} wide`);
|
||||||
|
}
|
||||||
|
await bob.page.setViewportSize({ width: 1280, height: 800 });
|
||||||
assert.equal(await bob.page.locator('.stage .tile:not(.self) video.mirror').count(), 0);
|
assert.equal(await bob.page.locator('.stage .tile:not(.self) video.mirror').count(), 0);
|
||||||
await alice.page.locator('.stage .status', { hasText: '1 watching' }).waitFor();
|
await alice.page.locator('.stage .status', { hasText: '1 watching' }).waitFor();
|
||||||
await bob.page.locator('.stage .status', { hasText: '1 watching' }).waitFor();
|
await bob.page.locator('.stage .status', { hasText: '1 watching' }).waitFor();
|
||||||
|
|||||||
Reference in New Issue
Block a user