Skip to content
animorio
IT
All components

The popcorn cart button

Buttons, Physics, HoverCanvas, GSAPAdvancedCompressed weight: Demo 12 KBGSAP 28 KBFreeDownload the zip

The Get tickets of a small cinema, in the rich style: in the round end of the red pill stands a popcorn cart with its glass case, its striped canopy and its steel kettle. On a click the kernels pop one after the other, hit the glass and pile up, the kettle tips, the little door opens and the popcorn spills over the pill; then a torn ticket comes out from behind it.

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 popcorn cart button</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/mona-sans@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the popcorn cart button: the Get tickets of a small cinema, with a popcorn cart standing in the round left end
         of the pill. Glass case with a gold frame, a striped canopy, a warm lamp and a steel kettle hanging inside.
         A click heats the kettle: the kernels pop one after the other, fly against the glass and pile up; the kettle
         tips to empty itself, the little door swings open and the popcorn spills onto the pill, one piece bouncing
         away. Then a torn ticket rises from behind the pill: Enjoy the show. A second click sweeps it all away -->
    <div class="pop" data-pop>
      <span class="pop__ticket" aria-hidden="true" data-ticket>
        <svg viewBox="0 0 92 46" width="92" height="46">
          <defs>
            <linearGradient id="tk-paper" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0" style="stop-color:var(--ticket)"/>
              <stop offset="1" style="stop-color:var(--ticket-deep)"/>
            </linearGradient>
          </defs>
          <path d="M4 3 H80 L84 6 L80 9 L84 12 L80 15 L84 18 L80 21 L84 24 L80 27 L84 30 L80 33 L84 36 L80 39 L84 42 L80 43 H4 a3 3 0 0 1 -3 -3 V6 a3 3 0 0 1 3 -3 Z" fill="url(#tk-paper)"/>
          <path d="M80 3 H4 a3 3 0 0 0 -3 3 V40 a3 3 0 0 0 3 3 H80" fill="none" style="stroke:var(--gold)" stroke-opacity="0.55" stroke-width="0.8"/>
          <rect x="5" y="7" width="67" height="9" rx="1.5" style="fill:var(--red)"/>
          <text x="38.5" y="14" text-anchor="middle" class="pop__ticket-band">ADMIT ONE</text>
          <text x="38.5" y="29" text-anchor="middle" class="pop__ticket-film" data-ticket-film>Velvet Hour</text>
          <text x="38.5" y="38" text-anchor="middle" class="pop__ticket-seat" data-ticket-seat>Screen 2 · Row F · Seat 7</text>
          <path d="M70 7 v32" style="stroke:var(--red)" stroke-opacity="0.35" stroke-dasharray="1.5 2" stroke-width="0.8"/>
        </svg>
      </span>
      <button class="pop__btn" type="button" data-btn data-autoclic>
        <span class="pop__fill" aria-hidden="true"></span>
        <span class="pop__faces">
          <span class="pop__face" data-face="rest">
            <span class="pop__word" data-label-text>Get tickets</span>
            <span class="pop__time" data-time-text>Fri 9:40</span>
          </span>
          <span class="pop__face pop__face--busy" data-face="busy" aria-hidden="true">
            <span class="pop__shine" data-busy-text>Popping…</span>
          </span>
          <span class="pop__face pop__face--done" data-face="done" aria-hidden="true">
            <span class="pop__done" data-done-text>Enjoy the show</span>
          </span>
        </span>
      </button>
      <canvas class="pop__art" aria-hidden="true" data-art></canvas>
      <p class="sr" aria-live="polite" data-live></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.

35 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 popcorn cart button” from Animorio and fit it into my site

What it does

On a white background a cinema-red pill with a gold thread along its edge, the words Get tickets and the show time Fri 9:40 in gold. In its round left end stands a popcorn cart: two night-blue wheels with gold spokes and hubs, the red cabinet with a gold band and a POPCORN plate, the glass case with a gold frame and reflections on the glass, the warm light of a lamp inside, and on top a red and cream striped canopy with a scalloped hem and a pennant flying. Inside the case, hanging from a rod, is a steel kettle with two lids, a lever with a red knob and the kernels showing at its rim. Under the mouse the kettle trembles on its rod, the kernels rattle and the lamp brightens. On a click the words turn to Popping… with a sheen running through them, the foot of the kettle glows orange, the lids lift and the kernels pop one at a time, faster and faster: each starts as a hard seed and puffs out into an uneven white cloud with a shadow, a little butter and a fleck of hull, flies out of the kettle, hits the glass and piles up on the floor of the case. Then the kettle tips on its rod and pours the last ones out to the right, and the glass door swings open on its hinge: the popcorn spills over onto the pill and the first piece bounces away along its edge. From behind the pill a torn ticket rises, ADMIT ONE, Velvet Hour, with the row and the seat, and the words become Enjoy the show. A second click lowers the ticket, sweeps the popcorn away and closes the door.

