Skip to content
animorio
IT
Back to the library

The cathode-ray tube

An old computer desktop behind a television glass: scanlines, a band of light, flicker, text that types in steps and a switch that turns the tube off into a line and back on with another phosphor.

WebGL, Text, TransitionsWebGL, CSS only, GSAPAdvancedFree

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN.

<!doctype html>
<html lang="it">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Il tubo catodico</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/vt323@5.2.7/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="tubo" data-tubo data-tema="verde">
    <!-- tutto quello che sta sul vetro: si spegne e si riaccende come una televisione vecchia -->
    <div class="schermo" data-schermo>
      <header class="barra">
        <span class="barra__marchio">Bottega</span>
        <span class="barra__voci" aria-hidden="true"><span>Archivio</span><span>Vista</span><span>Aiuto</span></span>
        <span class="barra__ora" data-ora></span>
      </header>

      <section class="scrivania">
        <h1 class="titolo" data-titolo>Bottega di luce</h1>
        <p class="testo" data-righe>Tre file e un tubo che ronza. Apri un’icona, oppure spegni e riaccendi: il tubo cambia colore.</p>
        <div class="icone">
          <button class="icona" type="button" data-apri="lettera" data-autoclic>
            <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 1h7l3 3v11H3z" /><path class="icona__segni" d="M5 6h6M5 8h6M5 10h6M5 12h4" /></svg>
            <span>lettera.txt</span>
          </button>
          <button class="icona" type="button" data-apri="ricetta">
            <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2 3h12v10H2z" /><path class="icona__segni" d="M4 6h3M4 8h8M4 10h8" /></svg>
            <span>ricetta.txt</span>
          </button>
          <button class="icona" type="button" data-apri="diario">
            <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 1h10v14H3z" /><path class="icona__segni" d="M5 1v14M7 5h4M7 7h4" /></svg>
            <span>diario.log</span>
          </button>
        </div>
      </section>

      <section class="finestra" data-finestra hidden role="dialog" aria-labelledby="finestra-titolo">
        <header class="finestra__barra">
          <span id="finestra-titolo" data-finestra-titolo></span>
          <button class="finestra__chiudi" type="button" data-chiudi aria-label="Chiudi la finestra">
            <svg viewBox="0 0 12 12" aria-hidden="true"><path d="M2 2l8 8M10 2l-8 8" /></svg>
          </button>
        </header>
        <div class="finestra__corpo" data-finestra-corpo></div>
      </section>

      <footer class="comandi">
        <button class="interruttore" type="button" data-spegni>Spegni e riaccendi</button>
        <div class="temi" role="group" aria-label="Colore del fosforo">
          <button class="tema" type="button" data-scegli="verde" aria-pressed="true" aria-label="Verde"></button>
          <button class="tema" type="button" data-scegli="ghiaccio" aria-pressed="false" aria-label="Ghiaccio"></button>
          <button class="tema" type="button" data-scegli="rosa" aria-pressed="false" aria-label="Rosa"></button>
        </div>
      </footer>
      <div class="lampo" data-lampo aria-hidden="true"></div>
    </div>

    <!-- il vetro: righe, griglia, banda che scende, vignetta e cornice, disegnati da uno shader -->
    <canvas class="velo" data-velo aria-hidden="true"></canvas>
    <!-- i tre rettangoli che annunciano una finestra, come sui computer di una volta -->
    <div class="zoom" data-zoom aria-hidden="true"><i></i><i></i><i></i></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>

What it does

Inside a round-cornered frame sits a phosphor-green desktop: the top bar with the time, a title, two lines and three icons. The glass has its scanlines, a thin grille, a band of light slowly descending and a slight flicker. The title types one letter at a time with a brighter head, the lines appear behind a block cursor. Opening an icon, three rectangles fly from the icon to the window, and the window appears through a dither screen. The button at the bottom switches the tube off: the picture squashes into a line, then a dot; after a moment of darkness a line of light widens and the tube comes back, in another colour.

How it works

The glass

A WebGL canvas on top of everything, which ignores clicks, draws the glass thirty times a second. Inside the screen the alpha darkens (scanlines every three pixels with sin(y × 2π/3), a grille every four, a vignette) and the colour adds light (the band descending every nine seconds, grain, a reflection at the top left); outside the round-cornered rectangle sits the opaque frame.

Thirty steps a second

The text does not glide: it jumps. A single clock (requestAnimationFrame with an accumulator) ticks every 33 milliseconds and runs the tasks of the moment. The title reveals ⌈f/d × n⌉ letters per tick and keeps the newest two ticks brighter; lines appear through two mask-image layers, the finished lines and the current line up to the cursor.

The rectangles and the dither

Opening a file, three empty rectangles go from the centre of the icon to the size of the window in five ticks each (easeOutQuad), one tick behind the other. Then the window appears through four Bayer masks, at 20, 40, 60 and 80%, two ticks each.

Off and on again

A GSAP timeline squashes the screen to 0.6% of its height and then narrows it to zero width, brighter and brighter, in 0.44 seconds. In the dark the phosphor changes (the CSS variables and the shader colours); then the line widens, the glass opens to 103% and settles at 100% in 0.78 seconds, while a blade of light fades.

Values to change

NameFileDefaultWhat it changes
SCATTIscript.js30How many times a second the text moves: lower, more visible steps.
SPEGNEscript.js0.44Seconds of switching off.
ACCENDEscript.js0.78Seconds of switching on.
--carta, --inchiostrostyle.csstre temiThe colours of each phosphor: the glass reads them from here, so changing them in the CSS is enough.

When to use it

For a portfolio or a product with the soul of an old computer, or for a game page. The glass goes over any content without touching it.

On phones

The same, with a narrower frame, the bar without menus and the glass drawn at density 1 to save work.

With reduced motion

The glass keeps its lines and frame but loses the band, grain and flicker; texts appear whole, no rectangles or dither, and the phosphor change is a 150-millisecond fade.

Pitfalls

  • The glass must let clicks through (pointer-events: none) and use premultiplied alpha: alpha darkens, colour adds light, and the colour must not exceed the alpha.
  • Do not redraw the glass every frame: thirty times a second is enough, and while still or off screen it is not needed.
  • For the steps use a single clock tied to requestAnimationFrame, not many setIntervals: that way the timings stay together and can all be slowed down at once.
  • Measure the window before showing it (visibility: hidden), or the rectangles do not know where to go.

Libraries

GSAP 3.15.0, VT323 (font) 5.2.7.

Weight: 29 KB of our own files.

Testing

Tested on 27 September 2026

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

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