Skip to content
animorio
IT
All components

The zen garden footer

Footers, Hover, 3DCanvas, GSAPAdvancedCompressed weight: Demo 23 KBGSAP 28 KBFreeDownload the zip

The footer of a small tea house, made all of a raked sand garden lit by a low sun, with stones, moss and rings raked round them, and the house's name pressed into the sand along the bottom. The pointer is a rake that carves grooves where it goes, a link pointed at gets a ring raked round its word, and signing up to the letters brings a wide rake across that leaves the garden as new.

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 zen garden footer</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/newsreader@5.3.0/opsz.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/newsreader@5.3.0/opsz-italic.css">
  <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 footer of a small tea house: its whole ground is a raked sand garden lit by a low sun, with stones and
         moss and rings raked round them, and the house's name pressed into the sand along the bottom. The pointer is
         a rake that carves grooves where it goes, a link pointed at gets a ring raked round its word, and a sign-up
         to the letters brings a wide rake across that leaves the garden as new -->
    <footer class="zg" data-footer data-autoclic>
      <canvas class="zg__sand" aria-hidden="true" data-sand></canvas>
      <canvas class="zg__still" aria-hidden="true" data-still></canvas>
      <canvas class="zg__rake" aria-hidden="true" data-rake></canvas>

      <div class="zg__top">
        <form class="zg__form" action="#" novalidate aria-labelledby="zg-news" data-form>
          <h2 class="zg__title" id="zg-news" data-in>Letters from the tea house</h2>
          <p class="zg__lede" data-in>New harvests, tasting evenings and the garden through the year. One letter each season.</p>
          <label class="sr" for="zg-email">Email address</label>
          <div class="zg__field" data-field data-in>
            <input class="zg__input" id="zg-email" name="email" type="email" inputmode="email" autocomplete="email" autocapitalize="off" spellcheck="false" enterkeyhint="send" placeholder="you@example.com" aria-describedby="zg-note" aria-invalid="false" data-input>
            <button class="zg__send" type="submit" data-send>
              <span class="zg__send-bg"></span>
              <span class="zg__send-label" data-send-label>Subscribe</span>
              <svg class="zg__spin" viewBox="0 0 24 24" aria-hidden="true" data-spin><circle cx="12" cy="12" r="8"/></svg>
              <svg class="zg__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="zg__note" id="zg-note" aria-live="polite" data-note data-in><span class="zg__line">Four letters a year. Leave with one click.</span></p>
        </form>

        <!-- where the stones lie: the script reads these boxes, the stones are drawn in the sand -->
        <div class="zg__garden" aria-hidden="true">
          <span class="zg__stone zg__stone--big" data-stone></span>
          <span class="zg__stone zg__stone--small" data-stone></span>
        </div>

        <nav class="zg__cols" aria-label="Footer">
          <div class="zg__col" data-in>
            <h3 class="zg__head">Visit</h3>
            <ul>
              <li><a class="zg__link" href="#" data-link>Opening hours</a></li>
              <li><a class="zg__link" href="#" data-link>Find us</a></li>
              <li><a class="zg__link" href="#" data-link>Private rooms</a></li>
              <li><a class="zg__link" href="#" data-link>Gift cards</a></li>
            </ul>
          </div>
          <div class="zg__col" data-in>
            <h3 class="zg__head">Tea</h3>
            <ul>
              <li><a class="zg__link" href="#" data-link>Our teas</a></li>
              <li><a class="zg__link" href="#" data-link>Tastings</a></li>
              <li><a class="zg__link" href="#" data-link>Tea ware</a></li>
              <li><a class="zg__link" href="#" data-link>Wholesale</a></li>
            </ul>
          </div>
          <div class="zg__col" data-in>
            <h3 class="zg__head">House</h3>
            <ul>
              <li><a class="zg__link" href="#" data-link>Our story</a></li>
              <li><a class="zg__link" href="#" data-link>The garden</a></li>
              <li><a class="zg__link" href="#" data-link>Journal</a></li>
              <li><a class="zg__link" href="#" data-link>Contact</a></li>
            </ul>
          </div>
        </nav>
      </div>

      <div class="zg__base" data-in>
        <p class="zg__brand">
          <svg class="zg__mark" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 22V3.2"/><path d="M12 6.4L8.6 9.6M12 6.4l3.4 3.2"/><path d="M12 10.6l-5 4.4M12 10.6l5 4.4"/><path d="M12 15l-6.6 5.2M12 15l6.6 5.2"/></svg>
          <span class="zg__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 Hollow Pine Tea House</span>
          <span class="zg__hours">Open Wednesday to Sunday, 10 till 6</span>
        </p>
        <nav class="zg__legal" aria-label="Legal">
          <a class="zg__link zg__link--small" href="#" data-link>Privacy</a>
          <a class="zg__link zg__link--small" href="#" data-link>Terms</a>
          <a class="zg__link zg__link--small" href="#" data-link>Cookies</a>
        </nav>
      </div>

      <!-- the house's name: real text, pressed into the sand by the script where the text stands -->
      <div class="zg__foot">
        <p class="zg__name" data-name><span data-word>Hollow</span> <span data-word>Pine</span></p>
        <span class="zg__stone zg__stone--corner" aria-hidden="true" data-stone></span>
      </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.

