Skip to content
animorio
IT
All components

The rippling code

Code, Text, Transitions, SVGGSAPAdvancedCompressed weight: Demo 13 KBGSAP + MorphSVG 37 KBFreeDownload the zip

The code block of the docs of an SDK for trail maps, with JavaScript, Python, Go and cURL tabs and the colours of the inks of a hiking map. Choosing a language drops a ripple under its tab that spreads through the code in rings: each letter goes under, changes into the new language and comes back up bigger and darker on the crest, and an echo comes back from the bottom edge. Copy puts the code on the clipboard and the selection floods out from under the button like a lake on a map, its shore following the shape of the lines; then it drains away.

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 rippling code</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/martian-mono@5.3.0/wdth.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/instrument-sans@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- a code block from the docs of a trail-map SDK: choosing a language drops a ripple into the code from under its
         tab, and each letter goes under in the trough and comes up in the new language; Copy floods the code with a
         selection shaped like a lake on a map, which drains back into the button -->
    <section class="snippet" aria-label="Get a trail with the Fellmap SDK" data-snippet data-autoclic>
      <header class="snippet__bar">
        <div class="tabs" data-tabs-wrap>
          <div class="tabs__row" role="tablist" aria-label="Language" data-tabs></div>
          <div class="tabs__row tabs__ink" aria-hidden="true" data-ink></div>
        </div>
        <button class="copy" type="button" data-copy>
          <svg class="copy__icon" viewBox="0 0 20 20" aria-hidden="true">
            <path class="copy__back" d="M13 6.5V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h1.5" data-back/>
            <path class="copy__front" d="M9 7h6a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2Z" data-front/>
          </svg>
          <span class="copy__label" data-label>Copy</span>
        </button>
      </header>
      <div class="code" role="tabpanel" id="code-panel" tabindex="0" data-panel>
        <div class="code__inner" data-inner>
          <svg class="code__lake" aria-hidden="true" data-lake-svg>
            <defs>
              <!-- the selection: the lines' rectangles blurred and cut at half their alpha (they melt into one shape
                   with rounded shores, inside and outside corners alike), then an eroded copy lighter inside -->
              <filter id="lake" filterUnits="userSpaceOnUse" x="0" y="0" width="100%" height="100%" color-interpolation-filters="sRGB" data-lake-filter>
                <feGaussianBlur in="SourceAlpha" stdDeviation="4.5" result="blur"/>
                <feColorMatrix in="blur" type="matrix" values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 24 -11" result="goo"/>
                <feFlood class="lake__shore" result="shoreInk"/>
                <feComposite in="shoreInk" in2="goo" operator="in" result="shore"/>
                <feMorphology in="goo" operator="erode" radius="1.3" result="inside"/>
                <feFlood class="lake__water" result="waterInk"/>
                <feComposite in="waterInk" in2="inside" operator="in" result="water"/>
                <feMerge>
                  <feMergeNode in="shore"/>
                  <feMergeNode in="water"/>
                </feMerge>
              </filter>
            </defs>
            <g filter="url(#lake)" data-lake></g>
          </svg>
          <div class="code__grid" aria-hidden="true" data-grid></div>
        </div>
        <pre class="sr" data-text></pre>
      </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="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/MorphSVGPlugin.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.

29 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 rippling code” from Animorio and fit it into my site

What it does

On a white page a code block the colour of map paper: at the top the band with the JavaScript, Python, Go and cURL tabs and the Copy button, below it the code of Fellmap (an invented SDK for trail maps) in the colours of the inks of a hiking map: contour brown for the words of the language, forest green for the texts, water blue for the functions, waymark red for the classes. The chosen tab sits in a dark pill that slides to the new tab, and the colour of the words changes exactly at its edge. Together, a ripple drops from under the tab and spreads through the code in rings: in front is the trough, where each letter shrinks, thins and nearly disappears; at the bottom it becomes the letter of the new language with its colour, then rises on the crest bigger and darker and settles. Smaller rings follow, and a fainter echo comes back from the bottom edge as if the ripple had hit it. The block grows or shrinks along with the ripple. Copy puts the code on the clipboard: the button fills with water, the sheet of the icon folds into a tick and Copy becomes Copied, keeping still the letters the two words share. Meanwhile the selection comes out from under the button like a lake: it runs along the lines faster than from one line to the next, takes the shape of the code with a darker shore and rounded corners, inside and out. After a while the lake drains back towards the button, which says Copy again.

How it works

The code in cells

