Skip to content
animorio
IT
All components

The city that lights up footer

Footers, HoverCanvas, GSAPAdvancedCompressed weight: Demo 23 KBGSAP 28 KBFreeDownload the zip

The footer of an evening city guide: under the newsletter and the links stands a skyline drawn at four depths that follow the pointer. The sky goes from sunset to night, the windows come on one after another, a train crosses on the elevated railway and the river mirrors it all, trembling. Every link has its own neon sign in the city: pointing at the link strikes the sign like a real tube, pointing at the sign lights the link.

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 city that lights up</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/bricolage-grotesque@5.3.0/standard.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/big-shoulders-display@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the footer of an evening city guide: under the newsletter and the links, a skyline at dusk drawn at four
         depths that follow the pointer. The sky goes from sunset to night, the windows come on one after another,
         an elevated train crosses and the river below mirrors it all. Every link has its own neon sign somewhere in
         the city: pointing at the link lights the sign, pointing at the sign lights the link -->
    <footer class="lc" data-footer data-autoclic>
      <canvas class="lc__city" aria-hidden="true" data-city></canvas>

      <div class="lc__top" data-top>
        <form class="lc__form" action="#" novalidate aria-labelledby="lc-news" data-form>
          <h2 class="lc__title" id="lc-news" data-rise>The Friday night list</h2>
          <p class="lc__lede" data-rise>Five places worth staying out for, in your inbox at six.</p>
          <label class="sr" for="lc-email">Email address</label>
          <div class="lc__field" data-field data-rise>
            <input class="lc__input" id="lc-email" name="email" type="email" inputmode="email" autocomplete="email" autocapitalize="off" spellcheck="false" enterkeyhint="send" placeholder="you@example.com" aria-describedby="lc-note" aria-invalid="false" data-input>
            <button class="lc__send" type="submit" data-send>
              <span class="lc__send-bg"></span>
              <span class="lc__send-label" data-send-label>Sign up</span>
              <svg class="lc__spin" viewBox="0 0 24 24" aria-hidden="true" data-spin><circle cx="12" cy="12" r="8"/></svg>
              <svg class="lc__done" viewBox="0 0 24 24" aria-hidden="true" data-done><path d="M6.4 12.6l3.7 3.7 7.6-8.6"/></svg>
            </button>
          </div>
          <p class="lc__note" id="lc-note" aria-live="polite" data-note data-rise><span class="lc__line">One email a week. Leave whenever you like.</span></p>
        </form>

        <nav class="lc__cols" aria-label="Footer">
          <div class="lc__col">
            <h3 class="lc__head" data-rise>Eat &amp; drink</h3>
            <ul class="lc__list">
              <li data-rise><a class="lc__link" href="#" data-link data-sign="BAR" style="--glow: var(--sign-bar)">Bars</a></li>
              <li data-rise><a class="lc__link" href="#" data-link data-sign="NOODLES" style="--glow: var(--sign-noodles)">Noodles</a></li>
              <li data-rise><a class="lc__link" href="#" data-link data-sign="DINER" style="--glow: var(--sign-diner)">Diners</a></li>
            </ul>
          </div>
          <div class="lc__col">
            <h3 class="lc__head" data-rise>Go out</h3>
            <ul class="lc__list">
              <li data-rise><a class="lc__link" href="#" data-link data-sign="CINEMA" style="--glow: var(--sign-cinema)">Cinemas</a></li>
              <li data-rise><a class="lc__link" href="#" data-link data-sign="JAZZ" style="--glow: var(--sign-jazz)">Jazz clubs</a></li>
              <li data-rise><a class="lc__link" href="#" data-link data-sign="THEATRE" style="--glow: var(--sign-theatre)">Theatres</a></li>
            </ul>
          </div>
          <div class="lc__col">
            <h3 class="lc__head" data-rise>Stay</h3>
            <ul class="lc__list">
              <li data-rise><a class="lc__link" href="#" data-link data-sign="HOTEL" style="--glow: var(--sign-hotel)">Hotels</a></li>
              <li data-rise><a class="lc__link" href="#" data-link data-sign="FERRY" style="--glow: var(--sign-ferry)">Ferries</a></li>
              <li data-rise><a class="lc__link" href="#" data-link data-sign="BOOKS" style="--glow: var(--sign-books)">Bookshops</a></li>
            </ul>
          </div>
        </nav>
      </div>

      <!-- the room the city stands in: the skyline, the elevated railway and the river are drawn on the canvas behind -->
      <div class="lc__room" aria-hidden="true" data-room></div>

      <div class="lc__base" data-base>
        <p class="lc__brand">
          <svg class="lc__mark" viewBox="0 0 28 28" aria-hidden="true"><path class="m-tower" d="M8 26V9.5L14 4l6 5.5V26z"/><path class="m-lit" d="M11.2 12.4h2.2v2.8h-2.2zM14.6 17.6h2.2v2.8h-2.2z"/><path class="m-dark" d="M14.6 12.4h2.2v2.8h-2.2zM11.2 17.6h2.2v2.8h-2.2z"/><path class="m-ground" d="M3 26h22"/></svg>
          <span class="lc__name">Lamplit</span>
          <span class="lc__copy"><svg viewBox="0 0 12 12" aria-hidden="true"><circle cx="6" cy="6" r="5.1"/><path d="M7.9 4.5a2.3 2.3 0 1 0 0 3"/></svg><span class="sr">Copyright</span> 2026 Lamplit Guides</span>
        </p>
        <nav class="lc__legal" aria-label="Legal">
          <a class="lc__small" href="#" data-small>Privacy</a>
          <a class="lc__small" href="#" data-small>Terms</a>
          <a class="lc__small" href="#" data-small>Contact</a>
        </nav>
      </div>
    </footer>
  </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.

