Copied!
Gallery

Image carousel 3D coverflow

A coverflow row: the centre card sits flat, enlarged and outlined while the cards beside it swing back in perspective. Drag, swipe, arrow keys or buttons.

← Library

Element · Vanilla JS + CSSUpdated November 6, 2026

3DElementVanilla JS + CSS

Live preview needs JavaScript. The code below works without any dependencies.

Prompt for your AI assistant

Paste this into Claude, Cursor or ChatGPT. The settings update as you move the controls above.

Build "Image carousel 3D coverflow" for my site using the Enrichr Library element at https://enrichr.dev/elements/image-carousel-3d.html.

Read the machine-readable spec and source at https://enrichr.dev/elements/image-carousel-3d.json (markup, CSS, JavaScript, parameters).
Requirements: vanilla JavaScript and CSS, no new dependencies; keep the behaviour of the source unchanged; respect prefers-reduced-motion; work on touch screens; keep it accessible.

Settings:
- accent: #e6ff55
- angle: 52
- spread: 0.3
- scale: 1.18
- autoplay: 0

Verify: reduced motion shows no movement, it works at 375px wide, there are no console errors, and scrolling stays smooth.

The same spec as JSON, for tools that read it directly: /elements/image-carousel-3d.json.

Code

Mount it on an element that contains the markup. It returns an object with update(options) and destroy().

HTML

<div class="ef-ic">
  <svg class="ef-ic__defs" width="0" height="0" aria-hidden="true" focusable="false">
    <defs>
      <symbol id="efic-p1" viewBox="1513.7 1453.9 322.3 322.3"><path fill="currentColor" d="M1821 1761.2h-292.3v-111.6c0-8 6.5-14.5 14.5-14.5 4.2 0 8.2 1.9 11 5.1 7.8 8.6 19.7 13.6 32.7 11.9 16.8-2.3 30.1-16 32-32.7 2.5-22.6-15.1-41.6-37.1-41.6-11 0-20.9 4.7-27.7 12.3-2.8 3.1-6.7 4.9-10.9 4.9-8 0-14.5-6.4-14.5-14.4v-111.7h292.3v111.7c0 8-6.4 14.4-14.5 14.4-4.2 0-8.1-1.8-10.9-5-7.9-8.6-19.7-13.6-32.7-11.9-16.8 2.2-30.1 15.9-32 32.7-2.5 22.6 15.1 41.6 37.1 41.6 11 0 20.8-4.8 27.7-12.3 2.8-3.1 6.7-5 10.8-5 8.1 0 14.5 6.5 14.5 14.5z"/></symbol>
      <symbol id="efic-p4" viewBox="1423.36 860.8 503.07 322.3"><path fill="currentColor" d="M1821 875.8v111.6c0 8 6.5 14.5 14.5 14.5 4.2 0 8.1-1.9 10.9-5 6.8-7.5 16.7-12.3 27.7-12.3 22 0 39.6 19 37.1 41.6-1.9 16.7-15.2 30.5-32 32.7-13 1.7-24.8-3.3-32.7-11.9-2.8-3.2-6.8-5-11-5-8 0-14.5 6.4-14.5 14.4v111.7h-111.6c-8 0-14.5-6.5-14.5-14.5 0-4.2 1.9-8.2 5-11 8.7-7.9 13.7-19.7 12-32.7-2.3-16.8-16-30.1-32.8-32-22.5-2.5-41.5 15.1-41.5 37.1 0 11 4.7 20.9 12.3 27.7 3.1 2.8 4.9 6.7 4.9 10.8v0.1c0 8-6.4 14.5-14.5 14.5h-111.6v-111.7c0-8-6.4-14.4-14.5-14.4-4.1 0-8 1.8-10.8 4.9-6.9 7.6-16.7 12.3-27.7 12.3-22 0-39.6-19-37.1-41.6 1.9-16.7 15.2-30.4 32-32.7 13-1.7 24.8 3.3 32.7 11.9 2.8 3.2 6.7 5.1 10.9 5.1 8.1 0 14.5-6.5 14.5-14.5v-111.6z"/></symbol>
      <symbol id="efic-p8" viewBox="1513.7 166.86 322.3 503.06"><path fill="currentColor" d="M1528.7 564.5v-111.6c0-8 6.5-14.5 14.5-14.5 4.2 0 8.2 1.9 11 5 7.8 8.7 19.7 13.7 32.7 11.9 16.8-2.2 30.1-15.9 32-32.7 2.5-22.5-15.1-41.5-37.1-41.5-11 0-20.9 4.7-27.7 12.3-2.8 3.1-6.7 4.9-10.9 4.9-8 0-14.5-6.5-14.5-14.5v-111.6h111.6c8.1 0 14.5-6.5 14.5-14.5 0-4.1-1.8-8.1-4.9-10.9-7.6-6.8-12.3-16.6-12.3-27.6 0-22 19-39.6 41.5-37.1 16.8 1.8 30.5 15.2 32.8 32 1.7 12.9-3.3 24.8-12 32.7-3.1 2.8-5 6.7-5 10.9 0 8 6.5 14.5 14.5 14.5h111.6v111.6c0 8-6.4 14.5-14.5 14.5-4.2 0-8.1-1.9-10.9-5-7.9-8.7-19.7-13.6-32.7-11.9-16.8 2.2-30.1 15.9-32 32.7-2.5 22.5 15.1 41.6 37.1 41.6 11 0 20.8-4.8 27.7-12.3 2.8-3.1 6.7-5 10.8-5 8.1 0 14.5 6.5 14.5 14.5v111.6h-111.6c-8 0-14.5 6.5-14.5 14.5 0 4.2 1.9 8.1 5 10.9 7.5 6.8 12.3 16.7 12.3 27.7 0 22-19 39.5-41.6 37.1-16.8-1.9-30.5-15.3-32.7-32-1.7-13 3.3-24.9 11.9-32.7 3.1-2.9 5-6.8 5-11 0-8-6.4-14.5-14.5-14.5z"/></symbol>
    </defs>
  </svg>
  <div class="ef-ic__wrap" tabindex="0" role="region" aria-roledescription="carousel" aria-label="3D puzzle-piece carousel. Use the left and right arrow keys, the buttons, or drag to change card.">
    <div class="ef-ic__stage"><div class="ef-ic__floor"></div><div class="ef-ic__cards"></div></div>
    <div class="ef-ic__bar">
      <button type="button" class="ef-ic__btn ef-ic__prev" aria-label="Previous card"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path d="M15 5l-7 7 7 7" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
      <div class="ef-ic__cap" aria-live="polite"><span class="ef-ic__num"></span><span class="ef-ic__ttl"></span><span class="ef-ic__sub"></span></div>
      <button type="button" class="ef-ic__btn ef-ic__next" aria-label="Next card"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path d="M9 5l7 7-7 7" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
    </div>
  </div>
