Vai al contenuto
animorio
EN
Tutti i componenti

La carta d'imbarco della funivia

Biglietti, Fisica, SVG, TransizioniGSAPImpegnativaPeso compresso: Demo 16 KBGSAP 28 KBGratisScarica lo zip

La carta d'imbarco di una funivia di montagna, con la linea disegnata in testa: due cabine sulle loro funi portanti, la tua rossa e l'altra blu. A ogni cambio di stato la carta risponde: per Boarding now arriva la fascia ambra, la porta si apre, le luci si accendono e il codice si apre sotto una luce che passa; partita, la cabina corre lungo la fune, che si abbassa sotto di lei, e oscilla davvero sul suo braccio, indietro alla partenza, con un colpo sul sostegno del traliccio e in avanti in frenata. Passandoci sopra col dito o col puntatore, la cabina dondola.

Il codice

Una cartella che funziona da sola: index.html, style.css, script.js e le librerie da CDN. Nelle schede React e Vue la stessa demo è già un componente, che spegne le sue animazioni quando esce dalla pagina.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>The cable car boarding pass</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/encode-sans@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the boarding pass of a cable car up a mountain, as the app that sold it shows it. Its head is the line itself:
         the two cabins hang from their track cables, the cables sink under them, and each cabin swings on its hanger
         with every push the ride gives it, at the stations and over the tower. The cables, the cabins and the code
         are drawn and moved by the script -->
    <section class="pass" aria-label="Cable car boarding pass" data-pass>
      <article class="card" data-card>
        <!-- the live part of the pass: the line, what is happening now, and where it goes -->
        <div class="card__live" data-live-part>
          <div class="scene" data-scene>
            <p class="brand">
              <svg class="brand__mark" viewBox="0 0 24 24" aria-hidden="true">
                <path d="M2 21.5L8.5 14l3 3.8L15.5 12L22 21.5"/>
                <path d="M2.5 6.8L21.5 2.6M10.8 5v2.4"/>
                <rect class="brand__cab" x="7.6" y="7.4" width="6.4" height="5" rx="1.4"/>
              </svg>
              <span class="brand__text"><span class="brand__name">Morrow Peak Cableway</span><span class="brand__kind">Boarding pass</span></span>
            </p>
            <svg class="scene__svg" viewBox="0 0 360 196" role="img" aria-label="The line: Cabin Red waits at Alder Gap" data-svg>
              <defs>
                <linearGradient id="cc-sky" x1="0" y1="0" x2="0" y2="1">
                  <stop offset="0" class="g-sky-top"/>
                  <stop offset="1" class="g-sky-low"/>
                </linearGradient>
                <radialGradient id="cc-haze" cx="0.84" cy="0.16" r="0.62">
                  <stop offset="0" class="g-haze" stop-opacity="0.5"/>
                  <stop offset="1" class="g-haze" stop-opacity="0"/>
                </radialGradient>
                <linearGradient id="cc-rock" x1="0" y1="96" x2="0" y2="196" gradientUnits="userSpaceOnUse">
                  <stop offset="0" class="g-rock"/>
                  <stop offset="1" class="g-card"/>
                </linearGradient>
                <linearGradient id="cc-warm" x1="0" y1="0" x2="0" y2="1">
                  <stop offset="0" class="g-lit" stop-opacity="0.95"/>
                  <stop offset="1" class="g-lit" stop-opacity="0.6"/>
                </linearGradient>
              </defs>

              <rect width="360" height="196" fill="url(#cc-sky)"/>
              <rect width="360" height="196" fill="url(#cc-haze)"/>

              <!-- the range behind, in the haze -->
              <path class="far" d="M0 112L18 100L34 106L58 80L76 94L92 86L112 100L136 72L152 84L170 78L196 96L220 70L246 88L268 64L300 60L360 54V196H0Z"/>
              <path class="far-snow" d="M52 86L58 80L66 87L61 85L56 89ZM130 78L136 72L143 79L138 77L134 81ZM214 76L220 70L228 78L222 76L218 79Z"/>

              <!-- the mountain the line climbs, its snow and the larches at its foot -->
              <path class="rock" fill="url(#cc-rock)" d="M0 196V186H112C128 186 140 178 152 166L170 154L190 146L210 140L230 130L248 118L262 104L276 90L298 84L314 58L328 24L338 6L350 18L360 24V196Z"/>
              <path class="snow" d="M300 82L314 58L328 24L338 6L350 18L360 24V44L352 40L346 50L338 42L330 56L322 54L316 70L308 74Z"/>
              <path class="snow snow--shade" d="M338 6L350 18L360 24V44L352 40L346 50L340 36Z"/>
              <path class="snow" d="M240 124L248 118L262 104L258 112L252 116L246 124Z"/>
              <g class="trees" aria-hidden="true">
                <path d="M96 172L91.5 182H94L90 189H102L98 182H100.5Z"/>
                <path d="M108 168L102.5 180H105.5L101 189H115L110.5 180H113.5Z"/>
                <path d="M121 175L117.5 183H119.5L116.5 189H125.5L122.5 183H124.5Z"/>
                <path d="M134 164L129 175H131.5L127.5 185H140.5L136.5 175H139Z"/>
                <path d="M146 158L141.5 168H144L140.5 177H151.5L148 168H150.5Z"/>
                <path d="M159 150L155 159H157.5L154 167H164L160.5 159H163Z"/>
              </g>

              <!-- the two stations, inside: the platform each cabin docks at, and the light that comes on to board -->
              <g class="station">
                <rect class="station__wall" x="0" y="118" width="86" height="72"/>
                <rect class="station__hall" x="40" y="124" width="46" height="48"/>
                <rect class="station__light" x="40" y="124" width="46" height="48" fill="url(#cc-warm)" opacity="0" data-light="0"/>
                <rect class="station__floor" x="40" y="172" width="46" height="3"/>
                <path class="station__glass" d="M8 136h24v10H8z"/>
                <path class="station__frame" d="M16 136v10M24 136v10"/>
              </g>
              <g class="station">
                <rect class="station__wall" x="276" y="16" width="84" height="74"/>
                <rect class="station__hall" x="276" y="22" width="44" height="50"/>
                <rect class="station__light" x="276" y="22" width="44" height="50" fill="url(#cc-warm)" opacity="0" data-light="1"/>
                <rect class="station__floor" x="276" y="72" width="44" height="3"/>
                <path class="station__glass" d="M330 36h22v10h-22z"/>
                <path class="station__frame" d="M337.5 36v10M344.5 36v10"/>
              </g>

              <!-- the far track: its cable and Cabin Blue, coming the other way -->
              <path class="cable cable--far" data-cable="far"/>
              <g class="cabin cabin--blue" data-cabin="far"></g>

              <!-- the tower, with the saddles the cables run over -->
              <g class="tower">
                <path d="M138.0 166L143.6 105.6M154.0 166L148.4 105.6M138.0 166L152.7 152M154.0 166L139.3 152M139.3 152L151.5 139M152.7 152L140.5 139M140.5 139L150.4 127M151.5 139L141.6 127M141.6 127L149.4 116M150.4 127L142.6 116M142.6 116L148.6 108M149.4 116L143.4 108"/>
                <path class="tower__arm" d="M136 105.6H156"/>
              </g>

              <!-- the near track: its cable and your cabin -->
              <path class="cable" data-cable="near"/>
              <g class="cabin cabin--red" data-cabin="near"></g>

              <!-- the roofs over the platforms, in front of the cabins -->
              <path class="station__roof" d="M-2 121L90 110V116.5L-2 126.5Z"/>
              <path class="station__roof" d="M270 17L362 7V13L270 23Z"/>
            </svg>
          </div>

          <!-- what is happening now: the script writes it, and the line under the card says it aloud -->
          <div class="status" data-status>
            <span class="status__band" aria-hidden="true" data-band></span>
            <span class="status__icon" aria-hidden="true" data-status-icon></span>
            <span class="status__text">
              <span class="status__title" data-status-title>Boarding at 09:40</span>
              <span class="status__sub" data-status-sub>Cabin Red is at Alder Gap</span>
            </span>
          </div>

          <div class="route" data-route>
            <p class="stop">
              <span class="stop__label">From</span>
              <span class="stop__move" data-stop="0">
                <span class="stop__name" data-name="0">Alder Gap</span>
                <span class="stop__alt" data-alt="0">1,046 m</span>
              </span>
            </p>
            <p class="stop">
              <span class="stop__label">To</span>
              <span class="stop__move" data-stop="1">
                <span class="stop__name" data-name="1">Morrow Peak</span>
                <span class="stop__alt" data-alt="1">2,610 m</span>
              </span>
            </p>
          </div>
        </div>

        <dl class="facts">
          <div class="fact"><dt>Boarding</dt><dd data-board>09:40</dd></div>
          <div class="fact"><dt>Departs</dt><dd data-depart>09:45</dd></div>
          <div class="fact"><dt>Cabin</dt><dd>Red</dd></div>
          <div class="fact"><dt>Gate</dt><dd data-gate>B</dd></div>
        </dl>

        <div class="ticket">
          <div class="code">
            <svg class="code__svg" viewBox="-2 -2 29 29" role="img" aria-label="Boarding code for ticket MPC 4471 2093" data-code></svg>
          </div>
          <div class="holder">
            <dl class="holder__list">
              <div><dt>Passenger</dt><dd>Mara Lindqvist</dd></div>
              <div><dt>Ticket</dt><dd>Adult, return</dd></div>
            </dl>
            <p class="holder__note" data-code-note>Code opens at 09:40</p>
          </div>
        </div>
      </article>

      <div class="bar">
        <p class="bar__updated" data-updated>Updated 09:31</p>
        <button class="act" type="button" data-act data-autoclic>
          <span class="act__icon" aria-hidden="true" data-act-icon></span>
          <span class="act__label" data-act-label>Refresh</span>
        </button>
      </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

