Skip to content
animorio
IT
Back to the library

The house of cards

Physics, SVGGSAP, CSS onlyAdvancedCompressed weight: Demo 11 KBGSAP 28 KBMatter.js 25 KBFreeDownload the zip

A house of playing cards builds itself, card by card, with the cards gliding in and leaning on each other in pairs with a tremble; a click or a puff of the pointer brings it down with real physics, the cards pile up and then the house builds itself again.

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>The house of cards</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bodoni-moda@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="scena" data-stage data-autoclic tabindex="0" role="button" aria-label="A house of cards: click to knock it down">
    <!-- the drawings every card uses: the back of the deck and four fronts, drawn upright and turned to fit the card -->
    <svg class="disegni" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <pattern id="lattice" patternUnits="userSpaceOnUse" width="8" height="8" patternTransform="rotate(45)">
          <path class="dorso-rete" d="M0 0V8M0 0H8" />
        </pattern>
        <path id="pip-heart" d="M0 8C-9 1-11-4-11-7C-11-11-8-13-5.5-13C-3-13-1-11.5 0-9.5C1-11.5 3-13 5.5-13C8-13 11-11 11-7C11-4 9 1 0 8Z" />
        <path id="pip-diamond" d="M0-12C2.6-7 5.6-3 8.4 0C5.6 3 2.6 7 0 12C-2.6 7-5.6 3-8.4 0C-5.6-3-2.6-7 0-12Z" />
        <path id="pip-spade" d="M0-13C-9-5-11 0-11 3C-11 7-8 9-5.5 9C-3.6 9-2 8-1.2 6.6C-1.4 9-2.6 11.2-4.6 12.6H4.6C2.6 11.2 1.4 9 1.2 6.6C2 8 3.6 9 5.5 9C8 9 11 7 11 3C11 0 9-5 0-13Z" />
        <path id="pip-club" d="M0-13C3.4-13 5.8-10.4 5.8-7.2C5.8-5.8 5.4-4.6 4.6-3.6C5.2-3.8 5.8-3.9 6.4-3.9C9.6-3.9 12-1.4 12 1.8C12 5 9.6 7.6 6.4 7.6C4.4 7.6 2.6 6.6 1.6 5C1.8 8.2 2.8 10.8 4.6 12.6H-4.6C-2.8 10.8-1.8 8.2-1.6 5C-2.6 6.6-4.4 7.6-6.4 7.6C-9.6 7.6-12 5-12 1.8C-12-1.4-9.6-3.9-6.4-3.9C-5.8-3.9-5.2-3.8-4.6-3.6C-5.4-4.6-5.8-5.8-5.8-7.2C-5.8-10.4-3.4-13 0-13Z" />
        <path id="star" d="M0-6C.8-1.6 1.6-.8 6 0C1.6.8.8 1.6 0 6C-.8 1.6-1.6.8-6 0C-1.6-.8-.8-1.6 0-6Z" />

        <!-- the back of the deck: a lattice, a frame, and a moon with a star in an oval -->
        <symbol id="back" viewBox="0 0 100 70">
          <g transform="translate(100 0) rotate(90)">
            <rect class="dorso-carta" width="70" height="100" rx="5" />
            <rect class="dorso" x="4" y="4" width="62" height="92" rx="3" />
            <rect fill="url(#lattice)" x="7" y="7" width="56" height="86" rx="2" />
            <rect class="dorso-filo" x="7" y="7" width="56" height="86" rx="2" />
            <rect class="dorso-filo dorso-filo--fine" x="9.5" y="9.5" width="51" height="81" rx="1.5" />
            <ellipse class="dorso dorso-filo" cx="35" cy="50" rx="17" ry="23" />
            <ellipse class="dorso-filo dorso-filo--fine" cx="35" cy="50" rx="14" ry="20" />
            <path class="dorso-oro" d="M39.5 37.5C33 37.5 28.5 43 28.5 49.5C28.5 56 33 61.5 39.5 61.5C36.6 62.8 33.4 63.2 30.2 62C25.4 60 22.6 55 22.6 49.5C22.6 44 25.4 39 30.2 37C33.4 35.8 36.6 36.2 39.5 37.5Z" />
            <use class="dorso-oro" href="#star" transform="translate(41 47) scale(.9)" />
            <use class="dorso-oro" href="#star" transform="translate(44.5 57) scale(.45)" />
            <use class="dorso-oro" href="#star" transform="translate(14 14) scale(.55)" />
            <use class="dorso-oro" href="#star" transform="translate(56 14) scale(.55)" />
            <use class="dorso-oro" href="#star" transform="translate(14 86) scale(.55)" />
            <use class="dorso-oro" href="#star" transform="translate(56 86) scale(.55)" />
          </g>
        </symbol>

        <!-- four fronts: an ace and three court letters, each with one big pip -->
        <symbol id="front-a" viewBox="0 0 100 70">
          <g transform="translate(100 0) rotate(90)">
            <rect class="fronte" width="70" height="100" rx="5" />
            <rect class="fronte-filo" x="3.5" y="3.5" width="63" height="93" rx="3" />
            <use class="seme-nero" href="#pip-spade" transform="translate(35 50) scale(1.55)" />
            <text class="indice seme-nero" x="9" y="16">A</text>
            <use class="seme-nero" href="#pip-spade" transform="translate(10.5 24) scale(.32)" />
            <g transform="rotate(180 35 50)">
              <text class="indice seme-nero" x="9" y="16">A</text>
              <use class="seme-nero" href="#pip-spade" transform="translate(10.5 24) scale(.32)" />
            </g>
          </g>
        </symbol>
        <symbol id="front-k" viewBox="0 0 100 70">
          <g transform="translate(100 0) rotate(90)">
            <rect class="fronte" width="70" height="100" rx="5" />
            <rect class="fronte-filo" x="3.5" y="3.5" width="63" height="93" rx="3" />
            <use class="seme-rosso" href="#pip-heart" transform="translate(35 51) scale(1.5)" />
            <text class="indice seme-rosso" x="9" y="16">K</text>
            <use class="seme-rosso" href="#pip-heart" transform="translate(10.5 24) scale(.32)" />
            <g transform="rotate(180 35 50)">
              <text class="indice seme-rosso" x="9" y="16">K</text>
              <use class="seme-rosso" href="#pip-heart" transform="translate(10.5 24) scale(.32)" />
            </g>
          </g>
        </symbol>
        <symbol id="front-q" viewBox="0 0 100 70">
          <g transform="translate(100 0) rotate(90)">
            <rect class="fronte" width="70" height="100" rx="5" />
            <rect class="fronte-filo" x="3.5" y="3.5" width="63" height="93" rx="3" />
            <use class="seme-rosso" href="#pip-diamond" transform="translate(35 50) scale(1.6)" />
            <text class="indice seme-rosso" x="9" y="16">Q</text>
            <use class="seme-rosso" href="#pip-diamond" transform="translate(10.5 24) scale(.32)" />
            <g transform="rotate(180 35 50)">
              <text class="indice seme-rosso" x="9" y="16">Q</text>
              <use class="seme-rosso" href="#pip-diamond" transform="translate(10.5 24) scale(.32)" />
            </g>
          </g>
        </symbol>
        <symbol id="front-j" viewBox="0 0 100 70">
          <g transform="translate(100 0) rotate(90)">
            <rect class="fronte" width="70" height="100" rx="5" />
            <rect class="fronte-filo" x="3.5" y="3.5" width="63" height="93" rx="3" />
            <use class="seme-nero" href="#pip-club" transform="translate(35 50) scale(1.45)" />
            <text class="indice seme-nero" x="9" y="16">J</text>
            <use class="seme-nero" href="#pip-club" transform="translate(10.5 24) scale(.32)" />
            <g transform="rotate(180 35 50)">
              <text class="indice seme-nero" x="9" y="16">J</text>
              <use class="seme-nero" href="#pip-club" transform="translate(10.5 24) scale(.32)" />
            </g>
          </g>
        </symbol>
      </defs>
    </svg>

    <header class="testa">
      <p class="sopra" data-head>Ottoline Vane presents</p>
      <h1 class="titolo" data-head>The House of Cards</h1>
    </header>

    <!-- the table, turned a little so the backs of the cards show; the script deals the cards onto it -->
    <div class="palco" data-view>
      <div class="mondo" data-world>
        <div class="tavolo" data-table></div>
      </div>
    </div>

    <!-- the puff of air that knocks it down -->
    <svg class="soffio" data-gusts aria-hidden="true" xmlns="http://www.w3.org/2000/svg"></svg>

    <p class="nota" aria-live="polite"><span data-hint>Dealing the house</span></p>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/matter-js@0.20.0/build/matter.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

