Skip to content
animorio
IT
Back to the library

The cards that cross over

ScrollGSAP, CSS onlyMediumCompressed weight: Demo 4.9 KBGSAP + ScrollTrigger 45 KBLenis 5.3 KBFreeDownload the zip

Scroll inside the preview

A minimal-style scroll animation: what a mountain hotel offers, on six large cards in a row. The page holds still and the row slides left with the scroll; each card comes in low on the right and tilted, rises as it crosses the screen and leaves high on the left, tilted the other way. The card’s title widens as it passes the middle.

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 cards that cross over</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <!-- what a made-up mountain hotel, Hollin, offers, on six large cards in a row. The row slides to the left with
       the scroll while the stage stays still; each card comes in low on the right and tilted, rises as it crosses
       the screen and leaves high on the left, tilted the other way. Its title widens as it passes the middle -->
  <main class="pagina">
    <header class="apertura">
      <p class="occhiello">Hollin, a hotel above the tree line</p>
      <h1 class="apertura__titolo">Stay a little longer.</h1>
      <p class="scendi">Scroll</p>
    </header>

    <section class="vantaggi" data-vantaggi aria-label="What the hotel offers">
      <div class="palco" data-palco>
        <p class="palco__nota">What is included</p>
        <div class="binario">
        <div class="fila" data-fila>
          <article class="carta" data-carta>
            <svg class="carta__disegno" viewBox="0 0 200 120" aria-hidden="true">
              <path d="M40 110V58M64 110V44M88 110V58M112 110V44M136 110V58M160 110V44" />
              <path d="M58 34c-6-8 6-14 0-22M100 34c-6-8 6-14 0-22M142 34c-6-8 6-14 0-22" />
            </svg>
            <h2 class="carta__titolo" data-titolo>Sauna</h2>
            <p class="carta__testo">Cedar benches, a cold plunge and the valley through a window.</p>
          </article>
          <article class="carta" data-carta>
            <svg class="carta__disegno" viewBox="0 0 200 120" aria-hidden="true">
              <path d="M30 104 130 14M52 108 152 18" />
              <path d="M96 112 120 64M128 112 152 64" />
              <path d="M24 112h152" />
            </svg>
            <h2 class="carta__titolo" data-titolo>Ski room</h2>
            <p class="carta__testo">Warm racks for boots, and a door straight onto the first lift.</p>
          </article>
          <article class="carta carta--sabbia" data-carta>
            <svg class="carta__disegno" viewBox="0 0 200 120" aria-hidden="true">
              <path d="M40 112V40M28 64h24M32 82h16M60 112V24M44 52h32M48 74h24M48 94h24M140 112V30M126 56h28M130 78h20M164 112V48M152 72h24M156 92h16" />
              <path d="M84 112c10-18 26-22 22-40s14-24 10-44" />
            </svg>
            <h2 class="carta__titolo" data-titolo>Forest trail</h2>
            <p class="carta__testo">Six kilometres of pine from the terrace to the waterfall.</p>
          </article>
          <article class="carta" data-carta>
            <svg class="carta__disegno" viewBox="0 0 200 120" aria-hidden="true">
              <path d="M62 52h64v26a32 32 0 0 1-64 0z" />
              <path d="M126 60h10a10 10 0 0 1 0 20h-12" />
              <path d="M40 112h120" />
              <path d="M80 38c-4-6 4-10 0-16M100 38c-4-6 4-10 0-16" />
            </svg>
            <h2 class="carta__titolo" data-titolo>Breakfast</h2>
            <p class="carta__testo">Bread from the oven downstairs, until eleven, on weekends till noon.</p>
          </article>
          <article class="carta" data-carta>
            <svg class="carta__disegno" viewBox="0 0 200 120" aria-hidden="true">
              <path d="M24 60h152M24 112h152" />
              <path d="M36 60v52M60 60v52M84 60v52M108 60v52M132 60v52M156 60v52" />
              <path d="M30 44 72 18l26 16 30-20 42 30" />
            </svg>
            <h2 class="carta__titolo" data-titolo>Terrace</h2>
            <p class="carta__testo">A long balcony facing west, with blankets when the sun goes.</p>
          </article>
          <article class="carta" data-carta>
            <svg class="carta__disegno" viewBox="0 0 200 120" aria-hidden="true">
              <path d="M38 112V36M50 112V48M62 112V30M74 112V44M86 112V38M98 112V52M110 112V34M122 112V46M134 112V40M146 112V28M158 112V44" />
              <path d="M28 112h144" />
            </svg>
            <h2 class="carta__titolo" data-titolo>Library</h2>
            <p class="carta__testo">Maps, field guides and a chair by the stove for each of them.</p>
          </article>
        </div>
        </div>
      </div>
    </section>

    <footer class="chiusura">
      <p class="chiusura__titolo">Book a winter week.</p>
    </footer>
  </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/ScrollTrigger.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/lenis@1.3.26/dist/lenis.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.