How it works

The cart on a canvas

The cart, the kettle, the door and the popcorn are drawn on a canvas that covers the component and 40 pixels above it, for the pennant and the flying pieces: spoked wheels, the cabinet with its shading and plate, the warm back of the case with the lamp's cone, the striped canopy cut as a trapezoid with a scalloped hem, the slanting reflections on the glass and the gold frame drawn after the popcorn, so the popcorn sits behind the glass. The pill, the words and the ticket are HTML and SVG under the canvas, which takes no pointer.

Popcorn that is not a circle

Each piece has five to seven lobes around a middle one, with random distances and sizes, drawn three times: the shade moved down, the white, butter on some lobes and light only on the top ones, plus a fleck of hull. When it is born it starts at a quarter of its size, with the orange seed showing, and puffs out in 0.22 seconds with back.out(3.2): the pop.

The physics

For the knocks each piece is a circle of 5.2 pixels: GRAVITY, BOUNCE off the walls of the glass and the floor, friction on the floor, and between pieces three passes of separation and a knock between equal masses, so the pile stands up. The frame's step is capped at 1/30 of a second and split in two. When the door is open the right wall below the hinge is gone: the pieces come out, and outside the ground is the edge of the pill, round at its left end. All the chance comes from a generator with a fixed seed, so every show is the same.

The direction

A GSAP timeline heats the kettle, opens the lids and calls the pops at shrinking intervals; then it tips the kettle TILT degrees around its rod, pouring the last kernels out with a push to the right, opens the door with back.out and gives the piece nearest the way out a push so it bounces far. At the end the ticket rises from behind the pill with back.out(2.2) and a 7 degree turn. SPEED changes the timeScale of it all.

A real button

It is a button: Enter and Space do the same as a click, the keyboard focus has its ring and makes the kettle tremble like the mouse, aria-busy and aria-disabled while it pops, and an aria-live region says Popping your popcorn and then Tickets ready. The three sets of words sit on top of each other and the pill is as wide as the longest, measured once the typeface has arrived.

Values to change

NameFileDefaultWhat it changes
LABELscript.jsGet ticketsThe words at rest.
TIMEscript.jsFri 9:40The show time beside them.
BUSYscript.jsPopping…The words while the popcorn pops.
DONEscript.jsEnjoy the showThe words once the ticket is out.
FILMscript.jsVelvet HourThe film printed on the ticket.
SEATscript.jsScreen 2 · Row F · Seat 7The screen, row and seat on the ticket.
KERNELSscript.js34How many kernels pop, from 16 to 40.
GRAVITYscript.js1500How hard the popcorn falls, in pixels a second squared.
BOUNCEscript.js0.32How much the popcorn bounces.
TILTscript.js34How far the kettle tips to empty itself, in degrees.
SPEEDscript.js1How fast the whole show runs.

Try them in the Playground

When to use it

For the tickets button of a cinema, a drive-in, an open-air film season or a movie night, or for ordering the snack in a cinema app.

On phones

The component is as wide as its words, between 300 pixels and the screen minus 32, and 140 tall with a 60 pixel pill. With no hover, the finger resting on it makes the kettle tremble like the mouse, and the tap does not wait for a double tap.

With reduced motion

No flying pops, no trembling and no door swinging open: on a click the pile of popcorn appears already still in the case with a fade, and the ticket appears in its place without rising. The words change by fading.

Pitfalls

  • Popcorn drawn as circles looks like a handful of balls: it needs a lobed shape, and the lobes must be drawn in a single path for shade, white and butter, or each lobe covers its neighbour's shade.
  • The popcorn in the case must be drawn before the reflections and the frame, and clipped inside the glass: that way it sits behind the glass, while the spilled popcorn is drawn after the cart, in front of everything.
  • The canvas also covers the pill: it needs pointer-events none, or the click would never reach the button.
  • With Math.random every show was different and the preview video never came back the same: the generator has a fixed seed that restarts on every click.
  • The ticket waits behind the pill: it is shorter than the pill, so at rest it cannot be seen, and its z-index is below the button's.

Libraries

GSAP 3.15.0, Mona Sans (font variabile) 5.3.0.

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

Testing

Tested on 22 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 components