Cambia valori, colori e testi con le manopole e guarda l’animazione rispondere dal vivo. Oppure scrivi tu nel codice. Poi scarica la cartella con le tue modifiche.

69 valori da cambiare

Oppure chiedila a Claude Code

Col connettore di Animorio, Claude Code la prende da qui con tutta la sua guida e la adatta al tuo progetto. Incolla questa richiesta:

Prendi «La carta d'imbarco della funivia» da Animorio e adattala al mio sito

Cosa fa

Su un fondo bianco la carta d'imbarco di Morrow Peak Cableway, blu scuro di montagna, come la mostra l'app che l'ha venduta. In testa c'è la linea stessa, in uno stile da manifesto: il cielo del mattino, le cime nella foschia, la montagna con la neve, i larici, la stazione a valle e quella in cima di cemento chiaro, un traliccio, e le due funi portanti con le loro cabine: la tua, Cabin Red, ferma alla stazione di Alder Gap, e Cabin Blue in cima. Sotto, lo stato, Boarding at 09:40 con Cabin Red is at Alder Gap; From Alder Gap 1,046 m, To Morrow Peak 2,610 m in un carattere stretto; Boarding, Departs, Cabin e Gate; il codice da mostrare, ancora spento, con Code opens at 09:40, il nome di chi viaggia e il biglietto. Sotto la carta Updated 09:31 e il pulsante Refresh. All'arrivo le funi si srotolano dalla valle e le cabine entrano dondolando. Refresh fa girare la sua freccia e porta lo stato nuovo: Boarding now, su una fascia ambra che entra da sinistra; la porta della cabina rossa scorre nella parete, le luci delle cabine e della stazione si accendono, la cabina cede un poco mentre si sale, e sul codice passa una luce calda in diagonale che lo accende modulo per modulo, con Show it at Gate B. Al Refresh dopo, la fascia se ne va, la porta si chiude, il codice si spegne con Used at Gate B, 09:43 e lo stato dice On your way up, Departed 09:45, arrives 09:53. Le due cabine partono insieme, legate alla stessa fune traente, una sale e l'altra scende: la fune si abbassa sotto ogni cabina con uno spigolo dove la tiene il carrello, la cabina si inclina indietro mentre accelera, passando sul traliccio prende un colpo e dondola, a metà strada incrocia l'altra, e frenando oscilla in avanti finché le guide della stazione la calmano. Arrivata, la porta si apre e lo stato dice Arrived at Morrow Peak, 09:53, last cabin down at 17:30; il pulsante diventa Show return pass. Premuto, la carta passa al ritorno: i due nomi si scambiano di posto come le cabine, quello che sale passa davanti e quello che scende passa dietro, sbiadendo, gli orari rotolano a 16:20 e 16:25, il cancello diventa A, e si ricomincia dall'alto. Passando col puntatore vicino a una cabina, o toccandola col dito, la si spinge e dondola; in stazione molto meno, perché le guide la tengono.

