36 CSS Stacked Cards19 / 36

Pure CSSMIT licensed

Photos Pinch

A scattered cluster of photos that, on hover of any single image, gathers inward toward a shared centre line while that photo brightens and lifts — a 'pinch to focus' gesture rendered entirely in CSS transforms.

Published Updated

Live Demo
Try it

The code

<section class="scd-19" aria-label="Pinch-to-focus photo cluster">
  <div class="scd-19__cluster">
    <a class="scd-19__ph" href="#" style="--x:-120px;--y:-40px;--r:-7deg"><img src="https://picsum.photos/seed/pinch-1/260/260" width="260" height="260" alt="Street market"></a>
    <a class="scd-19__ph" href="#" style="--x:110px;--y:-60px;--r:6deg"><img src="https://picsum.photos/seed/pinch-2/260/260" width="260" height="260" alt="Neon alley"></a>
    <a class="scd-19__ph" href="#" style="--x:-90px;--y:70px;--r:5deg"><img src="https://picsum.photos/seed/pinch-3/260/260" width="260" height="260" alt="Rooftop garden"></a>
    <a class="scd-19__ph" href="#" style="--x:120px;--y:60px;--r:-6deg"><img src="https://picsum.photos/seed/pinch-4/260/260" width="260" height="260" alt="Harbour lights"></a>
    <a class="scd-19__ph" href="#" style="--x:0px;--y:0px;--r:2deg"><img src="https://picsum.photos/seed/pinch-5/260/260" width="260" height="260" alt="Train window"></a>
  </div>
</section>
.scd-19,
.scd-19 *,
.scd-19 *::before,
.scd-19 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.scd-19 {
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 60px 20px;
  background: #0c0d13;
}

.scd-19__cluster {
  position: relative;
  width: min(360px,90vw);
  height: 340px;
}

.scd-19__ph {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 150px;
  height: 150px;
  margin: -75px 0 0 -75px;
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 16px 34px -18px rgba(0,0,0,.8);
  transform: translate(var(--x),var(--y)) rotate(var(--r));
  transition: transform .45s cubic-bezier(.3,.9,.3,1),filter .35s;
  outline: none;
}

.scd-19__ph img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.scd-19__cluster:has(.scd-19__ph:hover) .scd-19__ph,
.scd-19__cluster:has(.scd-19__ph:focus-visible) .scd-19__ph {
  transform: translate(calc(var(--x) * .3),calc(var(--y) * .3)) rotate(0);
  filter: brightness(.5);
}

.scd-19__ph:hover,
.scd-19__ph:focus-visible {
  transform: translate(0,0) rotate(0) scale(1.25)!important;
  filter: brightness(1.1)!important;
  z-index: 10;
}

.scd-19__ph:focus-visible {
  box-shadow: 0 0 0 3px oklch(0.7 0.15 250),0 24px 44px -18px rgba(0,0,0,.9);
}

@media (prefers-reduced-motion: reduce) {
  .scd-19__ph {
    transition: filter .25s;
  }

  .scd-19__cluster:has(.scd-19__ph:hover) .scd-19__ph {
    transform: translate(var(--x),var(--y)) rotate(var(--r));
  }

  .scd-19__ph:hover {
    transform: translate(var(--x),var(--y)) rotate(var(--r)) scale(1.1)!important;
  }
}
/* No JavaScript — :has(:hover) on the cluster pulls every photo toward centre and dims the group, while the hovered photo overrides to brighten, scale and rise. */
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Stacked Card from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Photos Pinch
Source: https://codefronts.com/components/css-stacked-cards/photos-pinch/