Playground

Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.

25 values to change

Or ask Claude Code for it

With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:

Take “The house of cards” from Animorio and fit it into my site

What it does

Onto a green felt table, seen a little from above and turned to one side, come the cards of a made-up deck, the one of the magician Ottoline Vane: a dark red back with a lattice of diamonds, a gold frame and a moon with stars in an oval. They come from above one at a time and glide like leaves, rocking and swaying from side to side. Two cards stop just above their place, then drop together and lean on each other with a tremble; when a row of pairs is ready, a flat card settles on top, with a tiny bounce, and the pairs underneath shiver. So, storey by storey, a four-storey house of twenty-six cards goes up; a few show their front, an ace or a court letter with its suit drawn on it. Moving the mouse slowly over the house makes it all lean a little and come back with an elastic sway. A click, a tap or a quick sweep of the mouse (the puff) brings it down: three wisps of air pass over the cards, and on that side the cards let go first, in a wave; they slide on the felt, turn, glide and pile up. After a moment each card lifts out of the pile and glides back to its place, and the house builds itself again, faster than the first time.

How it works

Real cards in 3D

The table and the cards live in a CSS 3D world: a container with transform-style preserve-3d, turned with rotateX and rotateY inside a stage with perspective. Every card is a div as long as the card and as deep as it is wide, with two faces back to back (backface-visibility hidden): rotate gives the card its lean, rotateX(90deg) stands it on its edge, so the back shows at three quarters. The drawings (back, ace and court cards, suits) are SVG symbols of our own, placed with use.

