Skip to content
animorio
IT
All components

The ripening tomato timer

Timers, Physics, TransitionsCanvas, GSAPAdvancedCompressed weight: Demo 21 KBGSAP 28 KBFreeDownload the zip

A pomodoro technique timer where the time is a real tomato ripening on the vine: it starts green and hard and over the twenty-five minutes turns yellow, orange and glossy red, from the bottom up to the stalk as on the plant, with the skin tightening, the reflections getting crisp and the sepals opening, while the branch bends under the weight. At zero the tomato lets go, drops with a soft bounce and the break begins, with a yellow flower opening in its place.

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 ripening tomato timer</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/full.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- a pomodoro timer where the time is a real tomato ripening on the vine: it starts green and hard, and over the
         twenty-five minutes it turns from the blossom end up, through breaker, turning, pink and light red, to a glossy
         red, its skin tightening and its sepals opening, while the branch bends under it. At zero the ripe tomato lets
         go at its knuckle, drops with a soft bounce, and the break begins while a new flower opens on the stalk -->
    <section class="pomo" aria-label="Pomodoro timer" data-timer>
      <div class="pomo__vine" data-vine>
        <canvas class="pomo__canvas" aria-hidden="true" data-canvas></canvas>
      </div>

      <div class="pomo__readout" data-readout>
        <span class="pomo__time" role="timer" aria-live="off" aria-atomic="true">
          <span class="pomo__digits" aria-hidden="true" data-time></span>
          <span class="sr" data-time-words></span>
        </span>
        <p class="pomo__state"><span class="pomo__word" data-word></span><span class="pomo__detail" data-detail></span></p>
      </div>

      <div class="pomo__controls" data-controls>
        <button class="pomo__reset" type="button" aria-label="Reset" aria-disabled="true" data-reset>
          <span class="pomo__turn">
            <svg viewBox="0 0 24 24" aria-hidden="true" data-reset-icon><path d="M5.6 9.2A7.2 7.2 0 1 1 4.9 14.2"/><path d="M5.2 4.6v4.8H10"/></svg>
          </span>
        </button>
        <button class="pomo__go" type="button" data-go data-autoclic>
          <span class="pomo__shine" aria-hidden="true"></span>
          <span class="pomo__go-words" data-go-words>Start</span>
        </button>
        <span class="pomo__picked" role="img" aria-label="No tomatoes picked today" data-picked>
          <svg class="pomo__mini" viewBox="0 0 32 32" aria-hidden="true" data-mini>
            <defs>
              <radialGradient id="pomo-mini-skin" cx="38%" cy="36%" r="70%">
                <stop offset="0" stop-color="#ff7a55"/>
                <stop offset="0.45" stop-color="#e2321d"/>
                <stop offset="1" stop-color="#a3140f"/>
              </radialGradient>
            </defs>
            <path d="M16 9.2c-3.1-1.7-6.4-1.4-8.6.7-2.7 2.4-3.4 6.6-1.9 10.2 1.6 3.8 5.6 6.4 10.5 6.4s8.9-2.6 10.5-6.4c1.5-3.6.8-7.8-1.9-10.2-2.2-2.1-5.5-2.4-8.6-.7z" fill="url(#pomo-mini-skin)"/>
            <path d="M10.4 12.6c.8-1.3 2.2-2 3.4-2.1" fill="none" stroke="#ffd9c8" stroke-width="1.3" stroke-linecap="round" opacity="0.85"/>
            <path d="M16 10.4l-1.6-3 1.6 1.1 1.5-2.4.1 2.7 3.1-.6-2.3 2.1 2.9 1.3-3.4.1-.7 2-1.2-1.9-2.6 1.4 1.3-2.2-3.2-.6 3-.9z" fill="#3f7a25"/>
            <path d="M16 8.4c.1-1.6.7-2.9 1.8-3.8" fill="none" stroke="#3f7a25" stroke-width="1.6" stroke-linecap="round"/>
          </svg>
          <span class="pomo__count" aria-hidden="true" data-count></span>
        </span>
      </div>

      <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.

28 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 ripening tomato timer” from Animorio and fit it into my site

What it does

On a white background a tomato branch comes in from the left, hairy and with its toothed leaves, and in the middle it holds a green tomato, squat and ribbed, hanging from its stalk with the knuckle half way; below, on a floor you do not see, its soft shadow. Further down the time in large figures, 25:00, Ready · 25 min focus, and three things: a round button to reset, dimmed, a tomato red pill with Start and a small grey tomato, the ones picked today. With Start the tomato gives a small swing and starts to sway in the breeze; the figures run down and next to Focus comes the ripening stage, in its colour: Green, Breaker, Turning, Pink, Light red, Red. The tomato really ripens: first the green pales, then an amber blush rises in patches from the bottom, then yellow, orange and red, and the shoulders round the stalk stay green the longest. Meanwhile the skin tightens: the ribs soften, the light narrows and a two pane window is reflected crisp on the red; the sepals, which hugged the fruit, open and curl. The branch bends under the weight, and the figures get softer and fuller. Moving the pointer over it, or touching it, the tomato swings and the leaves shiver. At zero the tomato stays a moment red and glossy, Ripe · time for a break, then trembles at the knuckle of its stalk, lets go and drops: it lands with a squash, bounces gently, rolls a little and settles on its side, while the branch, freed, springs up and sways. The small tomato next to the buttons takes its colour and the number 1. The break begins, 05:00, Break · step away: on the stalk a bud comes out and opens into a yellow flower, its petals folded back round the cone of anthers. At the end of the break the petals fall, the picked tomato goes and in place of the flower a new green tomato swells. Pause stops everything, Resume carries on; the round button, while working, takes the tomato back to green, and during the break skips it.