73 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 city that lights up footer” from Animorio and fit it into my site

What it does

On a black background a footer 1040 pixels wide and about 410 tall, with rounded corners, for Lamplit, a made-up guide to evenings out in the city. At the top, in the sky, the sign-up to the Friday list (The Friday night list, a line saying what arrives, the email field with the Sign up button inside it and a line of help) and three columns of short links: Eat & drink (Bars, Noodles, Diners), Go out (Cinemas, Jazz clubs, Theatres), Stay (Hotels, Ferries, Bookshops). Under them, a city drawn by us on a single canvas: a haze of far towers, a middle row of buildings (stepped art deco towers with spires, crowns with fins of light, water tanks on the roofs, masts), the nearest buildings (gables, cornices, shops under the arches), a lattice elevated railway on the quay with the overhead wire on its masts, and the river. At the bottom, on the water, the mark, the copyright and the legal links. When the footer comes into view the words rise one after another and the sky is at sunset: the orange sun goes down behind the towers, the edges of the buildings facing it catch the last warm light, the long clouds are pink underneath. In six seconds the sky turns to blue night, the city throws a warm haze upwards, and the windows come on one after another as warm rectangles: homes with a lamp that warms up slowly (amber, cream, yellow, some with the blind half down), offices floor by floor with the hesitant stutter of strip lights, the shops first; the far towers fill with specks of light all together. Halfway through the evening the train goes by, its windows lit, the pantograph on the wire and the headlight's beam ahead. The river is the city itself upside down, line by line: each line of water takes a line of the city a little higher up and shifts it sideways with two waves, so the lights break into trembling dashes; when nothing moves the water calms down. The pointer slides the depths on damped springs, the nearest most, and the river ripples with the movement. Every link has its own neon sign, hung on a building made to carry it: vertical on the facade (CINEMA, NOODLES, JAZZ), a panel on the facade (BAR, BOOKS, DINER, FERRY) or big letters on a frame on the roof (HOTEL, THEATRE). Off, the dark tubes show; pointing at the link, or reaching it with the Tab key, strikes the sign like gas in a tube (on, nearly off, on, a dip, on for good), with a wide glow, the tube's almost white core, coloured light on the wall around it and its reflection in the river; the link takes the sign's colour, widens on the font's wdth axis and glows a little. Pointing at the sign in the city lights the link too. A click on a building turns its floors on from the bottom, or off from the top; a click on the elevated railway calls a train. The sign-up has its states: a wrong address shakes the field and the line under it says what is wrong; a right one sends the word out of the button, which shrinks to a circle with a spinning arc and then a tick, the line under it says thanks and the whole city lights up in a wave from left to right, every window and every sign, while a train goes by; a few seconds later it goes back as it was, with the wave running back.

How it works

A city from a seed

The city comes from a seeded random number generator (SEED): three rows of buildings, far, middle and near, each with its own widths, heights and roofs (setbacks, spire, dome, gable, water tank, mast). The near row is built around the signs: each link's sign is measured with its own font (measureText) and, at its place along the city, a building is made just wide and tall enough to carry it above the railway, with other buildings in between. The tallest the city may stand is read from the real footer, between the end of the text and the quay, and everything is rebuilt whenever the size changes.

Each depth painted twice

Each row is painted once on a hidden canvas at sunset (lighter facades, dark windows mirroring the warm sky, a line of light on the edges facing the sun) and once at night. On every frame where something changes, the two versions are blended with globalAlpha by the hour (dusk from 0 to 1, a GSAP tween) and the lit windows are drawn on top; the result is kept in a third canvas and drawn again only when a window or the hour changes. So the parallax just moves three ready images.