Come funziona

La fune: un filo teso con un peso

Ogni fune portante va dalla stazione a valle al sostegno del traliccio e da lì alla stazione in cima, in due campate. In una campata la fune è un filo teso: col suo peso fa una parabola, y = corda + s·4u(1−u), con la freccia s che cresce col quadrato della campata; col carico della cabina in c fa in più un triangolo, d·4·u(1−c) prima del carico e d·4·c(1−u) dopo, con d che cresce con la campata. Così sotto il carrello la fune ha uno spigolo, e la si ridisegna a ogni fotogramma come un path di punti ogni 5 pixel, più quelli esatti del traliccio e del carico.

Il carrello corre sulla curva che si fa da solo

Il carrello sta sempre sulla fune piegata dal suo stesso peso: la sua strada è la parabola con la freccia intera, s + d. Per farlo andare a velocità costante lungo la fune e non lungo il terreno, la strada è misurata una volta in 480 pezzi e si cerca la x per lunghezza con una ricerca binaria. La corsa segue il profilo di un argano vero: accelerazione costante, velocità costante, frenata costante (un trapezio di velocità, RAMP 0,32). L'altra cabina è legata alla stessa fune traente: sta sempre a 1 − p.

La cabina è un pendolo su un punto che si muove

La cabina pende dal carrello. Per un pendolo appeso a un punto che accelera (ax, ay), con y verso il basso: φ'' = (−(g − ay)·sin φ − ax·cos φ)/L − 2ζωφ'. Con g = ω²L, dove ω viene da SWING. L'accelerazione del punto non si stima dai fotogrammi: è la differenza seconda della posizione esatta del carrello a t − h, t, t + h, con h = 1/240 s, e il pendolo avanza a passi fissi di 1/240 con un accumulatore. Per questo allo spigolo del traliccio, dove la pendenza cambia di colpo, la cabina prende sempre lo stesso colpo, a qualunque frequenza di fotogrammi.

