Skip to content
animorio
IT
All components

The ticket that becomes the hall

Tickets, SVG, Physics, TransitionsGSAPAdvancedCompressed weight: Demo 14 KBGSAP 28 KBFreeDownload the zip

The ticket of a concert in an open-air amphitheatre: shown at the door, the screen goes to full brightness and the scanner's red line reads the code. Once you are in, the code has done its job: its bars lift off the paper and bend, row after row, into the plan of the hall, with the stage, your seat in amber and the way to 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 ticket that becomes the hall</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/imbue@5.3.0/opsz.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/schibsted-grotesk@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the ticket of a concert in an open-air amphitheatre, as the app that sold it shows it. Shown at the door,
         the screen brightens for the scanner and a red line of light reads the barcode; once you are in, the bars
         of the barcode lift off the paper and bend into the plan of the hall, with your seat and the way to it.
         The bars, the hall and the icons are drawn by the script -->
    <section class="pass" aria-label="Concert ticket" data-pass>
      <div class="pass__glow" aria-hidden="true" data-glow></div>

      <article class="ticket" data-ticket>
        <header class="head">
          <p class="head__venue">
            <svg class="head__mark" viewBox="0 0 24 24" aria-hidden="true">
              <path d="M2.5 6.5h19M8.6 6.5a3.4 3.4 0 0 0 6.8 0M5.4 6.5a6.6 6.6 0 0 0 13.2 0M2.5 6.5a9.5 9.5 0 0 0 19 0"/>
            </svg>
            <span>Cove Amphitheatre</span>
          </p>
          <p class="head__date"><time datetime="2026-10-10T20:45">Sat 10 Oct</time></p>
          <h2 class="head__artist">Ilse Varga</h2>
          <p class="head__tour">The Tide Sessions</p>
          <!-- the moon rising on the sea, behind the empty side of the head -->
          <svg class="head__sea" viewBox="0 0 120 64" aria-hidden="true">
            <path class="head__moon" d="M60 50a24 24 0 0 1 48 0z"/>
            <path class="head__line" d="M0 50.5h120"/>
            <path class="head__wave" d="M52 55h64M64 59h40M76 63h16"/>
          </svg>
        </header>

        <!-- the entrance, the row and the seat are written by the script, from ROW and SEAT in script.js -->
        <dl class="facts">
          <div class="fact"><dt>Doors</dt><dd>19:30</dd></div>
          <div class="fact"><dt>Show</dt><dd>20:45</dd></div>
          <div class="fact"><dt>Tier</dt><dd>Lower</dd></div>
          <div class="fact fact--big"><dt>Entrance</dt><dd data-door></dd></div>
          <div class="fact fact--big"><dt>Row</dt><dd data-row></dd></div>
          <div class="fact fact--big"><dt>Seat</dt><dd data-seat></dd></div>
        </dl>
        <p class="holder"><span>Leni Arnau</span><span>Adult, €48.00</span></p>

        <div class="perf" aria-hidden="true" data-perf></div>

        <div class="code" data-code>
          <p class="note" data-note>
            <span class="note__icon" aria-hidden="true" data-note-icon></span>
            <span class="note__text" data-note-text>Scan at Door 3 from 19:30</span>
          </p>
          <div class="code__box">
            <svg class="code__svg" viewBox="0 0 300 156" role="img" aria-label="Barcode of ticket 4021 8860 1427 39" data-svg></svg>
            <div class="laser" aria-hidden="true" data-laser><i class="laser__glow"></i><i class="laser__core"></i></div>
          </div>
        </div>
      </article>

      <button class="show" type="button" data-show data-autoclic>
        <span class="show__icon" aria-hidden="true" data-show-icon></span>
        <span class="show__label" data-show-label>Show at the door</span>
      </button>
      <p class="sr" aria-live="polite" data-live></p>
    </section>
  </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.

39 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 ticket that becomes the hall” from Animorio and fit it into my site

What it does

On a black background the ticket of a concert, as the app that sold it shows it. At the top, on the dark blue of the sea at night, Cove Amphitheatre with its mark, Sat 10 Oct, the name Ilse Varga in a narrow Bodoni, The Tide Sessions and a half moon rising on the sea; below, on stone-coloured paper, Doors 19:30, Show 20:45, Tier Lower and, large, Door 3, F and 14, the holder's name and the price; then the perforation with its two half-moon notches and the barcode with its number, under the line Scan at Door 3 from 19:30. Under the ticket, the Show at the door button. As it comes in, the bars drop onto the paper one after the other. Moving the pointer over the code, the bars near it come a little off the paper with their shadow, like keys brushed by a finger. Pressing Show at the door the screen goes to full brightness: the paper turns white and a glow lights up round the ticket in the dark; the scanner's red line of light settles on the code, trembling a little like a hand, red on the paper and invisible on the black bars, reads it with a flash and goes out. The line says Admitted at 19:42 with a tick that draws itself, and the ticket gives a small knock. Then the code is not needed any more: its bars lift off the paper, the back rows first, fly bending and become the plan of the amphitheatre: ten curved rows in three wedges split by two aisles, the back wall with Door 3 and Door 4, and the stage, which comes last as a thin arc and fills into a half disc with the word Stage. The rows split into seats, yours turns amber with a bounce and an amber dashed line draws the way from Door 3, up the aisle and along the row, to it; the line says Row F, seat 14: left aisle from Door 3. With the pointer over the plan, the row under it lifts. Back to the code plays it all backwards, and the code comes back with the line To re-enter, show it at Door 3.

How it works

A real Code 128