61 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 zen garden footer” from Animorio and fit it into my site

What it does

On a white background a footer with rounded corners, 1080 pixels wide and about 400 tall, for Hollow Pine, a made-up tea house. Its whole ground is a garden of pale sand raked in long straight lines, lit by a low sun from the upper left: every groove is a line of light and a line of shade, and the sand has its grain. On the left the sign-up to the house's letters (Letters from the tea house, a line saying what arrives, a pale email field with the Subscribe button inside it and a line of help under it), on the right three columns of links (Visit, Tea, House), between them two dark stones on a cushion of moss, with three rings raked round them that merge into one, and a third stone in the bottom corner; then the mark (a drawn pine), the copyright, the opening hours and the legal links, and all along the bottom the name Hollow Pine pressed into the sand in big serif letters: the floor of the letters is smooth and a little darker, their walls catch the light on one side and the shade on the other, and round them the sand has been pushed up. The stones cast their shadow on the sand, they are granite with cracks and grain, and the moss climbs from their foot on the shady side. When the footer comes into view the sand is smooth and a wide rake, a wooden bar as tall as the footer with its teeth in the sand, crosses it from left to right, leaving the lines, the rings and the name behind it. The pointer on the sand is a small five-tooth bamboo rake, pulled by its handle: the head trails behind and swings round to follow, and where it goes it leaves five parallel grooves, with ridges of sand pushed up at their edges, cutting across the lines, the rings and the name too, which wears away; the grooves stay. Over a link or the field the rake lifts and fades; pointing at a link, or reaching it with the Tab key, a small two-tooth rake draws an oval ring round the word, starting from the left, and inside it the sand is smoothed, so the word sits in a still pool; moving away, the sand settles back as it was. The sign-up has its states: a wrong address shakes the field and the line under it says in brick red what is wrong; a right one sends the word out of the button, which narrows to a circle with a spinning arc and then a tick, the line under it says thank you with a leaf, and the wide rake crosses the whole footer again from left to right, pushing a little sand ahead of it: behind it the garden is as freshly raked, the grooves gone and the name whole again.

How it works

The sand is a height map

The sand is a height every CELL pixels, drawn on a canvas of that size which the browser enlarges smoothly. For every point the slope comes from the differences with its neighbours, and a low sun (SUN and SUN_HEIGHT) lights it the Lambert way: the light it gets, divided by what flat ground gets, picks among three sand colours (flat, in the sun, in shade), a hollow gathers a little shade (the Laplacian) and the light warms slightly on the side of the sun. The grain is a noise in every point's height plus a fine dust of light and dark specks drawn once at the screen's own pixels.

The garden freshly raked

Worked out once: straight lines with a slow wave and a depth that varies a little, as raked by hand; rings round the stones measured from the edge of the moss along the ray from the stone's centre, rounder the further out they go, with the lines ending on the outermost crest. The name is drawn in its own font on a hidden canvas at twice the size, exactly where the real words stand (each word's box plus fontBoundingBoxAscent); a blur makes the walls of the stamp, the floor is flat and a little darker, and round it the sand is smoothed and lifted.

A rake pulled by its handle

The rake's head only moves when the pointer is further than HANDLE pixels away, and then straight towards it: so it trails behind and swings round to follow, and the teeth always lie across the way. Between two pointer positions it goes in steps of 0.7 points, so a quick gesture misses nothing. At every step the sand within the rake's width takes the teeth's profile (a cosine with round troughs and narrower crests), the outer teeth heap a crest and just outside the sand pushed aside rises; there is no digging where the stones and moss are. Only the rectangle touched is lit again, with putImageData's dirty rectangle.

Rings round the links

A ring is not written into the sand: while it is lit, its band (two teeth) takes the place of the sand under it, as far round as GSAP has drawn it (the angle counted from the left end, with a soft tip), and inside the oval the sand is smoothed. The distance from the oval comes from the ellipse's equation divided by its slope. Moving away, its strength goes down to zero and the sand under it shows again as it was, grooves and all.

The wide rake, the stones and the moss