How it works

The skin, pixel by pixel

The tomato is a squat ellipsoid seen a little from above, painted on a hidden canvas. For every pixel, once for each size, the script works out the normal, how high it sits above the bottom of the fruit, the angle round the stalk and a soft noise. The ribs are six grooves starting at the stalk, strong on the shoulders and gone by the middle: they barely change the outline, they bend the normal along the angle. Round the stalk the normals lean inwards: the dimple. So painting means, for every pixel, a few multiplications and two powers, at most thirty times a second.

It ripens from the bottom

The local ripeness is the timer's ripeness minus the height on the fruit, plus the noise: the colour rises from the bottom in patches and the shoulders stay green the longest, as on the plant. The colour comes from a table of 256 steps between the stages growers use (green, pale green, breaker, turning, pink, light red, red), mixed as the eye sees them and not in linear light, which went through brown between green and orange. On the green there are small pale round flecks, scattered on a grid round the fruit, that go with the red.

The skin tightening

As it ripens the ribs soften and the light narrows: the exponent of the highlight goes from 16 to 136. The window is the reflected view direction compared with the light direction, measured on two axes across it: two panes with the cross between them, soft on the waxy green and crisp on the glossy red. The shade is not grey: the flesh lets light through, so in the shade the colour gets deeper, the colour squared. Underneath there is the light bouncing off the floor, on the rim the light of the sky.

Sepals, flower, branch and leaves

The sepals are six blades in 3D round the axis of the stalk: each starts from a ring at a slant and curls along its length, and is drawn as a tapering leaf, from the farthest to the nearest. Closed they hug the green fruit, open they fold back. The break's flower uses the same blades for its petals, which point down as a bud and then turn back round the cone of anthers. The branch is a cantilever held off the drawing: its shape is the deflection under a weight at the node, and the deflection is a damped spring that follows the weight of the tomato. The leaves, made of toothed leaflets drawn once as Path2D, sit on springs the branch and the pointer stir.

The pendulum, the drop and the roll

The stalk is a pendulum with the stiffness of the stem: the breeze pushes it while the time runs, the pointer gives it a push. Since turning the image would also turn the highlight, the skin is repainted with the light turned back by the same angle. At zero the tomato lets go at the knuckle with the speed of its swing, falls with gravity, squashes on impact along the vertical round the point of contact and bounces; then it rolls without sliding, and gravity brings it back onto its wide side, because a squat fruit standing up sits higher.

The time and the preview

The time left comes from the clock (performance.now), so a tab left in the background ends on time. With QUICK a round started by hand lasts a few seconds and the figures show the real minutes; the figures roll only if the change before was more than a quarter of a second ago. The figures are set in Fraunces: as it ripens the SOFT axis goes from 0 to 100 and the weight from 360 to 560, and in the break they firm up again. The preview's click (detail 0) only starts a whole quick round, work, drop, break and new tomato, and script clicks arriving while it runs do nothing. role="timer" with aria-live off, and an aria-live region that speaks only when the state changes.

Values to change

NameFileDefaultWhat it changes
FOCUSscript.js25The minutes of a focus round.
BREAKscript.js5The minutes of the break after it.
QUICKscript.js20To try it out, a round started by hand lasts this many seconds (the figures still show the real minutes); 0 = in real time.
PREVIEWscript.js[8, 3]In the library's preview, the seconds of the focus round and of the break.
BENDscript.js1How much the branch bends under the tomato: 0 = stiff.
SWAYscript.js1How much the tomato swings in the breeze and under the pointer: 0 = still.
BOUNCEscript.js0.34How much the ripe tomato bounces when it lands: 0 = it just drops.

Try them in the Playground

When to use it

For the timer of a focus or study app, a task manager, a browser extension or a personal site about productivity: wherever the one working wants to see how long is left out of the corner of an eye, without counting. The same pixel painter suits any fruit that changes colour with time (a peach, an apple, a pepper) with another shape and other colour stages.

On phones

The component is 360 pixels wide and at 390 it narrows to 358 without spilling over; the drawing is repainted at the new size. A tap on the tomato swings it the other way and a finger sliding sideways stirs the leaves and the fruit, without blocking vertical scrolling. The buttons are 48 and 56 tall. The skin is painted on about fifty thousand pixels at most thirty times a second, and never while it is still.

With reduced motion

The tomato still ripens, because it is the time: it changes colour and gloss without moving. No breeze, no swing, the branch does not bend; at zero the tomato does not fall: it fades from the branch and appears on the ground. The flower fades in already open, the new tomato fades in and the figures change without rolling.

Pitfalls

  • Mixing the colour stages in linear light, between green and orange the tomato went through an olive brown that looked rotten: the stages are now mixed as the eye sees them, and between green and breaker there is a pale green, as in the real fruit.
  • With the last stage too dark and little ambient light the ripe tomato looked like a plum: brighter reds, more ambient, and the shade made with the colour squared instead of grey.
  • The pale flecks on the green, made by cutting a noise at a threshold, came out as crooked rectangles: now they are round dots, one per cell of a grid round the fruit, moved at random and lit in only half the cells.
  • Fully open, the sepals stood straight up like a crown: now they open across and curl only at the tips.
  • The swinging tomato is drawn turned, and the highlight turned with it as if the lamp moved: the skin is repainted with the light turned back by the same angle.
  • After the entrance the branch and the leaves kept shaking by less than a pixel for four seconds, and the drawing never stopped: springs with more damping and a threshold under which they stop in place; the entrance comes to rest in three seconds.
  • With reduced motion the tomato that let go vanished at once, because it was no longer the hanging one: it stays hanging while it fades, and only then leaves the drawing.

Libraries

GSAP 3.15.0, Fraunces (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 21 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