One set of numbers per card

Every card is an object with x, y, z, lean and opacity: GSAP moves those numbers, the physics overwrites them, and on every tick of the ticker a single function copies them into the transforms. Under every card lies a shadow on the felt, as wide as the card seen from above and fainter the higher the card is; and an upright card is darker than one lying flat, which catches the light of the lamp.

Gliding cards

The places in the house are worked out in card lengths: the lean decides how far apart the feet of a pair are, the flat card rests on the two nearby tips, the next storey stands on it. Every card arrives with a timeline: x through keyframes that swing to the side, y slowing down, the lean rocking and dying away. The two cards of a pair stop a moment above their place, drop together and tremble with small keyframes around the right pose.

The fall with Matter.js

On the click every card becomes a thin rectangle in Matter.js, held still (static) where it is. The cards are let go in a wave, the nearest to the hit first, with a shove that gets weaker with distance but is never zero, so the whole thing comes down. The felt is slippery at first, then grips and the pile stays put. On every step the speed across the card is braked and the speed along it is not: a card lying flat falls slowly and glides sideways, like real ones.

The puff and the rebuild

A quick sweep of the mouse over the house (more than 2.6 pixels per millisecond) does the same as a click, in the direction of the movement; a slow one only makes it lean. The physics moves in fixed steps of 1/120 of a second, and when the cards are still (or after a few seconds) the Matter world is emptied. A moment later a new timeline lifts each card out of the pile towards us and brings it back to its place with the same flight as the first time, faster.

Values to change

NameFileDefaultWhat it changes
LEVELSscript.js4The storeys of the house, 2 to 5: four storeys take twenty-six cards.
LEANscript.js18How many degrees each card leans from upright.
DEAL_SPEED / FLUTTERscript.js1 / 1The speed of the building and how much the cards rock as they glide.
BLOW / SLIDEscript.js1 / 0.03The strength of the blow and how slippery the felt is while it falls.
RESTscript.js1.6The seconds the pile lies there before the house builds itself again.
TURNscript.js28How many degrees the table is turned, to show the backs of the cards.
--back, --gold, --feltstyle.css#8f1f2c, #e7c47a, #1d5446The back of the cards, the gold of the drawings and the felt of the table.

Try them in the Playground

When to use it

For the page of a magician, a board-game club or a card room, for a 404 page (everything fell down here) or a page about fragility and risk. It is a toy to watch: the real content goes next to it.

On phones

The same animations and the same physics: the cards are sized to the screen, so on a phone the house fits in width and in height. The puff is mouse only; with a finger a tap is enough.

With reduced motion

The house is already built. A click fades it out and the pile appears in its place, already still (the physics is worked out all at once); the next click, again with a fade, brings the house back.

Pitfalls

  • A house of rectangles in Matter.js, simply let go, stays standing: the engine is good at keeping stacks still. To bring it down every card needs a shove (even a small one) and the felt has to be slippery for the first second.
  • The cards must not touch when the physics starts: two rectangles overlapping by even one pixel push each other away at once. The poses leave half a card's thickness between the two tips.
  • A thin, fast card can pass through another in a single step: small fixed steps (1/120 of a second) and more iterations almost always prevent it.
  • The physics is driven by GSAP's ticker, not by a requestAnimationFrame of its own: that way it slows down and stops together with the timelines, and a watchdog made with gsap.delayedCall ends the fall even if one card keeps creeping.
  • In CSS 3D two crossing planes cut through each other: the cards all sit on the same z plane and the physics keeps them from overlapping, so it does not happen.

Libraries

GSAP 3.15.0, Matter.js 0.20.0, Bodoni Moda (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 11 KB; the libraries: GSAP 28 KB, Matter.js 25 KB.

Testing

Tested on 10 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.

All animations