33 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 cards that cross over” from Animorio and fit it into my site

What it does

On warm white paper, the title of a made-up mountain hotel, Hollin: ‘Stay a little longer.’. Scrolling down, the page stops under a hairline that reads ‘What is included’, and in from the right, low, comes the first of six large cards, tilted: Sauna, Ski room, Forest trail, Breakfast, Terrace, Library, each with a drawing in thin brown lines (the benches and the steam, the skis leaning on a wall, the pines and the path, the cup, the railing before the mountains, the spines of books) and a sentence. The row slides left as you scroll, and every card takes its own way: it comes in low and tilted one way, rises as it crosses the screen, goes through the middle almost straight and leaves high on the left, tilted the other way. The cards in even places dip a little lower and the odd ones a little less, so the row makes a zigzag, and every card has its own tilt and its own shift, the same at every visit. Each card’s title is narrow at the edges and widens and gains a hair of weight as the card passes the middle of the screen. Only one card, Forest trail, is sand coloured. Once the last one has gone, the page moves on to ‘Book a winter week.’.

How it works

The stage and the row

The section is as tall as the screen plus the length of the row (the script works it out at every ScrollTrigger refresh, with refreshInit), and inside it is a sticky stage 100lvh tall. One tween takes the row from xPercent 0 to -100, moving it by its whole width, with a ScrollTrigger from the top of the section to its bottom and scrub: one pixel of scroll, one pixel of row. The row starts with a left margin of 86% of the screen, so the first card just peeks in from the right, and has a right margin that lets the last one leave.

Every card on its own

Every card has its own fromTo with containerAnimation on the row’s tween: ScrollTrigger measures the card inside the moving row, from when its left edge is at 120% of the screen (still off to the right) to when its right edge is at -20% (already off to the left), with scrub and ease none. It starts from rotation r, xPercent x and yPercent y + LIFT ± SWAY and ends at -r, -x and -y - LIFT ∓ SWAY: even cards with the plus coming in and the minus going out, odd ones the other way round.

Chance with a seed

r (within TILT), x (within SHIFT_X) and the sign of each, plus a y of 5, come from a small seeded random generator (mulberry32): the cards all cross a little differently, but at every visit, in the video and in the Playground they take the same path. Changing SEED changes the set.

The title that widens

On every update of the row’s tween the script measures where each card’s centre is compared with the middle of the screen and takes the title’s font-stretch from 70% (at the edges) to WIDE (in the middle) with a soft step (smoothstep), and the weight from 520 to 600. Variable Archivo has a width axis from 62 to 125.

The smooth scroll

Lenis with a duration of SMOOTH (1.2 seconds) and an exponential braking curve, Math.min(1, 1.001 - 2 to the power of -10t), driven by GSAP’s ticker; ScrollTrigger updates on every scroll. With reduced motion there is no Lenis and no tween: the section is plain and the cards stand straight in a grid that wraps.

Values to change

NameFileDefaultWhat it changes
TILTscript.js[5, 10]How tilted a card is coming in, in degrees: a random amount between the two, either way. Going out it is tilted the same, the other way.
SHIFT_Xscript.js[20, 30]How far it is pushed sideways at the two ends of its path, as a percentage of its width (a random amount between the two).
LIFTscript.js75How far below the middle line it comes in (and above it goes out), as a percentage of its height.
SWAYscript.js15How much is added or taken away on every other card, so the row zigzags.
SEEDscript.js7The seed of the random amounts: change it for another set of crossings.
WIDEscript.js112The width of a title in the middle of the screen (70 at the edges).
SMOOTHscript.js1.2The seconds of the smooth scroll; 0 for the browser’s own.

Try them in the Playground

When to use it

For the amenities of a hotel, a holiday home or a spa, the perks of a membership, the sections of a landing page: five or six cards to leaf through while scrolling down, each with a drawing and a sentence.

On phones

The same scene, with the cards almost three quarters of the screen wide and a shorter row: they come in tilted from the right and leave on the left as on a computer. The site does not do it on a phone; we keep it, with the sizes adapted.

With reduced motion

No held stage and no smoothed scroll: the cards stand straight in a grid that wraps, with their titles already wide.

Pitfalls

  • With containerAnimation the row’s tween must have ease none: with a curve the card positions measured by ScrollTrigger would not match the real ones.
  • The row is centred vertically by a CSS translate on a separate wrapper (the rail): GSAP, writing its transform on the row, would wipe out the class’s translate.
  • The section’s height is worked out again in refreshInit, before ScrollTrigger measures: worked out afterwards, on a window resize the row ended before or after the bottom of the section.
  • With Math.random every visit would have different cards, and the preview video would never match the Playground: that is why chance has a seed.

Libraries

GSAP 3.15.0, Lenis 1.3.26, Archivo (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 4.9 KB; the libraries: GSAP + ScrollTrigger 45 KB, Lenis 5.3 KB.

Testing

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