The ticket number, 14 figures, is written in a Code 128 with set C: the start symbol C, the figures two at a time, the check (each value times its place, modulo 103) and the stop symbol. Every symbol is a row of six widths from 1 to 4 modules (seven for the stop); a module is 2 pixels and the bars fall on whole pixels, so the code is sharp and a reader would really read it. It always comes out as 31 bars.

Every bar is an arc

Every bar is drawn as a piece of circle described by five numbers: the middle point, the direction there, the curvature (1 over the radius, 0 when straight), the length and the width. The ends come from the middle: x + (sin(a ± c·L/2) − sin a)/c and y + (cos a − cos(a ± c·L/2))/c, and the path is an SVG A command with a stroke as wide as the bar; with the curvature close to zero it becomes a line. So the same straight bar of the code bends, without changing element, into the arc of a row. The drawn turn stops a little past half a circle, or a bar still long and already very curved would wind round itself.

From the code to the hall

The hall has exactly 31 pieces: the stage, a half disc, and ten rows by three wedges. The ten bars on the left of the code become the left wedge (the outermost one the back row), the ten on the right the right wedge, the middle one the stage and the others the middle wedge, the ones nearest the centre at the front. An arc can be run either way: each bar takes the way that turns it least, and in the middle, where both ways are the same, they all turn the same way, so two bars crossing in flight never make an X.

The springs and the flight

Each of the nine quantities of a bar (middle, direction, curvature, length, width, the lift in flight, the lift under the pointer and the colour) has its own spring, a multiple of SPRING, worked out on GSAP's ticker in steps of 1/240 of a second with the frame capped at 1/30; when everything is still the loop switches off. A GSAP timeline only changes the targets: the back rows leave first, one every STAGGER seconds, so no bar crosses rows already in place; the stage leaves last, thin, and widens into its half disc after it has landed. In flight a bar rises by LIFT and grows a little, and its shadow, a group blurred with feGaussianBlur, stays on the paper.

The seats and the way

Once landed, the rows split into seats with stroke-dasharray: the same number of seats, with the gaps growing from 0 to 1.8 pixels and the seats getting shorter, in a wave from the stage outwards. Your seat comes from the row and the number (counted from the left across the wedges), together with the nearest aisle and its door; an amber arc a touch larger covers it with a bounce, and the dashed way draws itself through a mask: a solid stroke with pathLength 1 whose stroke-dashoffset goes from 1 to 0.

Full brightness and the scanner

Show at the door takes the --lum variable to 1: the paper goes from stone to white with color-mix, and a blurred white glow behind the ticket lights up in the dark, in proportion to GLOW. The scanner is a red line with its glow, in mix-blend-mode multiply over the code: red on the white paper and black on the black bars, like real light. It trembles with two sines like a hand for SCAN seconds, reads with a flash and goes out.

Values to change

NameFileDefaultWhat it changes
TICKETscript.js'40218860142739'The ticket number, 14 figures: the script writes it into a real Code 128 (with fewer figures it adds zeros).
ROWscript.js'F'Your row, from A by the stage to J at the back.
SEATscript.js14Your seat in the row, counted from the left: it also decides the aisle and the door.
ADMITTEDscript.js'19:42'The time the door lets you in.
SPRINGscript.js[150, 0.62]The bars' springs: how stiff they are and how much they are held back (1 = no bounce).
LIFTscript.js1How high the bars rise off the paper in flight (0 = they slide flat).
STAGGERscript.js0.045The seconds between one row of the hall and the next.
SCANscript.js0.7The seconds the scanner's light rests on the code before it reads it.
GLOWscript.js0.6How much the ticket shines in the dark while you show it at the door.

Try them in the Playground

When to use it

For a ticketing app for concerts, theatres, stadiums or cinemas; for the page that opens after you are in; for a conference pass with its room and seat; anywhere a code, once used, can turn into something useful.

On phones

The ticket is 340 pixels wide and fits a 390 screen; with a finger you tap the button and everything goes as with the mouse. The bars and rows lifting under the pointer are there only with a mouse: with a finger the button is enough.

With reduced motion

Nothing moves: full brightness is only the colour of the paper and the glow that lights up, the scanner appears and disappears still, and the code fades into the hall already formed, with the seats, your seat and the way already drawn; going back, the hall fades into the code. As it comes in the bars are already on the paper, and under the pointer they do not lift.

Pitfalls

  • The laser could not be seen: the mask that fades its ends sat on an element zero pixels tall, and a mask takes the size of its element. The laser has a real height and its two lines sit inside.
  • mix-blend-mode multiply on the laser's two lines blended only inside the laser (the mask and the opacity isolate it), and the red covered the black bars too: the multiply sits on the whole element, which then blends with the code below, red on the paper and black on the bars.
  • The stage, a bar still 80 pixels long but already as curved as its 12 pixel radius, went past a full turn and the SVG A command drew a random piece: the drawn turn stops a little past half a circle.
  • In flight the stage was a 24 pixel stroke on a 12 pixel radius and became a crescent with a bite: it flies thin and widens into its half disc only after it has landed (and going back it thins before it leaves).
  • The middle bars turned half one way and half the other and crossed in an X, and the front rows, which left first, were crossed by the last ones: they all turn the same way and the back rows leave first.
  • The button's entrance with a GSAP transform fought the CSS transition on transform that the press needs, and came in jerkily: the entrance moves what is inside the button.
  • The preview's click every 2.8 seconds came while the hall was still forming and sent it back: clicks from a script (not trusted) wait for the hall to be finished, while a real press can stop it halfway, because the springs change target from where they are.
  • color-mix() is missing in Safari before 16.2 and the ticket would have stayed transparent: every color-mix has a plain fallback colour before it.

Libraries

GSAP 3.15.0, Imbue (font variabile) 5.3.0, Schibsted Grotesk (font variabile) 5.3.0.

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

Testing

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