Skip to content
animorio
IT
Back to the library

The tilt maze

Physics, 3DGSAP, CSS onlyAdvancedCompressed weight: Demo 15 KBGSAP 28 KBFreeDownload the zip

The old wooden toy with the maze and the steel ball, all in CSS 3D: the pointer tilts the board on its two axes and the ball rolls with real physics (gravity on the tilted board, bounces off the walls, friction). The holes swallow it with a small whirl, the brass cup at the end sets paper confetti flying. On its own, the board tilts and steers the ball along the line.

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 tilt maze</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/index.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage" data-stage>
    <header class="shop">
      <p class="shop__name" data-shop>Marlow &amp; Pip Toyworks</p>
      <p class="shop__line">The tilt maze, hand-cut in maple</p>
    </header>

    <!-- the toy: a box that stays still, the maze board that tilts inside it, the steel ball on the board -->
    <div class="scene" data-scene>
      <div class="fit" data-fit>
        <div class="intro" data-intro>
          <div class="table" data-table role="img" aria-label="A wooden tilt maze with a steel ball, holes along the way and a brass cup at the end">
            <div class="box" data-box>
              <div class="lift" data-lift>
                <div class="board" data-board>
                  <div class="board__top" data-top>
                    <svg class="art" data-art aria-hidden="true"></svg>
                    <div class="holes" data-holes></div>
                    <svg class="whirl" data-whirl viewBox="-30 -30 60 60" aria-hidden="true">
                      <path d="M0 -22 C14 -22 22 -12 20 0 C18 10 8 14 0 12"/>
                      <path d="M19 11 C12 23 -2 25 -12 17 C-19 10 -16 0 -9 -3"/>
                      <path d="M-19 11 C-25 -2 -18 -16 -6 -20 C3 -23 9 -15 7 -8"/>
                    </svg>
                    <i class="shadow" data-shadow></i>
                  </div>
                  <div class="ball" data-ball><i class="ball__shine"></i></div>
                </div>
              </div>
            </div>
            <!-- the knobs that turn the two axles (they are moved onto the front of the box by the script) -->
            <span class="knob" data-knob><i class="knob__cap"></i></span>
            <span class="knob knob--right" data-knob><i class="knob__cap"></i></span>
          </div>
        </div>
      </div>
    </div>

    <p class="hint" data-hint aria-live="polite"></p>

    <!-- what flies when the ball reaches the cup -->
    <div class="confetti" data-confetti aria-hidden="true"></div>
    <div class="win" data-win>
      <p class="win__shop" data-shop>Marlow &amp; Pip Toyworks</p>
      <p class="win__title" data-win-title>Rolled home!</p>
      <p class="win__line">Hand-cut maze boards. Steel balls included.</p>
    </div>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.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.

41 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 tilt maze” from Animorio and fit it into my site

What it does

On a linen-coloured table stands a walnut box, seen from the front and a little from above, with the name of a made-up toy shop, Marlow & Pip Toyworks, written in brass between two knobs. Inside the box is the maple board with its grain, the birch walls, nine holes, a printed line that runs through the maze, a ring marked START at the top left and a brass cup at the bottom right. On opening the box rises and settles with a bounce, the board drops into place, the walls pop up out of the wood one after another, the line draws itself, the holes open and the knobs make an elastic turn. Then the steel ball falls from above onto the start and bounces on the wood, its shadow shrinking as it lands. Moving the pointer tilts the board towards it (up to 6 degrees each way), the knobs turn with it and the ball rolls towards the side that goes down: it picks up speed, knocks against the walls losing some of it (the board gives a little jolt when the knock is hard) and slows down with friction. Near a hole the rim pulls it in: the ball spirals towards the middle, gets smaller and darker, a small whirl of three pale strokes spins over the hole, the line under the box says «Swallowed!» and a new ball drops onto the start. If it reaches the cup, the ball settles, the brass ring rings with an elastic wobble, a burst of coloured paper strips shoots out of the cup and flutters back down spinning, and a shop tag arrives above the board with the title «Rolled home!», its letters hopping up one after another. After a few seconds the tag goes and a new ball drops onto the start. With nobody steering, the board tilts by itself: the first ball of every round goes straight into the hole in the left column, the second follows the whole line to the cup. The arrow keys work too; once the board is let go, after two seconds the autopilot picks the ball up from where it is.

How it works

A real box in CSS 3D

Each wall is a block with five faces (the top and the four sides), made of divs turned 90 degrees round one edge and with backface-visibility hidden: the ones turned away are never drawn. The walnut box is built the same way and stays still; the board, with its walls and the ball, sits in a container with transform-style: preserve-3d that turns with rotateX and rotateY. Everything is measured in board units (580 by 400), the same ones the physics uses; a scale on the outer container fits it to the screen and the perspective grows with it.

Gravity projected on the board