A regex for each language (the language is guessed from the tab's name) cuts the code into words, and a word belongs to the language if it is in a list, is a function if a bracket follows, a name after a dot, a class if it starts with a capital, a constant if it is all capitals. Each character becomes an inline-block cell as wide as one Martian Mono character, measured once: the font is monospaced and its width does not change with the weight, so each cell knows where it is (column times width, row times height) without measuring anything. Tabs go to the next column of four; on a phone long lines wrap with two spaces of indent, after a space, a comma or a bracket.

The ripple

A ring that starts under the tab: at a distance s behind its front the water is −sin(2πs/WAVE)·e^(−s/0.6WAVE) high, less far from where it fell (divided by √(1+d/220)), so the trough comes first and the crest after. In the trough the letter shrinks, drops in weight down to 140 and fades; at the bottom it changes text and colour; on the crest it grows by 16%, rises in weight up to 800 (the wght axis of the variable font) and lifts a few pixels. Ripples still travelling add up, and the newest is the only one that changes the letters.

The echo from the edge

The bottom edge works as a mirror: for each ripple there is a second source, the same one mirrored below the edge, sending a fainter ring (ECHO) upwards. It is worked out like the first one, and it does not change the letters.

More lines and fewer lines

During a change the grid has as many rows as the longer of the two codes, and each cell knows what it shows and what it will become; a cell that has to disappear becomes a space, so the letters after it do not move. The block keeps its height and takes it to the new one following the ripple: at once if it grows, once the ripple has passed if it shrinks. When the ripple is over the rows are written again, clean.

The lake

A rectangle per row, from the first column to the last letter, inside an SVG group with the filter: feGaussianBlur blurs, feColorMatrix cuts the alpha at half (0 0 0 24 −11) and the rectangles melt into one shape with rounded corners inside too; feMorphology erodes a copy by 1.3 pixels for the light water, and what is left outside is the shore. Each rectangle opens where an ellipse reaches it, growing from under the button and squashed by ACROSS between the rows, with a little random delay per row.

Copy, the tabs and the preview

Copy writes to the clipboard with navigator.clipboard, or else with a hidden field and execCommand, or else selects the code; the confirmation goes through aria-live. The word keeps still the letters in common (Cop) and changes the tail in a slot that widens; the icon folds with MorphSVG. The tabs are a tablist with the arrows, Home and End; the pill is a dark copy of the row cut with clip-path inset to the chosen tab. A click from a script (the preview) presses Copy the first time and every third, and otherwise goes to the next tab.

Values to change

NameFileDefaultWhat it changes
SAMPLESscript.js[['JavaScript', "import { Fellmap } …"], ['Python', …], ['Go', …], ['cURL', …]]The tabs: the language's name and its code. The name also says how to colour it (py, go, curl or shell, otherwise JavaScript); Go's tabs stay in the copied code.
SPEEDscript.js600How fast the ripple spreads, in pixels a second.
WAVEscript.js140The wavelength, in pixels: the longer it is, the more letters are in the trough and on the crest together.
SWELLscript.js1How much the letters grow, thicken and fade on the ripple (0 = the code just changes).
ECHOscript.js0.4How strong the ring coming back from the bottom edge is (0 = no echo).
FLOODscript.js0.8The seconds the lake takes to flood the code.
COPIEDscript.js1.6The seconds the code stays selected and the button says Copied.
COPIED_LABELscript.js'Copied'What the button says once the code is on the clipboard.

Try them in the Playground

When to use it

For the documentation of an SDK or an API with examples in several languages, the install page of a tool (npm, pnpm, yarn), a developer's blog, a guide with the same example written in different ways.

On phones

Under 480 pixels the code goes down to 12 pixels and narrows on Martian Mono's width axis (75%), so about 45 characters fit on a row; longer lines wrap with two spaces of indent, and nothing spills sideways. The tabs narrow a little too (font-stretch 92%) to fit on one row with Copy at 390 pixels. Taps do not wait for a double tap (touch-action manipulation).

With reduced motion

The new code comes in with a fade, without a ripple; the pill jumps to the chosen tab; the lake appears all at once with a fade and goes the same way; the icon and the word change without moving, the button does not shrink when pressed and there is no entrance.

Pitfalls

  • Changing the letter on the crest, when it is biggest and darkest, the jump showed: the ripple comes with the trough in front, and the letter changes at the bottom, where it is small and nearly transparent.
  • A cell to be emptied (the new line is shorter) would take its place away from the letters after it, which would jump left: it becomes a space, and the rows are cleaned up only once the ripple is over.
  • Cells measured before the font arrives have the width of the fallback font: document.fonts.ready is not enough, because a font no text uses yet is not requested; document.fonts.load is awaited for both fonts.
  • Wrapping at the last possible point split process.env. from FELLMAP_KEY and api.fellmap. from dev: first a space, a comma or a bracket is looked for in the second half of the row, then a dot or a slash (never between the two of https://).
  • A button centres its text: Copy's tail, in a slot widening towards Copied, slid to the right until text-align left was set.
  • With a dark background on each tab, the sliding pill showed a light stripe in the gap between two tabs: the dark background is on the whole copy of the row, and the cut makes the pill.
  • If the block's height is let go at the end of the ripple while the tween taking it to the new one is still running, the block jumps: it is let go only once the tween has finished.

Libraries

GSAP 3.15.0, MorphSVG (GSAP) 3.15.0, Martian Mono (font variabile) 5.3.0, Instrument Sans (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 13 KB; the libraries: GSAP + MorphSVG 37 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