
Tiles that rise
SVG, Hover, Physics
NewFree
A small pinball table seen from above and tilted like a real one, all in SVG: the steel ball follows physics (gravity on the slope, bounces, pop bumpers that kick it with a full flash, drop targets that fall, a ramp to climb) and the two flippers snap up with a click or the keys. On its own an autopilot plays; the score rolls like a mechanical counter.
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>Night Comet pinball</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/unbounded@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage" data-stage>
<!-- the backbox: the name of the table, the rolling score and a strip for messages -->
<header class="backbox" data-backbox>
<p class="backbox__title">Night Comet</p>
<div class="score" data-score role="img" aria-label="Score"></div>
<p class="message" data-message aria-live="polite"></p>
</header>
<!-- the table, tilted away from you: the playfield is one SVG, 440 by 800 units -->
<div class="tilt" data-tilt>
<div class="tilt__plane">
<svg class="table" data-table viewBox="0 0 440 824" role="img" aria-label="A small pinball table with three pop bumpers, five drop targets, a ramp and two flippers">
<defs>
<radialGradient id="pf" cx="50%" cy="38%" r="75%">
<stop offset="0" class="stop-pf-a"/>
<stop offset="1" class="stop-pf-b"/>
</radialGradient>
<radialGradient id="steel" cx="38%" cy="32%" r="72%">
<stop offset="0" stop-color="#ffffff"/>
<stop offset="0.35" stop-color="#d9dde6"/>
<stop offset="0.75" stop-color="#7d8496"/>
<stop offset="1" stop-color="#3b4152"/>
</radialGradient>
<linearGradient id="tail" x1="0" y1="1" x2="1" y2="0">
<stop offset="0" class="stop-tail-a"/>
<stop offset="1" class="stop-tail-b"/>
</linearGradient>
<filter id="soft" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="3"/>
</filter>
<clipPath id="field">
<path d="M20 800 L20 220 A200 200 0 0 1 420 220 L420 800 Z"/>
</clipPath>
</defs>
<g data-art>
<path class="playfield" d="M20 800 L20 220 A200 200 0 0 1 420 220 L420 800 Z"/>
<g clip-path="url(#field)">
<!-- the comet: a head and three streaks of tail -->
<path class="comet__tail" d="M188 506 C240 470 300 420 384 300 C320 390 262 440 206 490 Z"/>
<path class="comet__tail comet__tail--thin" d="M176 494 C210 440 250 380 300 300 C270 370 236 420 196 486 Z"/>
<path class="comet__tail comet__tail--thin" d="M200 520 C260 500 320 470 388 420 C330 480 270 512 214 530 Z"/>
<path class="comet__head" d="M190 478 C194 500 202 508 224 512 C202 516 194 524 190 546 C186 524 178 516 156 512 C178 508 186 500 190 478 Z"/>
<path class="comet__core" d="M190 496 C192 506 196 510 206 512 C196 514 192 518 190 528 C188 518 184 514 174 512 C184 510 188 506 190 496 Z"/>
<!-- the orbit rings drawn round the bumpers -->
<ellipse class="orbit" cx="204" cy="282" rx="118" ry="78"/>
<ellipse class="orbit orbit--dash" cx="204" cy="282" rx="150" ry="104"/>
<!-- a few stars, drawn as four-point sparkles -->
<g class="stars">
<path d="M300 520 l3 -9 l3 9 l9 3 l-9 3 l-3 9 l-3 -9 l-9 -3 Z"/>
<path d="M120 560 l2 -6 l2 6 l6 2 l-6 2 l-2 6 l-2 -6 l-6 -2 Z"/>
<path d="M330 130 l2 -6 l2 6 l6 2 l-6 2 l-2 6 l-2 -6 l-6 -2 Z"/>
<path d="M150 150 l2 -7 l2 7 l7 2 l-7 2 l-2 7 l-2 -7 l-7 -2 Z"/>
<path d="M250 610 l2 -6 l2 6 l6 2 l-6 2 l-2 6 l-2 -6 l-6 -2 Z"/>
<path d="M78 600 l1.6 -5 l1.6 5 l5 1.6 l-5 1.6 l-1.6 5 l-1.6 -5 l-5 -1.6 Z"/>
</g>
<text class="label" x="93" y="458" text-anchor="middle">ORBIT</text>
<text class="label" x="204" y="408" text-anchor="middle">POP</text>
</g>
<!-- the apron at the bottom, with the drain between the flippers -->
<path class="apron" d="M20 560 L121 672 L140 740 L158 800 L20 800 Z"/>
<path class="apron" d="M388 560 L287 672 L268 740 L250 800 L388 800 Z"/>
<text class="apron__name" x="72" y="770" text-anchor="middle">NIGHT</text>
<text class="apron__name" x="334" y="770" text-anchor="middle">COMET</text>
</g>
<g data-walls>
<path class="rail" d="M20 800 L20 220 A200 200 0 0 1 420 220 L420 792"/>
<path class="rail rail--thin" d="M388 262 L388 800"/>
<path class="rail rail--thin" d="M20 560 L121 672"/>
<path class="rail rail--thin" d="M388 560 L287 672"/>
<rect class="lane" x="390" y="262" width="28" height="550"/>
<circle class="post" cx="68" cy="426" r="5"/>
<circle class="post" cx="118" cy="426" r="5"/>
<path class="lip" d="M72 408 L114 408"/>
<path class="rail rail--thin" d="M388 800 L388 812 M420 792 L420 812"/>
<rect class="lockbar" x="12" y="812" width="416" height="11" rx="4"/>
</g>
<g data-parts></g>
<g data-ball-low></g>
<g data-ramp></g>
<g data-ball-high></g>
<g data-pops></g>
</svg>
</div>
</div>
<p class="hint" data-hint>Click the left or right half to flip. Keys: Z and M, or the arrows.</p>
</main>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="script.js"></script>
</body>
</html>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.
39 values to change
With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:
Take “The pinball table” from Animorio and fit it into my siteThe table is an SVG of 440 by 800 units, and the physics uses the same units: no conversions. The tilt is plain CSS: the container has perspective, the plane inside has rotateX with its origin on the bottom edge, so the top recedes. The clicks do not need to know where the ball falls on the screen: all that matters is whether they are left or right of the middle of the page.
The ball moves in steps of 1/480 of a second, as many as fit into the frame time (requestAnimationFrame, with an accumulator). On every step gravity adds downward speed, then the ball moves and the collisions are checked: segments (rails, slingshots, targets, the arch made of forty pieces), circles (bumpers and posts) and the flippers. For each one the closest point is found: if it is closer than the radius, the ball is put back outside and the part of its speed going into the surface is reversed, damped. With such small steps the ball never passes through a rail.
A flipper is a segment with round ends, thicker at the pivot. It swings up and down at a fixed speed towards its angle, and its angular speed gives the speed of the point touching the ball (perpendicular to the radius, proportional to the distance from the pivot): the bounce is worked out against that moving point. That is why a ball hit near the tip leaves harder and in another direction than one hit near the pivot. The bumpers instead guarantee a minimum speed going out, and the slingshots add a push along their edge.
The ramp is a curve of four Béziers, sampled and measured once. When the ball enters the mouth going up, it leaves the playfield and moves along the curve: its speed drops going up and grows coming down according to the height of the ramp at that point, like a real ball. If it stops before the top it rolls back and comes out of the mouth. While it is on the ramp it is drawn in another layer, above the plastic, bigger and with its shadow moved away. A target hit hard enough stops counting in the collisions; when all five are down they rise again after a moment, if the ball is not in front of them.
The autopilot watches where the ball is along the flipper and flips when it reaches the spot picked at random (or gets near the tip), holds it up for a fifth of a second and then waits. The chance comes from a seeded generator: the same seed gives the same game, so the first and second rounds are identical and the preview video closes by itself. The score is seven columns of digits from 0 to 9 plus one more 0: GSAP takes each column to its whole number (the hundreds of 12,300 are 123), and the shift shown is that number modulo 10, so the digits roll through every one in between and the carry turns over by itself.
| Name | File | Default | What it changes |
|---|---|---|---|
GRAVITY | script.js | 1 | How steep the table is: 0.6 is slow and floaty, 1.6 fast and heavy. |
LAUNCH | script.js | 1 | How hard the plunger shoots the ball up. |
BUMPER | script.js | 1 | How hard the pop bumpers and the slingshots kick: 0.2 barely, 2 twice as hard. |
FLIPPER | script.js | 1 | How fast, and so how hard, the flippers snap up. |
ROUND | script.js | 18 | How many seconds the autopilot plays before it lets the ball drop and starts again. |
TILT | script.js | 26 | How far the table leans away from you, in degrees (0 = seen straight from above). |
For a gaming page or an arcade bar with pinball machines, an arcade-themed event, a 404 page you can play, an app's waiting screen, the site of a studio that wants to show physics in the browser. The collision model (segments, circles, moving flippers) also works for other SVG table games.
The same table and the same physics, with the sizes adapted: the table takes all the free height under the backbox and the type gets smaller. A tap on the left or right half of the screen raises the flipper on that side, and two fingers raise both.
The ball does not travel: the table fades in and every two and a half seconds the ball fades out and back in at a new spot (a bumper lighting up, the targets down with their letters lit, the ramp), with the score already updated. The flippers go up on a click with no motion.
GSAP 3.15.0, Unbounded (font variabile) 5.3.0.
Compressed with gzip, the three demo files weigh 15 KB; the libraries: GSAP 28 KB.
Tested on 10 October 2026
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.
With your account’s email and password.
or with your email
You’re signed in as .
With your account, you can download the zip of every animation and every pack, for free.