Le guide della stazione

Dentro una stazione la cabina è fra le guide: lo smorzamento sale a 0,45 col procedere dentro la stazione (una rampa di 14 pixel) e l'angolo ha un limite morbido di circa 9 gradi, oltre il quale una molla forte la riporta indietro: sbatte piano sulla guida invece di uscire dal muro. Le spinte del dito o del puntatore valgono un terzo. Quando le due cabine sono ferme e non corrono il ciclo sul ticker di GSAP si spegne.

Il dito che la spinge

Il puntatore che passa vicino a una cabina (a meno di 30 pixel dal suo centro, che si calcola dall'angolo) le dà una spinta angolare proporzionale a quanto si è spostato, non alla sua velocità, così non dipende da quanti eventi manda il browser; un tocco la spinge via dal dito. La testa ha touch-action pan-y, così col dito si può sfiorare la cabina e la pagina scorre lo stesso in verticale.

La carta che segue il viaggio

Gli stati sono quattro, Boarding at, Boarding now, On your way, Arrived, poi il ritorno. La fascia ambra entra con un clip-path inset da sinistra e le parole nuove arrivano solo quando lei c'è (e uscendo se ne va prima delle parole), così non si vede mai testo bianco sull'ambra. Il codice è un disegno fatto come un QR (tre quadrati, le righe di temporizzazione, il quadrato di allineamento e moduli ricavati dal numero del biglietto) in due strati, spento e acceso: quello acceso è tagliato da un poligono x + y < k, e una striscia di luce sfocata segue il bordo mentre k va da −6 a 62. I nomi delle stazioni si scambiano con un FLIP: quello che sale passa davanti e quello che scende dietro, e sparisce prima di sovrapporsi.

Valori da cambiare

NomeFileDi partenzaCosa cambia
RIDEscript.js3.4I secondi di una corsa da una stazione all'altra (quella vera dura otto minuti).
SWINGscript.js1.3I secondi di un'oscillazione della cabina sul suo braccio: più lunga, più si inclina quando accelera.
DAMPINGscript.js0.06Quanto in fretta si spegne un'oscillazione in aria (1 = non dondola).
SAGscript.js7Quanto si abbassa la fune sotto la cabina a metà di una campata lunga 100 pixel: più si abbassa, più forte è il colpo sul traliccio.
NUDGEscript.js1Quanto risponde una cabina al dito o al puntatore che le passa accanto (0 = per niente).
UPscript.js['09:40', '09:45', '09:53']La corsa in salita: imbarco, partenza e arrivo.
DOWNscript.js['16:20', '16:25', '16:33']La corsa in discesa, sulla metà di ritorno del biglietto.
GATESscript.js['B', 'A']Il cancello alla stazione a valle e a quella in cima.
LAST_DOWNscript.js'17:30'L'ultima cabina in discesa, detta all'arrivo in cima.

Provali nel Playground

Quando usarla

Per l'app di una funivia, di un trenino a cremagliera, di un traghetto o di un treno notturno; per il pass di un giro panoramico con l'orario d'imbarco; per una carta del portafoglio che segue lo stato del viaggio; ovunque un biglietto possa mostrare dove si trova davvero il mezzo.

Sul telefono

La carta è larga 360 pixel, 358 in uno schermo di 390 coi suoi margini di 16; il disegno della linea scala col suo viewBox, e negli schermi più stretti di 360 i nomi e il codice diventano un poco più piccoli. Col dito si preme il pulsante e tutto va come col mouse; toccando una cabina la si spinge, e sfiorandola di lato dondola, mentre la pagina scorre lo stesso in verticale.

Con il movimento ridotto

Niente si sposta: le cabine non dondolano e non corrono, ma si dissolvono dove sono e ricompaiono dove arrivano; la porta è subito aperta o chiusa, la fascia ambra e il codice entrano con una dissolvenza, i nomi e gli orari cambiano dissolvendosi, il pulsante non si allarga e la freccia non gira. Le funi e le cabine sono già al loro posto all'arrivo.

Trappole

  • Le due cabine si incrociano a metà della fune, e col traliccio a metà si incrociavano proprio sul traliccio, in un groviglio di tralicci e cabine: il traliccio è passato all'uscita della stazione a valle e l'incrocio sta in mezzo alla campata lunga.
  • Con la stessa freccia per due campate di lunghezza diversa, quella corta faceva uno spigolo enorme sul traliccio: la freccia del peso cresce col quadrato della campata e quella del carico con la campata, come in una fune vera.
  • Il colpo sul traliccio cambiava con la frequenza dei fotogrammi: l'accelerazione del carrello viene dalla sua posizione esatta a ±1/240 di secondo e il pendolo avanza a passi fissi, così lo spigolo dà sempre la stessa spinta.
  • Spinta in stazione, la cabina dondolava fuori dal muro: dentro la stazione lo smorzamento sale, la spinta vale un terzo e l'angolo ha un limite morbido, come le guide vere.
  • La porta che scorreva sopra i finestrini sembrava un blocco rosso: è una porta a scomparsa, tagliata dal suo vano, che sparisce nella parete.
  • Le parole bianche del nuovo stato comparivano sopra la fascia ambra che se ne andava: la fascia si muove prima e le parole la aspettano.
  • I due nomi che si scambiano si sovrapponevano a metà: quello che passa dietro sparisce prima di arrivare sopra l'altro.
  • La striscia di luce sul codice usciva dall'angolo arrotondato ed era seghettata (il codice ha shape-rendering crispEdges): sta in un clipPath della carta, sfocata, con geometricPrecision.
  • stop-color="var(--colore)" negli attributi dei gradienti non funziona: gli attributi di presentazione non leggono le variabili, i colori dei gradienti stanno in classi del CSS.
  • Il transform in linea che GSAP lascia sull'icona del pulsante dopo il cambio di parola annullava la rotazione dell'hover nel CSS: il cambio pulisce il transform, e il giro di Refresh muove l'svg dentro, non lo span.
  • Il clic dell'anteprima ogni 2,8 secondi arrivava a corsa iniziata: durante la corsa il pulsante è aria-disabled e il clic non fa niente.

Librerie

GSAP 3.15.0, Encode Sans (font variabile) 5.3.0.

Compressi con gzip, i tre file della demo pesano 16 KB; le librerie: GSAP 28 KB.

Collaudo

Provata il 7 ottobre 2026

  • Computer: Fatto
  • Telefono: Fatto
  • Movimento ridotto: Fatto
  • Safari su iPhone: Da fare

Licenza d’uso

Puoi usarla nei tuoi progetti e in quelli dei tuoi clienti. Non puoi rivenderla né ridistribuirla così com’è. GSAP resta sotto la sua licenza Standard. Licenza completa.

Tutti i componenti