The physics moves in fixed steps of 1/240 of a second inside GSAP's ticker. On every step the ball gets the part of gravity that runs along the board: the sine of the tilt on each of the two axes, times 5/7 for a rolling sphere. Then rolling friction (a steady brake) and a little drag are taken off, and the ball moves. Inside the rim of a hole an extra force pulls it towards the middle, like the wood sloping down: once its centre goes past the edge, the ball is in.

Knocks against the walls

For each wall the point of the rectangle closest to the centre of the ball is found; if it is closer than the radius, the ball is put back outside along that direction and the part of its speed going into the wall is reversed, damped by BOUNCE, while the part along the wall loses 4%. Steps this small stop the ball from going through a wall even when it is fast. The hardest knock of each frame gives the board an elastic jolt of a few tenths of a degree, in the drawing only.

A ball that always faces you

The ball is a circle with a steel gradient, the warm reflection of the wood below and a window at the top. It lives inside the tilted board, at its height with translate3d, but with the turns of the board and the box applied backwards (rotateY and rotateX with the sign flipped, in reverse order): so it always stays round towards you, and the walls in front hide it as they would a sphere. The shadow instead is drawn on the wood, further away and softer when the ball falls from above.

The autopilot and the direction

The autopilot has a list of points, the middle of each square along the line moved away from the holes nearby. On every step it works out an acceleration like a spring towards the point plus a damper on the speed, turns it into the tilt that would give it (arcsine, capped at MAX_TILT) and the board follows with a little lag, like on two real axles. Near the point it moves on to the next. The first ball of the round has the hole as its last point: that way the video shows the whirl too. The whirl, the fall, the cup, the confetti and the tag are GSAP timelines; everything always starts from the same state, so each round is identical to the one before.

Values to change

NameFileDefaultWhat it changes
MAX_TILTscript.js6How far the board can tilt, in degrees: a little makes a calm game, a lot a jumpy one.
GRAVITYscript.js1How heavy the ball feels: 0.6 slow and floaty, 1.6 fast.
FRICTIONscript.js1How much the wood slows the ball down: 0 is ice, 3 is felt.
BOUNCEscript.js0.34How much speed the ball keeps after hitting a wall (0 stops dead, 0.9 bounces a lot).
VIEWscript.js34How far the box leans away from you, in degrees (0 = seen straight from above).
SHOPscript.jsMarlow & Pip ToyworksThe shop's name, on the box and on the tag.
TITLEscript.jsRolled home!The title when the ball reaches the cup.
CONFETTIscript.js72How many strips of paper fly out of the cup.

Try them in the Playground

When to use it

For the site of a toy shop or a woodworker, a 404 page you can play, an app's waiting screen, a contest or a surprise gift that opens when the ball reaches the cup, the site of a studio that wants to show what CSS 3D can do. The physics (projected gravity, knocks against rectangles, holes with a rim) works for any table game with a ball.

On phones

The same box, the same physics and the same autopilot, with the sizes adapted: the box shrinks to fit the width of the screen, and the perspective with it. You tilt the board by dragging a finger: wherever the finger is compared with the middle of the box, that is where the board goes down; when the finger lifts, the autopilot takes over again.

With reduced motion

No motion: the box fades in with the game already done, the board level, the ball resting in the brass cup, and the «Rolled home!» tag fades in too. No confetti and no tilting.

Pitfalls

  • Opacity on a container with preserve-3d flattens it: during the fade-in the walls were drawn in the wrong order. The fade is on the scene (the one with the perspective), the motion on its child.
  • A flat circle inside the tilted board turns into an ellipse: the ball has the turns of the board and the box applied backwards, in reverse order, so it stays round.
  • The vertical and horizontal walls cross at the same point with their tops at the same height: two identical overlapping planes flicker. The vertical ones are six tenths of a unit taller.
  • The preview video closes when a moving frame comes back the same as an earlier one: with a random autopilot the round never came back the same. The autopilot has nothing random in it, so the new ball after the cup drops exactly like the one after the hole and the video closes there, with the whole round inside (entrance, hole, cup, confetti, new ball).
  • With time steps worked out from the frame, the accumulator changed slightly from one round to the next and the rounds were no longer identical: the steps are counted with a small margin and the accumulator goes back to zero with every new ball.
  • The physics runs in GSAP's ticker but the video waits for scheduled GSAP animations: every ball starts an empty thirty-second call that keeps the direction open.
  • With the board tilted, the corner going down went lower than the floor of the box: the floor cut through the board in 3D and hid part of it under a dark wedge. Now the box is as deep as the lowest corner needs at full tilt on both axes (plus the thickness of the board), and the depth is worked out from MAX_TILT.

Libraries

GSAP 3.15.0, Fraunces (font variabile) 5.3.0, Bricolage Grotesque (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 15 KB; the libraries: GSAP 28 KB.

Testing

Tested on 11 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