A scattered cluster of photos that, on hover of any single image, gathers inward toward a shared centre line while that photo brightens and lifts — a 'pinch to focus' gesture rendered entirely in CSS transforms.
## HTML
```html
<section class="scd-19" aria-label="Pinch-to-focus photo cluster">
  <div class="scd-19__cluster">
    <a class="scd-19__ph" href="#" style="--x:-120px;--y:-40px;--r:-7deg"><img src="https://picsum.photos/seed/pinch-1/260/260" width="260" height="260" alt="Street market"></a>
    <a class="scd-19__ph" href="#" style="--x:110px;--y:-60px;--r:6deg"><img src="https://picsum.photos/seed/pinch-2/260/260" width="260" height="260" alt="Neon alley"></a>
    <a class="scd-19__ph" href="#" style="--x:-90px;--y:70px;--r:5deg"><img src="https://picsum.photos/seed/pinch-3/260/260" width="260" height="260" alt="Rooftop garden"></a>
    <a class="scd-19__ph" href="#" style="--x:120px;--y:60px;--r:-6deg"><img src="https://picsum.photos/seed/pinch-4/260/260" width="260" height="260" alt="Harbour lights"></a>
    <a class="scd-19__ph" href="#" style="--x:0px;--y:0px;--r:2deg"><img src="https://picsum.photos/seed/pinch-5/260/260" width="260" height="260" alt="Train window"></a>
  </div>
</section>
```
## CSS
```css
.scd-19,
.scd-19 *,
.scd-19 *::before,
.scd-19 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.scd-19 {
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 60px 20px;
  background: #0c0d13;
}

.scd-19__cluster {
  position: relative;
  width: min(360px,90vw);
  height: 340px;
}

.scd-19__ph {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 150px;
  height: 150px;
  margin: -75px 0 0 -75px;
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 16px 34px -18px rgba(0,0,0,.8);
  transform: translate(var(--x),var(--y)) rotate(var(--r));
  transition: transform .45s cubic-bezier(.3,.9,.3,1),filter .35s;
  outline: none;
}

.scd-19__ph img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.scd-19__cluster:has(.scd-19__ph:hover) .scd-19__ph,
.scd-19__cluster:has(.scd-19__ph:focus-visible) .scd-19__ph {
  transform: translate(calc(var(--x) * .3),calc(var(--y) * .3)) rotate(0);
  filter: brightness(.5);
}

.scd-19__ph:hover,
.scd-19__ph:focus-visible {
  transform: translate(0,0) rotate(0) scale(1.25)!important;
  filter: brightness(1.1)!important;
  z-index: 10;
}

.scd-19__ph:focus-visible {
  box-shadow: 0 0 0 3px oklch(0.7 0.15 250),0 24px 44px -18px rgba(0,0,0,.9);
}

@media (prefers-reduced-motion: reduce) {
  .scd-19__ph {
    transition: filter .25s;
  }

  .scd-19__cluster:has(.scd-19__ph:hover) .scd-19__ph {
    transform: translate(var(--x),var(--y)) rotate(var(--r));
  }

  .scd-19__ph:hover {
    transform: translate(var(--x),var(--y)) rotate(var(--r)) scale(1.1)!important;
  }
}
```

## JavaScript
```js
/* No JavaScript — :has(:hover) on the cluster pulls every photo toward centre and dims the group, while the hovered photo overrides to brighten, scale and rise. */
```

How this works

Photos are absolutely placed around a centre with per-card offset vars. When you hover one photo, container-level :has(:hover) pulls every card toward the centre (reducing its offset) and dims the group, while the hovered card overrides that to brighten, scale up and rise. The result is a magnetic pinch that spotlights your target.

Each photo is a focusable link with alt text; :focus-within mirrors the pinch for keyboards. Reduced-motion keeps the scatter static and just brightens the focused image.

Make it yours

  • Set the scatter and pinch strength via the offset multipliers.
  • Change the dim/brighten balance for more or less spotlight.
  • Swap the centre point the cards gather toward.
  • Restyle frames (borders, radius, ratio).

Gotchas — read before shipping

  • :has() drives the group pinch — provide a hover-only fallback for older engines.
  • Keep alt text meaningful; the cluster is decoration.
  • Ensure the focused card's z-index clears the pack.

Browser support

ChromeSafariFirefoxEdge
111+15.4+121+111+

Floor set by :has(), oklch() as this demo is written. The core technique itself goes back further — Chrome 105+.

:has() enables the group pinch; where unsupported, each photo still brightens and lifts on its own hover.

Techniques used in this demo

Search CodeFronts

Loading…