</div>

CSS

/* Enrichr Library · https://enrichr.dev */
.ef-ic { width: 100%; height: 100%; }
.ef-ic__defs { position: absolute; width: 0; height: 0; overflow: hidden; }
.ef-ic__wrap { --ef-acc: #e6ff55; position: relative; box-sizing: border-box; width: 100%; height: 100%; min-height: 26rem; display: flex; flex-direction: column; overflow: hidden; border-radius: 1rem; background: radial-gradient(ellipse at 50% 38%, #005236 0%, #0c1e18 70%); color: #fbf8f1; font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; outline: none; }
.ef-ic__wrap:focus-visible { box-shadow: inset 0 0 0 2px var(--ef-acc); }
.ef-ic__stage { position: relative; flex: 1 1 auto; min-height: 0; perspective: 1100px; perspective-origin: 50% 42%; cursor: grab; touch-action: pan-y; user-select: none; -webkit-user-select: none; }
.ef-ic__stage.is-drag { cursor: grabbing; }
.ef-ic__floor { position: absolute; left: 50%; bottom: 7%; width: 62%; height: 14%; transform: translateX(-50%); border-radius: 50%; background: radial-gradient(ellipse at 50% 50%, rgba(0, 0, 0, 0.55), transparent 70%); pointer-events: none; }
.ef-ic__cards { position: absolute; inset: 0; transform-style: preserve-3d; }
.ef-ic__card { position: absolute; left: 50%; top: 46%; width: min(11rem, 36%); aspect-ratio: 4 / 5; margin: 0; border-radius: 1rem; overflow: hidden; transform-origin: 50% 50%; backface-visibility: hidden; -webkit-backface-visibility: hidden; box-shadow: 0 18px 28px -16px rgba(0, 0, 0, 0.7); will-change: transform; }
.ef-ic__card.is-active { box-shadow: 0 0 0 2px var(--ef-acc), 0 30px 40px -18px rgba(0, 0, 0, 0.8); }
.ef-ic__art { position: absolute; inset: 0; }
.ef-ic__art svg { position: absolute; left: 14%; top: 24%; width: 72%; height: 52%; display: block; overflow: visible; }
.ef-ic__idx { position: absolute; left: 0.9rem; top: 0.8rem; font-size: 0.7rem; font-weight: 800; letter-spacing: 0.16em; opacity: 0.85; }
.ef-ic__lbl { position: absolute; left: 0.9rem; right: 0.9rem; bottom: 0.85rem; font-size: 0.95rem; font-weight: 800; letter-spacing: -0.01em; }
.ef-ic__shade { position: absolute; inset: 0; background: #0c1e18; opacity: 0; pointer-events: none; }
.ef-ic__bar { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.4rem 1.1rem 1rem; }
.ef-ic__cap { flex: 1; min-width: 0; text-align: center; display: flex; flex-direction: column; align-items: center; gap: 0.1rem; }
.ef-ic__num { font-size: 0.68rem; font-weight: 700; letter-spacing: 0.18em; color: var(--ef-acc); }
.ef-ic__ttl { font-size: 1.05rem; font-weight: 800; letter-spacing: -0.01em; }
.ef-ic__sub { font-size: 0.74rem; opacity: 0.7; }
.ef-ic__btn { flex: 0 0 auto; width: 2.5rem; height: 2.5rem; border-radius: 50%; border: 1.5px solid rgba(251, 248, 241, 0.35); background: transparent; color: #fbf8f1; display: grid; place-items: center; cursor: pointer; transition: background 200ms, color 200ms, border-color 200ms; }
.ef-ic__btn:hover { background: var(--ef-acc); color: #0c1e18; border-color: var(--ef-acc); }
.ef-ic__btn:focus-visible { outline: 2px solid var(--ef-acc); outline-offset: 2px; }

JavaScript

// Enrichr Library · https://enrichr.dev
// Image carousel 3D: a coverflow row. The centre card sits flat, enlarged and outlined; the cards either side swing back on a hinge and
// recede in perspective, dimming as they go. Drag or swipe, arrow keys, the buttons, a sideways wheel, or click a side card.
// Reduced motion: no easing, no autoplay; the row steps instantly. The scrolling loop stops itself once the row has settled.
// mount(root, { accent, angle (20-75 deg), spread (0.15-0.6), scale (1-1.5), autoplay (0-8 seconds, 0 = off) })
function mount(root, o) {
  var mq = window.matchMedia("(prefers-reduced-motion: reduce)");
  var wrap = root.querySelector(".ef-ic__wrap");
  var stage = root.querySelector(".ef-ic__stage");
  var holder = root.querySelector(".ef-ic__cards");
  var prevB = root.querySelector(".ef-ic__prev"), nextB = root.querySelector(".ef-ic__next");
  var cNum = root.querySelector(".ef-ic__num"), cTtl = root.querySelector(".ef-ic__ttl"), cSub = root.querySelector(".ef-ic__sub");
  var DATA = [["Starter kit", "Where every set begins"], ["Edge match", "Straight sides, clean joins"], ["Slot in", "One tab, one gap"], ["Click fit", "Snaps into place"], ["Lock step", "Holds the row together"], ["Full set", "Every piece, one picture"], ["Final piece", "The one that completes it"]];
  var COLS = [["#005236", "#e6ff55"], ["#0a6b46", "#fbf8f1"], ["#2f7d5b", "#c6f26b"], ["#c6f26b", "#005236"], ["#fbf8f1", "#0a6b46"], ["#0c1e18", "#c6f26b"], ["#e6ff55", "#0c1e18"]];
  var SYMS = ["efic-p1", "efic-p4", "efic-p8", "efic-p4", "efic-p1", "efic-p8", "efic-p1"];
  var N = DATA.length;
  var opts = {}, cards = [], shades = [], offs = [], raf = 0, timer = 0, onScreen = true, io = null;
  var pos = 0, target = 0, last = 0, drag = null, cardW = 200, hovering = false, wheelAcc = 0, wheelAt = 0, active = -1;

  function mod(a, n) { return ((a % n) + n) % n; }
  function build() {
    holder.textContent = "";
    cards = []; shades = [];
    for (var i = 0; i < N; i++) {
      var c = document.createElement("div");
      c.className = "ef-ic__card";
      c.setAttribute("aria-hidden", "true");
      var pair = COLS[i % COLS.length];
      var art = document.createElement("div");
      art.className = "ef-ic__art";
      art.style.background = i === 6 ? opts.accent : pair[0];
      art.style.color = pair[1];
      var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
      var use = document.createElementNS("http://www.w3.org/2000/svg", "use");
      use.setAttribute("href", "#" + SYMS[i]);
      svg.appendChild(use);
      var ix = document.createElement("span"); ix.className = "ef-ic__idx"; ix.textContent = String(i + 1).padStart(2, "0");
      var lb = document.createElement("span"); lb.className = "ef-ic__lbl"; lb.textContent = DATA[i][0];
      var sh = document.createElement("span"); sh.className = "ef-ic__shade";
      art.appendChild(svg); art.appendChild(ix); art.appendChild(lb); art.appendChild(sh);
      c.appendChild(art);
      holder.appendChild(c);
      cards.push(c); shades.push(sh);
    }
    active = -1;
    measure();
    render();
  }
  function measure() { cardW = cards[0] ? cards[0].offsetWidth || 200 : 200; }

  function render() {
    var ang = opts.angle * Math.PI / 180;
    var step = cardW * opts.spread;
    var push = cardW * (0.5 * opts.scale + 0.5 * Math.cos(ang) + 0.16);
    var act = mod(Math.round(pos), N);
    for (var i = 0; i < N; i++) {
      var d = mod(i - pos + N / 2, N) - N / 2;
      var ad = Math.abs(d), s = d < 0 ? -1 : 1, t = Math.min(1, ad);
      var x = s * (push * t + step * Math.max(0, ad - 1));
      var z = -t * cardW * 0.75 - Math.max(0, ad - 1) * cardW * 0.28 + (1 - t) * cardW * 0.12;
      var sc = 1 + (opts.scale - 1) * (1 - t);
      var el = cards[i];
      el.style.transform = "translate(-50%,-50%) translateX(" + x.toFixed(1) + "px) translateZ(" + z.toFixed(1) + "px) rotateY(" + (-s * opts.angle * t).toFixed(2) + "deg) scale(" + sc.toFixed(3) + ")";
      el.style.zIndex = String(100 - Math.round(ad * 10));
      el.style.opacity = ad > 2.6 ? String(Math.max(0, 1 - (ad - 2.6) / 0.6)) : "1";
      el.style.visibility = ad > 3.2 ? "hidden" : "visible";
      shades[i].style.opacity = (t * 0.4).toFixed(3);
      el.classList.toggle("is-active", i === act);
    }
    if (act !== active) {
      active = act;
      cNum.textContent = String(act + 1).padStart(2, "0") + " / " + String(N).padStart(2, "0");
      cTtl.textContent = DATA[act][0];
      cSub.textContent = DATA[act][1];
    }
  }

  function frame(now) {
    raf = 0;
    var dt = Math.min(0.05, (now - last) / 1000 || 0.016);
    last = now;
    if (!drag) {
      var diff = target - pos;
      if (mq.matches || Math.abs(diff) < 0.0008) pos = target;
      else pos += diff * (1 - Math.exp(-dt * 9));
    }
    render();
    if (drag || pos !== target) raf = requestAnimationFrame(frame);
  }
  function run() {
    if (raf || !onScreen) return;
    last = performance.now();
    raf = requestAnimationFrame(frame);
  }
  function go(to) {
    target = Math.round(to);
    if (mq.matches) pos = target;
    run();
    arm();
  }
  function step(n) { go(Math.round(target) + n); }

  // autoplay, paused on hover/focus/drag and under reduced motion
  function arm() {
    clearTimeout(timer);
    timer = 0;
    if (!(opts.autoplay > 0) || mq.matches || !onScreen) return;
    timer = setTimeout(function () {
      if (!hovering && !drag && !wrap.contains(document.activeElement) && !document.hidden) step(1); else arm();
    }, opts.autoplay * 1000);
  }

  function down(e) {
    if (e.pointerType === "mouse" && e.button !== 0) return;
    drag = { id: e.pointerId, x: e.clientX, y: e.clientY, sx: e.clientX, t: performance.now(), v: 0, moved: false };
    try { stage.setPointerCapture(e.pointerId); } catch (er) {}
    stage.classList.add("is-drag");
    run();
  }
  function move(e) {
    if (!drag || e.pointerId !== drag.id) return;
    var now = performance.now();
    var dx = e.clientX - drag.x;
    if (Math.abs(e.clientX - drag.sx) > 6) drag.moved = true;
    var per = cardW * 0.6;
    var dp = -dx / per;
    pos += dp; target = pos;
    var dts = Math.max(0.008, (now - drag.t) / 1000);
    drag.v = drag.v * 0.6 + (dp / dts) * 0.4;
    drag.x = e.clientX; drag.t = now;
    run();
  }
  function up(e) {
    if (!drag || e.pointerId !== drag.id) return;
    var d = drag; drag = null;
    stage.classList.remove("is-drag");
    try { stage.releasePointerCapture(e.pointerId); } catch (er) {}
    if (!d.moved) {
      // a click: bring a side card to the centre
      var el = document.elementFromPoint(e.clientX, e.clientY);
      var c = el && el.closest ? el.closest(".ef-ic__card") : null;
      var i = c ? cards.indexOf(c) : -1;
      if (i >= 0) { var dd = mod(i - Math.round(pos) + N / 2, N) - N / 2; target = Math.round(pos); go(target + dd); return; }
    }
    var fling = performance.now() - d.t < 90 && !mq.matches ? d.v * 0.18 : 0;
    fling = Math.max(-2, Math.min(2, fling));
    go(pos + fling);
  }
  // wheel is captured only while the pointer is over the carousel, and only for sideways scrolling so the page still scrolls vertically
  function wheel(e) {
    var dx = e.deltaX;
    if (e.shiftKey && !dx) dx = e.deltaY;
    else if (Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return;
    e.preventDefault();
    var now = performance.now();
    if (now - wheelAt > 220) wheelAcc = 0;
    wheelAcc += dx;
    if (Math.abs(wheelAcc) > 40 && now - wheelAt > 220) { step(wheelAcc > 0 ? 1 : -1); wheelAcc = 0; wheelAt = now; }
  }
  function key(e) {
    if (e.key === "ArrowRight") { e.preventDefault(); step(1); }
    else if (e.key === "ArrowLeft") { e.preventDefault(); step(-1); }
    else if (e.key === "Home") { e.preventDefault(); go(Math.round(target) - mod(Math.round(target), N)); }
  }
  function pb() { step(-1); }
  function nb() { step(1); }
  function enter() { hovering = true; }
  function leave() { hovering = false; }
  function resize() { measure(); render(); }

  function attach() {
    stage.addEventListener("pointerdown", down);
    stage.addEventListener("pointermove", move);
    stage.addEventListener("pointerup", up);
    stage.addEventListener("pointercancel", up);
    stage.addEventListener("wheel", wheel, { passive: false });
    wrap.addEventListener("keydown", key);
    wrap.addEventListener("pointerenter", enter);
    wrap.addEventListener("pointerleave", leave);
    prevB.addEventListener("click", pb);
    nextB.addEventListener("click", nb);
    window.addEventListener("resize", resize);
    offs.push(function () {
      stage.removeEventListener("pointerdown", down);
      stage.removeEventListener("pointermove", move);
      stage.removeEventListener("pointerup", up);
      stage.removeEventListener("pointercancel", up);
      stage.removeEventListener("wheel", wheel);
      wrap.removeEventListener("keydown", key);
      wrap.removeEventListener("pointerenter", enter);
      wrap.removeEventListener("pointerleave", leave);
      prevB.removeEventListener("click", pb);
      nextB.removeEventListener("click", nb);
      window.removeEventListener("resize", resize);
    });
    if (window.IntersectionObserver) {
      io = new IntersectionObserver(function (en) { onScreen = en[0].isIntersecting; if (onScreen) { run(); arm(); } else clearTimeout(timer); });
      io.observe(wrap);
    }
  }

  function update(next) {
    var prev = opts;
    opts = Object.assign({ accent: "#e6ff55", angle: 52, spread: 0.3, scale: 1.18, autoplay: 0 }, opts, next || {});
    opts.angle = Math.max(15, Math.min(80, +opts.angle));
    opts.spread = Math.max(0.12, Math.min(0.7, +opts.spread));
    opts.scale = Math.max(1, Math.min(1.6, +opts.scale));
    opts.autoplay = Math.max(0, Math.min(10, +opts.autoplay));
    wrap.style.setProperty("--ef-acc", opts.accent);
    if (prev.accent !== opts.accent) build(); else render();
    arm();
  }
  attach();
  update(o);
  return {
    update: update,
    destroy: function () {
      offs.forEach(function (f) { f(); }); offs = [];
      if (raf) cancelAnimationFrame(raf);
      raf = 0; clearTimeout(timer); timer = 0; onScreen = false;
      if (io) io.disconnect();
      holder.textContent = ""; cards = []; shades = [];
    }
  };
}

// Usage
var fx = mount(document.querySelector("#my-root"), {"accent":"#e6ff55","angle":52,"spread":0.3,"scale":1.18,"autoplay":0});

How it works

One number, pos, says which card is in the centre. For every card the script takes its signed distance d from pos, wrapping around so the row loops. A card at d = 0 is flat and scaled up; as |d| grows to 1 it turns by the side angle on rotateY, moves outward, and drops back on translateZ; beyond 1 it only recedes and dims. Because everything is a continuous function of d, dragging feels smooth and the settle is just pos easing to a whole number. Release adds a short fling, and keys, buttons, a sideways wheel and a click on a side card all change the target.

Accessibility and performance

The carousel is a labelled region you can focus; left and right arrows, Home and the two buttons change card, and the caption below is announced politely. The cards are decorative, so the caption carries the name. Reduced motion removes easing and autoplay and steps instantly. Autoplay also pauses on hover and while focus is inside. The wheel is only captured for sideways scrolling, so vertical page scroll still works.

Seven cards, each moved with transform and opacity only. The loop runs just while the row is dragged or settling and sleeps otherwise; it also pauses when the element is off screen.

SectionGallery
TechniquesCarousel, CSS 3D, Pointer interaction
StackVanilla JS, CSS
Works withReact, Next.js, Vanilla
DependenciesNone
Statustested
On CodePenOpen the live pen on our CodePen account; fork it to experiment.
Inspired by3D Image Carousel by Osmo on osmo.supply. Original code, written from scratch.

Find this technique on a real site

Want to see this technique in the wild? Check any site with the free tools, then build your own version from this page.

Check which animation library a site uses · Extract a site's design tokens · Browse site breakdowns

Licence and provenance

Free to use in personal and client projects during the preview. Final licence terms will be published here before any paid tier starts; ask before reselling the code as a collection.

This is original code. The technique is a common one that we studied by measuring award-winning sites with Enrichr; no third-party code, shaders, fonts or images are used.

3D ring carouselCards on a CSS 3D ring that rotates one step at a time, with buttons, arrow keys and autoplay. Parallax photo carouselA draggable 3D carousel of gradient photo cards with parallax inner layers, inertia and spring snap, plus arrow keys, buttons and dots. Fanned card deckA looping deck of cards fanned like a hand around the one in focus, moved with buttons, arrow keys, a swipe or a click on a side card.

FAQ

How is this different from the other carousels?

It is a straight coverflow row: a flat centred card with angled side cards on a shared hinge angle, enlarged active card, caption bar and a looping row. It has no ring, arc or parallax layers.

Can I use photos?

Yes. Replace the art layer of each card with an img. The card is a plain div, so the angle, shade and enlarge all still work.

How do I change how far the sides turn?

Use the angle and spacing controls. Angle is the rotateY of the neighbours; spacing is how tightly the farther cards stack.