The windows, each on its own clock

Every window has a threshold: it switches on when the hour passes it (homes at random, offices floor by floor) and remembers the moment of the switch. Its light is a function of the time since then: a lamp rises in 0.24 s on a soft curve, an office strip light goes on, nearly off, on, a dip, then up (FLICKER). A click on a building changes its mode (all on or all off) with a delay for each floor, and the sign-up changes everyone's mode with a delay growing from left to right: the wave is the same rule.

The river, one line at a time

The scene above the quay is drawn in a canvas a few pixels wider. The river is filled line by line of pixels with drawImage: line k takes the line of the scene 1.45 k higher up (the city is squeezed in the reflection as in perspective), shifted sideways by two sines that grow towards the viewer and a little up and down, so the lights break into dashes. The amplitude is an energy that rises with the pointer's movement and the train and dies away on its own; a veil of water, darker towards the bottom, keeps the words readable.

The neon signs

Each sign is drawn twice, off (panel or frame and dark tubes of its colour) and on: the letters three times, with a wide shadowBlur, a tight shadowBlur and then the almost white core with no shadow. In the city the dark one is drawn and the lit one over it with globalAlpha equal to its light, plus a radial glow in lighter mode on the wall around it; the light is moved by a GSAP timeline with the strike of the gas. A sign is on while someone holds it on: the mouse on the link, keyboard focus, the pointer on the sign, a tap, the sign-up party.

The parallax, and a loop that stops

The pointer gives a target from -1 to 1; the view chases it on a damped spring (stiffness 70, damping 12) and each depth moves by PARALLAX times its depth (sky 0.06, far 0.2, middle 0.5, near 0.85, railway 1), a little vertically too. The requestAnimationFrame loop, with the step capped at 1/30 of a second, runs only while the spring, a window, a sign, the train, the water or the wave are moving: then it stops and the city stays drawn.

Values to change

NameFileDefaultWhat it changes
DUSKscript.js6How many seconds the evening takes to fall when the footer comes into view.
LITscript.js0.62The share of windows lit once night has fallen, from 0 to 1.
PARALLAXscript.js16How many pixels the nearest buildings slide with the pointer (the far ones less).
TRAINscript.js5How many seconds the elevated train takes to cross the city.
RIPPLEscript.js1How much the river ripples: at 0 it is a still mirror.
FLICKERscript.js1How much a neon sign stutters as it comes on: at 0 it lights in one go.
SEEDscript.js7The city itself: another number draws another skyline.
WAITscript.js1.1How long the sign-up takes in the demo, in seconds.

Try them in the Playground

When to use it

For the footer of a city guide, a downtown hotel, a bar or a cinema, a night festival, an events agency, a travel or city-life blog: anywhere the end of the page can become the evening, and the links can be real places to light up.

On phones

The footer takes the width of the screen: the newsletter goes on top, the three columns stay three, with 44-pixel rows to tap, and the city stands below, taller, with the river and the small print on two lines. The signs become nearly all vertical, smaller, and each still has its own building. A tap on a link lights its sign for a second and a half; a tap on a sign lights the link; a finger dragged sideways moves the depths, and let go it springs back to the middle; a tap on a building turns its floors on.

With reduced motion

Nothing moves: no parallax, no train, the river is still and the words fade in. The sky goes from sunset to night in a fade of little more than a second and the windows fade on, with no stutter. The signs fade on and off, the link changes colour without widening; the sign-up fades the whole city on at once.

Pitfalls

  • The first windows were 4-pixel squares in a regular grid and from a distance they looked like dots: now they are tall rectangles, sized differently for each building (tall sashes, wide panes, office strips), with some blinds half down.
  • With the river mirroring the city all the way down, the reflected lights ran right behind the copyright and the legal links and made them hard to read: now the water darkens by measuring where the small print begins, and on a phone the quay sits higher.
  • Shifting the river's lines sideways showed an empty strip at the edges: the scene is drawn in a canvas ten pixels wider on each side and the shift never goes past them.
  • A sign on a building in the middle row could end up behind a nearer building: every sign stands on the near row, and the city is built around them, with the right building for each, tall enough to hold it above the railway.
  • Drawing three thousand windows again on every frame of the parallax was wasted work: each row keeps its composed image and redoes it only when a window or the hour changes.
  • The signs are measured with their own font, which arrives later: once it has loaded (document.fonts.load) the city is rebuilt, with the same windows lit since they depend only on the hour.

Libraries

GSAP 3.15.0, Bricolage Grotesque (font variabile) 5.3.0, Big Shoulders Display (font variabile) 5.3.0.

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

Testing

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