feat(images): concept hover zoom

This commit is contained in:
Vito Sansevero
2026-10-20 04:55:59 -07:00
parent d3e2e6c47c
commit 9a932cd87f
2 changed files with 22 additions and 0 deletions
+18
View File
@@ -1226,3 +1226,21 @@ test('a debounced search that lands after a sort change does not reload the same
await page.waitForTimeout(1000);
expect(reads.filter(search => new URLSearchParams(search).get('q') === 'same')).toHaveLength(1);
});
// #249 D18: the concept's hover zoom (1.03 over 0.2s); reduced motion makes it instant, and a touch device (hover: none)
// has no sticky zoom after a tap.
for (const motion of ['no-preference', 'reduce']) {
test(`card image zooms on hover per concept (${motion})`, async ({ page }) => {
await page.emulateMedia({ reducedMotion: motion });
await page.goto(workspace);
await expect(cards(page)).toHaveCount(2);
const card = cards(page).first();
const img = card.locator('.image-open img');
const touch = await page.evaluate(() => matchMedia('(hover: none)').matches);
expect(await img.evaluate(n => getComputedStyle(n).transform)).toBe('none');
await card.locator('.image-open').hover();
// Touch: the emulated hover must not leave a zoom behind.
await expect.poll(() => img.evaluate(n => getComputedStyle(n).transform)).toBe(touch ? 'none' : 'matrix(1.03, 0, 0, 1.03, 0, 0)');
expect(await img.evaluate(n => getComputedStyle(n).transitionDuration)).toBe(motion === 'reduce' ? '0s' : '0.2s');
});
}
+4
View File
@@ -65,6 +65,10 @@ main:focus-visible { outline: 2px solid var(--ca-focus); outline-offset: 4px; }
/* #249 D32: likewise the continuation's text and Load more wrap instead of pushing the button out of the column. */
.infinite-continuation { flex-wrap: wrap; }
/* #249 D18: the concept zooms a hovered card image (1.03 over 0.2s; its reduced-motion rule makes that instant). A touch
device has no real hover, so a tap must not leave the zoom stuck. */
@media (hover: none) { .image-open:hover img { transform: none; } }
/* Images cards (#249 C3). #249 D3: bounded rendering mounts only the visible cards and places each one itself, in every
layout (the concept places only masonry cards), so the grid reserves the collection's height and every card is
absolute. Columns and gap still come from the concept's `.image-grid` tracks. */