Copied!
Gallery

Stack to grid intro

A pile of tiles sits under a big title, then the letters spread apart, the pile scatters and every tile lands in its grid cell, with a hover zoom afterwards.

← Library

Element · Vanilla JS + CSS + SVGUpdated November 6, 2026

CursorElementVanilla JS + CSS + SVG

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 "Stack to grid intro" for my site using the Enrichr Library element at https://enrichr.dev/elements/stack-to-grid-intro.html.

Read the machine-readable spec and source at https://enrichr.dev/elements/stack-to-grid-intro.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:
- title: Stack
- accent: #e6ff55
- count: 6
- scatter: 100
- speed: 1

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/stack-to-grid-intro.json.

Code

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

HTML

<div class="ef-stg">
  <div class="ef-stg__stage">
    <svg class="ef-stg__defs" width="0" height="0" aria-hidden="true" focusable="false">
      <symbol id="efstg-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="efstg-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="efstg-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>
    </svg>
    <div class="ef-stg__grid"></div>
    <h3 class="ef-stg__title" aria-hidden="true"></h3>
    <button type="button" class="ef-stg__replay">Replay</button>
  </div>
</div>

CSS

/* Enrichr Library · https://enrichr.dev */
/* Adapted from https://github.com/codrops/ImageStackGrid (MIT) — Copyright (c) 2009 - 2020 [Codrops](https://tympanus.net/codrops) */
.ef-stg { width: 100%; height: 100%; }
.ef-stg__stage { --ef-bg: #0c1e18; --ef-fg: #fbf8f1; --ef-acc: #e6ff55; position: relative; box-sizing: border-box; width: 100%; height: 100%; min-height: 28rem; overflow: hidden; background: var(--ef-bg); color: var(--ef-fg); container-type: inline-size; font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
.ef-stg__defs { position: absolute; width: 0; height: 0; overflow: hidden; }
.ef-stg__grid { position: absolute; inset: 0; display: grid; justify-content: center; align-content: center; gap: 10px; padding: 20px; }
.ef-stg__tile { position: relative; display: block; box-sizing: border-box; width: var(--tw); height: var(--th); margin: 0; padding: 0; border: 0; border-radius: 6px; overflow: hidden; cursor: pointer; color: inherit; font: inherit; text-align: left; background: #005236; will-change: transform, opacity; outline: none; }
.ef-stg__tile.is-stacked { cursor: default; }
.ef-stg__tile:focus-visible { box-shadow: 0 0 0 2px var(--ef-bg), 0 0 0 4px var(--ef-acc); }
.ef-stg__in { position: absolute; inset: 0; overflow: hidden; will-change: transform; }
.ef-stg__pc { position: absolute; left: 50%; top: 46%; width: 62%; height: 62%; margin: -31% 0 0 -31%; opacity: 0.92; }
.ef-stg__cap { position: absolute; left: 0; right: 0; bottom: 0; padding: 1.6rem 0.55rem 0.5rem; background: linear-gradient(180deg, transparent, rgba(12, 30, 24, 0.78)); color: #fbf8f1; pointer-events: none; }
.ef-stg__num { display: block; font-size: 0.6rem; font-weight: 700; letter-spacing: 0.16em; opacity: 0.85; }
.ef-stg__name { display: block; font-size: 0.85rem; font-weight: 800; letter-spacing: -0.01em; line-height: 1.15; }
.ef-stg__title { position: absolute; inset: 0; z-index: 2; margin: 0; display: flex; align-items: center; justify-content: center; font-size: clamp(2rem, 17cqw, 5.4rem); font-weight: 800; letter-spacing: -0.04em; line-height: 1; color: var(--ef-acc); pointer-events: none; text-shadow: 0 2px 24px rgba(12, 30, 24, 0.5); }
.ef-stg__ch { display: inline-block; white-space: pre; will-change: transform, opacity; }
.ef-stg__replay { position: absolute; z-index: 3; right: 0.7rem; bottom: 0.7rem; padding: 0.4rem 0.8rem; border: 1px solid var(--ef-fg); border-radius: 99px; background: transparent; color: var(--ef-fg); font: inherit; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer; transition: background 200ms, color 200ms; }
.ef-stg__replay:hover { background: var(--ef-acc); color: #0c1e18; border-color: var(--ef-acc); }
.ef-stg__replay:focus-visible { outline: 2px solid var(--ef-acc); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .ef-stg__replay { transition: none; } }

JavaScript

// Enrichr Library · https://enrichr.dev
// Adapted from https://github.com/codrops/ImageStackGrid (MIT) — Copyright (c) 2009 - 2020 [Codrops](https://tympanus.net/codrops)
// Stack to grid intro: tiles pile up in the centre at random angles under a big title, then the title letters spread out, the pile
// scatters and every tile lands in its grid cell (the first one flies straight in with a zoom-out reveal). Hover or focus pushes a tile in
// and zooms its art. Vanilla port: one rAF tween loop, no animation library. Replay button restarts it; reduced motion shows the final grid.
// mount(root, { title, accent, count (4-9), scatter (0-300 %), speed (0.5-2) })
function mount(root, o) {
  var reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  var stage = root.querySelector(".ef-stg__stage");
  var grid = root.querySelector(".ef-stg__grid");
  var titleEl = root.querySelector(".ef-stg__title");
  var replay = root.querySelector(".ef-stg__replay");
  var opts = {}, tiles = [], chars = [], tw = [], offs = [], raf = 0, phase = "idle", ro = null, dead = false, timer = 0;
  var NAMES = ["Tidal", "Ember", "Moss", "Dusk", "Arch", "Grain", "Haze", "Loop", "Fern"];
  var PIECES = ["efstg-p1", "efstg-p4", "efstg-p8"];

  function expo(t) { return t >= 1 ? 1 : 1 - Math.pow(2, -10 * t); }
  function p3(t) { return 1 - Math.pow(1 - t, 4); }
  function p1(t) { return 1 - (1 - t) * (1 - t); }
  function rnd(a, b) { return a + Math.random() * (b - a); }

  function art(i, a) {
    var p = i % 4;
    if (p === 0) return "linear-gradient(160deg, #0a6b46, #005236)";
    if (p === 1) return "radial-gradient(circle at 72% 24%, " + a + " 0 20%, transparent 21%), linear-gradient(200deg, #2f7d5b, #0c1e18)";
    if (p === 2) return "linear-gradient(145deg, #c6f26b, #2f7d5b)";
    return "repeating-linear-gradient(120deg, #005236 0 10px, #0c1e18 10px 20px)";
  }

  /* tween loop */
  function go(st, key, to, delay, dur, ease, fromVal, replace) {
    if (replace) tw = tw.filter(function (a) { return !(a.st === st && a.key === key); });
    tw.push({ st: st, key: key, to: to, t0: performance.now() + delay, dur: Math.max(1, dur), ease: ease, fv: fromVal, from: null });
    kick();
  }
  function at(delay, fn) { tw.push({ fn: fn, t0: performance.now() + delay }); kick(); }
  function kick() { if (!raf && !dead) raf = requestAnimationFrame(tick); }
  function tick(now) {
    raf = 0;
    for (var i = 0; i < tw.length; i++) {
      var a = tw[i];
      if (now < a.t0) continue;
      if (a.fn) { a.done = true; a.fn(); continue; }
      if (a.from === null) a.from = a.fv !== undefined ? a.fv : a.st[a.key];
      var p = Math.min(1, (now - a.t0) / a.dur);
      a.st[a.key] = a.from + (a.to - a.from) * a.ease(p);
      if (p >= 1) a.done = true;
    }
    tw = tw.filter(function (a) { return !a.done; });
    renderAll();
    if (tw.length) raf = requestAnimationFrame(tick);
  }
  function renderAll() {
    tiles.forEach(function (t) {
      var s = t.st;
      t.el.style.transform = "translate3d(" + s.x.toFixed(1) + "px," + s.y.toFixed(1) + "px,0) rotate(" + s.r.toFixed(2) + "deg) scale(" + s.s.toFixed(3) + ")";
      t.el.style.opacity = s.o.toFixed(3);
      t.inner.style.transform = "scale(" + s.is.toFixed(3) + ")";
      t.cap.style.opacity = s.co.toFixed(3);
    });
    chars.forEach(function (c) {
      c.node.style.transform = "translate3d(" + c.st.x.toFixed(1) + "px," + c.st.y.toFixed(1) + "px,0)";
      c.node.style.opacity = c.st.o.toFixed(3);
    });
  }
  function stop() {
    if (raf) cancelAnimationFrame(raf);
    raf = 0; tw = []; clearTimeout(timer);
  }

  /* layout */
  function layout() {
    var W = stage.clientWidth, H = stage.clientHeight, n = opts.count;
    var cols = n <= 4 ? 2 : n <= 6 ? 3 : n <= 8 ? 4 : 3;
    var rows = Math.ceil(n / cols), pad = 20, gap = 10;
    var tw0 = Math.min((W - pad * 2 - gap * (cols - 1)) / cols, ((H - pad * 2 - gap * (rows - 1)) / rows) * 0.75);
    tw0 = Math.max(40, tw0);
    grid.style.gridTemplateColumns = "repeat(" + cols + ", " + tw0.toFixed(1) + "px)";
    grid.style.setProperty("--tw", tw0.toFixed(1) + "px");
    grid.style.setProperty("--th", (tw0 / 0.75).toFixed(1) + "px");
  }
  function measure() {
    tiles.forEach(function (t) { t.el.style.transform = "none"; });
    var sr = stage.getBoundingClientRect();
    tiles.forEach(function (t) {
      var r = t.el.getBoundingClientRect();
      t.w = r.width; t.h = r.height;
      t.dx = sr.left + sr.width / 2 - (r.left + r.width / 2);
      t.dy = sr.top + sr.height / 2 - (r.top + r.height / 2);
    });
  }

  /* build */
  function build() {
    stop();
    grid.textContent = ""; tiles = [];
    titleEl.textContent = ""; chars = [];
    layout();
    var t = String(opts.title || "");
    for (var c = 0; c < t.length; c++) {
      var sp = document.createElement("span");
      sp.className = "ef-stg__ch"; sp.textContent = t[c];
      titleEl.appendChild(sp);
      chars.push({ node: sp, st: { x: 0, y: 0, o: 1 } });
    }
    for (var i = 0; i < opts.count; i++) (function (i) {
      var el = document.createElement("button");
      el.type = "button"; el.className = "ef-stg__tile";
      el.setAttribute("aria-label", "Piece " + (i + 1) + ": " + NAMES[i % NAMES.length]);
      var inner = document.createElement("span"); inner.className = "ef-stg__in";
      inner.style.background = art(i, opts.accent);
      var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
      svg.setAttribute("class", "ef-stg__pc"); svg.setAttribute("aria-hidden", "true");
      svg.style.color = i % 4 === 2 ? "#0c1e18" : i % 2 ? "#fbf8f1" : opts.accent;
      svg.style.transform = "rotate(" + ((i % 5) - 2) * 7 + "deg)";
      var use = document.createElementNS("http://www.w3.org/2000/svg", "use");
      use.setAttribute("href", "#" + PIECES[i % 3]);
      svg.appendChild(use); inner.appendChild(svg);
      var cap = document.createElement("span"); cap.className = "ef-stg__cap";
      var num = document.createElement("span"); num.className = "ef-stg__num"; num.textContent = String(i + 1).padStart(2, "0");
      var nm = document.createElement("span"); nm.className = "ef-stg__name"; nm.textContent = NAMES[i % NAMES.length];
      cap.appendChild(num); cap.appendChild(nm);
      el.appendChild(inner); el.appendChild(cap);
      var tile = { el: el, inner: inner, cap: cap, w: 0, h: 0, dx: 0, dy: 0, st: { x: 0, y: 0, s: 1, r: 0, o: 1, is: 1, co: 1 } };
      function hin() { hover(tile, true); }
      function hout() { hover(tile, false); }
      el.addEventListener("pointerenter", hin); el.addEventListener("pointerleave", hout);
      el.addEventListener("focus", hin); el.addEventListener("blur", hout);
      offs.push(function () { el.removeEventListener("pointerenter", hin); el.removeEventListener("pointerleave", hout); el.removeEventListener("focus", hin); el.removeEventListener("blur", hout); });
      grid.appendChild(el); tiles.push(tile);
    })(i);
    renderAll();
  }
  function hover(t, on) {
    if (phase !== "grid" || reduce) return;
    var d = 900 / opts.speed;
    go(t.st, "s", on ? 0.95 : 1, 0, d, expo, undefined, true);
    go(t.st, "is", on ? 1.2 : 1, 0, d, expo, undefined, true);
  }

  /* the intro */
  function play() {
    stop();
    var n = tiles.length, D = 1000 / opts.speed;
    layout(); measure();
    tiles.forEach(function (t) { t.el.classList.remove("is-stacked"); });
    if (reduce) {
      phase = "grid";
      tiles.forEach(function (t) { var s = t.st; s.x = 0; s.y = 0; s.s = 1; s.r = 0; s.is = 1; s.co = 1; s.o = 0; go(s, "o", 1, 0, 300, p1); });
      chars.forEach(function (c) { c.st.o = 0; });
      renderAll();
      return;
    }
    phase = "stack";
    tiles.forEach(function (t, i) {
      var s = t.st;
      s.x = t.dx; s.y = t.dy; s.s = 0.6; s.r = rnd(-10, 10); s.o = 0; s.is = i === 0 ? 1.8 : 1; s.co = 0;
      t.el.classList.add("is-stacked");
      go(s, "o", 1, 0.2 * i * D, 1, p1);
    });
    chars.forEach(function (c) { c.st.x = 0; c.st.y = 14; c.st.o = 0; go(c.st, "o", 1, 0, D, expo); go(c.st, "y", 0, 0, D, expo); });
    renderAll();
    var T = (0.2 * (n - 1) + 0.6) * D, mid = Math.floor(chars.length / 2);
    chars.forEach(function (c, pos) {
      var sd = (Math.abs(pos - mid) / Math.max(1, mid)) * 0.2 * D;
      go(c.st, "x", -40 * (mid - pos), T + sd, D, expo);
      go(c.st, "o", 0, T + sd, D, expo);
    });
    tiles.forEach(function (t, i) {
      var s = t.st;
      if (i === 0) {
        [["x", 0], ["y", 0], ["s", 1], ["r", 0], ["o", 1], ["is", 1]].forEach(function (k) { go(s, k[0], k[1], T, 1.2 * D, expo); });
      } else {
        var sc = opts.scatter / 100;
        go(s, "x", s.x + rnd(-2, 2) * t.w * sc, T, D, p3);
        go(s, "y", s.y + rnd(-2, 2) * t.h * sc, T, D, p3);
        go(s, "r", rnd(-20, 20), T, D, p3);
        go(s, "o", 0, T, D, p3);
        at(T + D, function () { s.x = 0; s.y = 0; s.r = 0; s.s = 0.8; });
        go(s, "s", 1, T + D, 0.5 * D, expo, 0.8);
        go(s, "o", 1, T + D, 0.5 * D, expo, 0);
      }
      go(s, "co", 1, T + 0.3 * D + i * 0.04 * D, D, p1);
    });
    at(T + 1.2 * D, function () { phase = "grid"; tiles.forEach(function (t) { t.el.classList.remove("is-stacked"); }); });
  }

  function onReplay() { play(); }
  replay.addEventListener("click", onReplay);
  offs.push(function () { replay.removeEventListener("click", onReplay); });
  if (typeof ResizeObserver !== "undefined") {
    ro = new ResizeObserver(function () {
      if (dead || !tiles.length) return;
      layout();
      if (phase === "stack") { measure(); tiles.forEach(function (t) { t.st.x = t.dx; t.st.y = t.dy; }); }
      renderAll();
    });
    ro.observe(stage);
  }

  function update(next) {
    var prev = opts;
    opts = Object.assign({ title: "Stack", accent: "#e6ff55", count: 6, scatter: 100, speed: 1 }, opts, next || {});
    opts.count = Math.max(4, Math.min(9, Math.round(opts.count)));
    opts.scatter = Math.max(0, Math.min(300, +opts.scatter));
    opts.speed = Math.max(0.5, Math.min(2, +opts.speed));
    stage.style.setProperty("--ef-acc", opts.accent);
    if (prev.count !== opts.count || prev.accent !== opts.accent || prev.title !== opts.title) { build(); play(); }
  }
  update(o);
  return {
    update: update,
    destroy: function () {
      dead = true; stop();
      if (ro) ro.disconnect();
      offs.forEach(function (f) { f(); }); offs = [];
      grid.textContent = ""; titleEl.textContent = "";
    }
  };
}

// Usage
var fx = mount(document.querySelector("#my-root"), {"title":"Stack","accent":"#e6ff55","count":6,"scatter":100,"speed":1});

How it works

This is a vanilla port of the Codrops intro, with no GSAP. Every tile keeps a small state object (x, y, scale, rotation, opacity, inner scale) and one requestAnimationFrame loop tweens those numbers with expo and power eases. First the script measures each grid cell and moves every tile to the centre of the stage at 0.6 scale and a random tilt, so they appear one after another as a pile. Then the title letters slide outward from the middle and fade, all tiles except the first fly off by a random distance and fade, reappear in their own cell at 0.8 scale and settle to 1, while the first tile flies straight in and its inner art scales from 1.8 down to 1 for the reveal. Captions fade in last. After the intro a hover or focus pushes the tile to 0.95 and zooms the art inside to 1.2.

Accessibility and performance

Tiles are buttons with labels, and keyboard focus triggers the same push-in and zoom as hover, plus a visible focus ring. The big title is decorative and hidden from screen readers. The Replay button is a real button. With reduced motion the stack is skipped: the grid just fades in and the hover movement is off.

Only transform and opacity change, on a handful of elements, and the rAF loop stops as soon as the last tween ends. Measuring happens once per replay. Keep the tile count modest if you swap the gradients for large photos, and give photos explicit sizes so the stack position is measured correctly.

SectionGallery
TechniquesReveal and intro, Stagger, Pointer interaction
StackVanilla JS, CSS, SVG
Works withReact, Next.js, Vanilla
DependenciesNone
Statustested
On CodePenOpen the live pen on our CodePen account; fork it to experiment.
Inspired byImage Stack Intro Animation by Codrops on github.com. 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.

Flight path heroA photo hero with a gradient headline that drops in letter by letter, a plane flying a soft contrail, and a menu button that wipes a full-screen menu down while the page swings away. Hover lift typeBig uppercase type that rises into place letter by letter, then lifts and changes colour under the pointer, word by word or letter by letter. Logo intro revealA loader builds the Enrichr mark beside a window of flicking shapes and a 100 to 000 counter, lifts like a curtain, then the pointer stirs ink that reveals a photo through the page.

FAQ

Can I use real photos instead of the gradients?

Yes. Replace the background on the inner layer of each tile with an image. The inner layer is what zooms on hover and during the reveal, so keep it as the image holder.

How do I make the intro slower or looser?

Use the speed parameter to scale every duration, and the scatter parameter to control how far the pile throws the tiles before they land.

Why does it measure the grid before animating?

The pile position is the distance from each cell to the stage centre. Measuring with transforms cleared gives the true layout position, and the numbers are refreshed on resize while the pile is showing.