After a sign-up a GSAP tween takes the front from left to right in SWEEP seconds: every column it passes takes back the freshly raked sand, and a small heap of pushed sand swells ahead of the bar. The bar with its teeth and the small rake are drawn on a third canvas, with their shadow. Stones, moss and shadows are drawn once, pixel by pixel, at the screen's resolution: a boulder flat on top and round at the shoulders, cracks from a ridged noise, the granite's speckle, a hint of sheen, a cushion of moss made of small domes and moss climbing the shady side; the cast shadow is the outline drawn off the canvas with its blurred shadow brought back in.

Values to change

NameFileDefaultWhat it changes
TINESscript.js5How many teeth the rake has: as many grooves as it leaves on each pass.
TINE_GAPscript.js9How far apart the teeth are, in pixels: one groove and one crest. It goes for the garden's lines and rings too.
DEPTHscript.js1.2How deep the teeth dig, in pixels of sand: more makes sharper grooves, with more light and more shade.
PRESSscript.js2.6How deep the name along the bottom is pressed into the sand, in pixels: at 0 it is gone.
SUNscript.js125Where the sun comes from, in degrees round the footer: 90 from the top, 180 from the left. From straight left the level lines nearly vanish.
SUN_HEIGHTscript.js24How high the sun stands, in degrees: lower makes the grooves stand out more and the stones' shadows longer.
GRAINscript.js0.6How much grain the sand shows: at 0 it is as smooth as plaster.
RINGSscript.js3How many rings are raked round each stone.
CELLscript.js2How many pixels each point of the sand covers: more is less work and softer sand, less is more detail.
HANDLEscript.js34How far the rake's head trails behind the pointer, in pixels: more makes wider, softer curves.
SWEEPscript.js1.9How long the wide rake takes to cross the footer after a sign-up, in seconds.
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 tea house, a spa, a meditation retreat, an app for calm or sleep, a bonsai nursery, a potter or a landscape architecture studio: anywhere the bottom of the page can be a place to stop for a moment and play with the sand.

On phones

The footer takes the width of the screen and becomes a taller column: the newsletter, then a strip of open sand with the two stones and their rings, then the three columns of links, the mark, the opening hours and the legal links, and the name pressed in on two lines, Hollow and Pine, with the third stone beside Pine. The sand has fewer points but the same grain. A finger dragged sideways rakes (up and down scrolls the page), and the rake shows while the finger is down; tapping a link draws the ring round its word and it stays a moment.

With reduced motion

Nothing passes by itself: the garden is already raked when the footer appears, fading in. The rake stays, since the hand moves it. A link pointed at has its ring already closed, fading in with the smooth pool; a wrong field does not shake, the button does not narrow and the arc keeps still; on a sign-up the wide rake does not cross and the garden fades back to new where it is.

Pitfalls

  • With the sun from straight left, the level lines, which only slope up and down, caught no light and vanished: the sun comes from the upper left (125 degrees), so both the lines and the grooves across them show.
  • To press the name where the real text stands needs the right baseline: an inline word's box starts at fontBoundingBoxAscent over the baseline, so the canvas writes there, and the real text stays transparent (for screen readers).
  • With a tall line, the stems of H, l and P touched the copyright line: measured with actualBoundingBoxAscent, the name went down to 132 pixels with a line height of 0.62, and the footer stays 402 tall.
  • The ring round a link at first hugged the word: the inner groove ran under the letters and the hover could hardly be seen. Now the oval is wider, the groove deeper and the sand inside is smoothed, so the word sits in a still pool.
  • The rake held up over a link stayed drawn half transparent across the words: now over the links and the field it fades away, and comes back as soon as the hand is on the sand again.
  • The thanks after a sign-up wrapped, the footer grew and the change of size rebuilt the sand from scratch, stopping the wide rake halfway. Now a new size carries the grooves over to where they were on the page (a mask remembers where a hand has raked) and lets the wide rake finish; on a phone the line under the field has room for two lines.
  • On a phone the word Hollow, which does not break, stretched the page's column past the screen and the right margin shrank to 9 pixels: the name has a size that follows the screen and the column is minmax(0, 1fr).
  • On a tablet, between 780 and 960 pixels, the name on one line ran under the stone in the corner: there its size follows the width leaving room for the stone, and below 780 it goes on two lines with the stone beside Pine.
  • The first stones were a smooth dome of noise and looked like plastic, with moss in green blobs like paint: now they are flat on top, with cracks and the granite's speckle at the screen's pixels, and the moss climbs from the foot on the shady side.
  • The stones' shadow does not use ctx.filter, which Safari does not know: the outline is drawn off the canvas and its blurred shadow brought back in (shadowBlur and shadowOffset ignore the transform and have to be multiplied by the pixel density).

Libraries

GSAP 3.15.0, Newsreader (font variabile) 5.3.0, Mona Sans (font variabile) 5.3.0.

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

Testing

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