
Il menu sotto il foglio
Menu, 3D, Fisica, Transizioni
NuovaGratis
Il menu dell'account di un'app per i progetti come un tesserino su un avvolgibile: l'avatar è la faccia della bobina, e premuto lascia uscire il tesserino sul suo cordino, che lo prende con un piccolo rimbalzo mentre lui scende dondolando dal punto in cui era inclinato all'indietro. Il tesserino è il menu; Switch workspace lo fa girare sul moschettone e le aree di lavoro sono sul retro, Sign out lo gira per chiedere conferma e poi la clip si apre e il tesserino cade, mentre il cordino rientra di scatto.
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 ID badge menu</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/host-grotesk@5.3.0/index.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- the portrait, drawn once and used on the reel and on the badge -->
<svg class="defs" aria-hidden="true">
<symbol id="portrait" viewBox="0 0 64 64">
<rect width="64" height="64" fill="#cfe3db"/>
<path d="M14 46c-2-6-2-13 0-19 3-9 10-14 18-14s15 5 18 14c2 6 2 13 0 19-3 2-7 3-10 3H24c-3 0-7-1-10-3z" fill="#2a1c16"/>
<path d="M27.5 40h9v11h-9z" fill="#a96d4d"/>
<path d="M9 64c1.5-9 9-14.5 23-14.5S53.5 55 55 64z" fill="#e7a43c"/>
<path d="M25.6 50.3c1.8 3.5 4 5.1 6.4 5.1s4.6-1.6 6.4-5.1" fill="none" stroke="#c98425" stroke-width="1.6" stroke-linecap="round"/>
<ellipse cx="32" cy="30.5" rx="10.6" ry="12.6" fill="#c48a64"/>
<path d="M21.3 30c-.6-9.6 4.4-16 10.7-16 6.6 0 11.4 5.6 10.7 15.2-3.4-1.6-6.6-4.6-8.4-8.6-2.2 4.6-7.4 8.2-13 9.4z" fill="#2a1c16"/>
<ellipse cx="25.4" cy="36.4" rx="2.2" ry="1.4" fill="#e0866b" opacity="0.4"/>
<ellipse cx="38.6" cy="36.4" rx="2.2" ry="1.4" fill="#e0866b" opacity="0.4"/>
<g fill="none" stroke="#1c1715" stroke-width="1.3" stroke-linecap="round">
<circle cx="27.6" cy="32" r="3.7"/>
<circle cx="36.4" cy="32" r="3.7"/>
<path d="M31.3 31.5q.7-.7 1.4 0"/>
<path d="M26.3 32.6q1.3-1.1 2.6 0M35.1 32.6q1.3-1.1 2.6 0"/>
</g>
<path d="M29.6 38.2q2.4 1.8 4.8 0" fill="none" stroke="#7a3d2a" stroke-width="1.3" stroke-linecap="round"/>
</symbol>
<symbol id="nobody" viewBox="0 0 64 64">
<rect width="64" height="64" fill="#ececf0"/>
<circle cx="32" cy="27" r="8.5" fill="none" stroke="#a3a5ae" stroke-width="2.6"/>
<path d="M15.5 52c2.6-8 9-12 16.5-12s13.9 4 16.5 12" fill="none" stroke="#a3a5ae" stroke-width="2.6" stroke-linecap="round"/>
</symbol>
</svg>
<main class="stage">
<!-- the account menu of a design tool, as an ID badge on a retractable reel. The reel is the button, with the
person's portrait on its face; pressed, the badge comes out on its cord and swings into place, and the badge is
the menu. Switch workspace and Sign out turn the badge over on its swivel: the second level is on its back -->
<div class="account" data-account>
<!-- the cord and its split ring, drawn by the script -->
<svg class="cord" aria-hidden="true" data-cord>
<line class="cord__base" data-cord-line/>
<line class="cord__braid" data-cord-line/>
<g class="ring" data-ring>
<circle r="4.4"/>
<path class="ring__glint" d="M-3.2-1.9a3.8 3.8 0 0 1 3.4-2.3"/>
</g>
</svg>
<!-- the badge's shadow on the wall behind it -->
<span class="shade" aria-hidden="true" data-shade><span class="shade__card"></span></span>
<div class="badge" data-badge>
<div class="badge__card" data-card inert>
<!-- the front: the workspace's band, the person, the menu -->
<section class="face face--front" data-face="front">
<div class="band" data-band>
<span class="slot" aria-hidden="true"></span>
<span class="band__logo" aria-hidden="true" data-band-logo><svg viewBox="0 0 24 24"><path d="M3.5 10c2.1-2.2 4.3-2.2 6.4 0s4.3 2.2 6.4 0 2.6-1.4 4.2-1.4M3.5 15.5c2.1-2.2 4.3-2.2 6.4 0s4.3 2.2 6.4 0 2.6-1.4 4.2-1.4"/></svg></span>
<span class="band__name" data-band-name>Tidewell Studio</span>
<span class="band__role" data-band-role>Admin</span>
</div>
<div class="who">
<svg class="who__photo" aria-hidden="true"><use href="#portrait"/></svg>
<p class="who__name" id="badge-name">Ines Marlow</p>
<p class="who__mail" id="badge-mail">ines@tidewell.studio</p>
</div>
<div class="menu" role="menu" id="badge-menu" aria-labelledby="badge-name badge-mail" tabindex="-1" data-menu="main">
<span class="menu__hl" aria-hidden="true" data-hl></span>
<a class="row" role="menuitem" href="#" tabindex="-1" data-item="profile">
<svg class="row__icon" viewBox="0 0 24 24" aria-hidden="true"><g data-nod><circle cx="12" cy="8.4" r="3.6"/></g><path d="M5 19.6c1.2-3.6 3.9-5.5 7-5.5s5.8 1.9 7 5.5"/></svg>
<span class="row__label" data-label>Profile</span>
</a>
<a class="row" role="menuitem" href="#" tabindex="-1" data-item="settings">
<svg class="row__icon" viewBox="0 0 24 24" aria-hidden="true"><g data-spin><path d="M10.46 5.58L10.88 3.67L13.12 3.67L13.54 5.58A6.6 6.6 0 0 1 15.45 6.37L17.1 5.32L18.68 6.9L17.63 8.55A6.6 6.6 0 0 1 18.42 10.46L20.33 10.88L20.33 13.12L18.42 13.54A6.6 6.6 0 0 1 17.63 15.45L18.68 17.1L17.1 18.68L15.45 17.63A6.6 6.6 0 0 1 13.54 18.42L13.12 20.33L10.88 20.33L10.46 18.42A6.6 6.6 0 0 1 8.55 17.63L6.9 18.68L5.32 17.1L6.37 15.45A6.6 6.6 0 0 1 5.58 13.54L3.67 13.12L3.67 10.88L5.58 10.46A6.6 6.6 0 0 1 6.37 8.55L5.32 6.9L6.9 5.32L8.55 6.37A6.6 6.6 0 0 1 10.46 5.58Z"/><circle cx="12" cy="12" r="2.7"/></g></svg>
<span class="row__label" data-label>Settings</span>
<span class="row__meta" data-keys></span>
</a>
<a class="row" role="menuitem" href="#" tabindex="-1" data-item="billing">
<svg class="row__icon" viewBox="0 0 24 24" aria-hidden="true"><g data-swipe><rect x="3.5" y="6" width="17" height="12" rx="2.2"/><path d="M3.5 10h17M7 14.5h3.2"/></g></svg>
<span class="row__label" data-label>Billing</span>
<span class="row__meta" data-band-plan>Team plan</span>
</a>
<button class="row" type="button" role="menuitem" tabindex="-1" aria-haspopup="menu" aria-expanded="false" aria-controls="badge-workspaces" data-item="workspace">
<svg class="row__icon" viewBox="0 0 24 24" aria-hidden="true"><rect data-swap-a x="3.8" y="3.8" width="9.4" height="9.4" rx="2.2"/><rect data-swap-b x="10.8" y="10.8" width="9.4" height="9.4" rx="2.2"/></svg>
<span class="row__label" data-label>Switch workspace</span>
<svg class="row__chevron" viewBox="0 0 24 24" aria-hidden="true" data-chevron><path d="M9.5 6l6 6-6 6"/></svg>
</button>
<a class="row" role="menuitem" href="#" tabindex="-1" data-item="help">
<svg class="row__icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="8.6"/><g data-ask><path d="M9.6 9.7a2.5 2.5 0 1 1 3.5 2.3c-.7.3-1.1.9-1.1 1.6v.3"/><path d="M12 16.7v.1"/></g></svg>
<span class="row__label" data-label>Help & support</span>
</a>
<div class="menu__rule" role="separator"></div>
<button class="row row--danger" type="button" role="menuitem" tabindex="-1" aria-haspopup="menu" aria-expanded="false" aria-controls="badge-signout" data-item="signout">
<svg class="row__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M13.5 4.5H7a1.6 1.6 0 0 0-1.6 1.6v11.8A1.6 1.6 0 0 0 7 19.5h6.5"/><g data-leave><path d="M10.5 12h9.5M17 8.8l3.2 3.2-3.2 3.2"/></g></svg>
<span class="row__label" data-label>Sign out</span>
</button>
</div>
<span class="face__sheen" aria-hidden="true" data-sheen></span>
<span class="face__dim" aria-hidden="true" data-dim></span>
</section>
<!-- the back: the workspaces, or the question before signing out -->
<section class="face face--back" data-face="back">
<div class="back__top"><span class="slot" aria-hidden="true"></span></div>
<div class="page" data-page="workspaces">
<div class="menu" role="menu" id="badge-workspaces" aria-label="Switch workspace" tabindex="-1" data-menu="workspaces">
<span class="menu__hl" aria-hidden="true" data-hl></span>
<button class="row row--back" type="button" role="menuitem" tabindex="-1" aria-label="Back to the account menu" data-item="back">
<svg class="row__icon" viewBox="0 0 24 24" aria-hidden="true" data-back><path d="M14.5 6l-6 6 6 6"/></svg>
<span class="row__label" data-label>Workspaces</span>
</button>
<div class="menu__rule" role="separator"></div>
<button class="row row--ws" type="button" role="menuitemradio" aria-checked="true" tabindex="-1" style="--ws: #0d5c58" data-ws="tidewell" data-role="Admin" data-plan="Team plan">
<span class="ws__logo" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3.5 10c2.1-2.2 4.3-2.2 6.4 0s4.3 2.2 6.4 0 2.6-1.4 4.2-1.4M3.5 15.5c2.1-2.2 4.3-2.2 6.4 0s4.3 2.2 6.4 0 2.6-1.4 4.2-1.4"/></svg></span>
<span class="ws__text"><span class="ws__name" data-label>Tidewell Studio</span><span class="ws__meta">14 members</span></span>
<svg class="row__check" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.6l4.3 4.3L19 7.2"/></svg>
</button>
<button class="row row--ws" type="button" role="menuitemradio" aria-checked="false" tabindex="-1" style="--ws: #3b3f99" data-ws="ferncastle" data-role="Guest" data-plan="Pro plan">
<span class="ws__logo" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 20.5V4.5M12 9.2 8 6.6M12 12.8l-4.8-2.8M12 16.4l-3.4-2.4M12 9.2l4-2.6M12 12.8l4.8-2.8M12 16.4l3.4-2.4"/></svg></span>
<span class="ws__text"><span class="ws__name" data-label>Ferncastle</span><span class="ws__meta">Client · 6 members</span></span>
<svg class="row__check" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.6l4.3 4.3L19 7.2"/></svg>
</button>
<button class="row row--ws" type="button" role="menuitemradio" aria-checked="false" tabindex="-1" style="--ws: #7c3a6b" data-ws="sketchbook" data-role="Owner" data-plan="Free">
<span class="ws__logo" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M5 19l1.2-4.4 9.4-9.4a2 2 0 0 1 2.8 0l.4.4a2 2 0 0 1 0 2.8l-9.4 9.4zM13.8 7l3.2 3.2"/></svg></span>
<span class="ws__text"><span class="ws__name" data-label>Sketchbook</span><span class="ws__meta">Personal · just you</span></span>
<svg class="row__check" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.6l4.3 4.3L19 7.2"/></svg>
</button>
<div class="menu__rule" role="separator"></div>
<button class="row" type="button" role="menuitem" tabindex="-1" data-item="new">
<svg class="row__icon" viewBox="0 0 24 24" aria-hidden="true" data-plus><path d="M12 5.5v13M5.5 12h13"/></svg>
<span class="row__label" data-label>New workspace</span>
</button>
</div>
</div>
<div class="page page--ask" data-page="signout" hidden>
<svg class="ask__door" viewBox="0 0 48 48" aria-hidden="true" data-door>
<path class="ask__frame" d="M26 8.5H13.5a3 3 0 0 0-3 3v25a3 3 0 0 0 3 3H26"/>
<path class="ask__arrow" d="M20.5 24h19M33.5 17.8l6.2 6.2-6.2 6.2"/>
</svg>
<p class="ask__title" id="badge-ask">Sign out of <span data-ask-ws>Tidewell Studio</span>?</p>
<p class="ask__text" id="badge-ask-text">You will need to sign in again on this browser. Your files stay put.</p>
<div class="menu menu--ask" role="menu" id="badge-signout" aria-labelledby="badge-ask" aria-describedby="badge-ask-text" tabindex="-1" data-menu="signout">
<span class="menu__hl" aria-hidden="true" data-hl></span>
<button class="row pill pill--danger" type="button" role="menuitem" tabindex="-1" data-item="confirm"><span class="row__label" data-label>Sign out</span></button>
<button class="row pill" type="button" role="menuitem" tabindex="-1" data-item="cancel"><span class="row__label" data-label>Cancel</span></button>
</div>
</div>
<span class="back__stripe" aria-hidden="true"></span>
<p class="back__print" aria-hidden="true">If found, please return to the front desk.</p>
<span class="face__sheen" aria-hidden="true" data-sheen></span>
<span class="face__dim" aria-hidden="true" data-dim></span>
</section>
<!-- the clip strap through the slot, seen from the front and from the back -->
<span class="strap strap--front" aria-hidden="true"><span class="strap__snap"></span></span>
<span class="strap strap--back" aria-hidden="true"><span class="strap__snap"></span></span>
<!-- the thickness of the card, seen when it is edge-on -->
<span class="edge edge--left" aria-hidden="true"></span>
<span class="edge edge--right" aria-hidden="true"></span>
</div>
</div>
<!-- the reel: the button, with the portrait on its domed face -->
<button class="reel" type="button" aria-haspopup="menu" aria-expanded="false" aria-controls="badge-menu" aria-label="Account: Ines Marlow" data-reel data-autoclic>
<span class="reel__body" data-reel-body>
<span class="reel__face">
<svg class="reel__portrait" aria-hidden="true" data-face-in><use href="#portrait"/></svg>
<svg class="reel__portrait" aria-hidden="true" data-face-out><use href="#nobody"/></svg>
</span>
<span class="reel__dome" aria-hidden="true"></span>
</span>
<span class="reel__nozzle" aria-hidden="true"></span>
</button>
<p class="account__note" aria-hidden="true" data-note>Signed out · <b>Sign in</b></p>
</div>
<p class="sr" aria-live="polite" data-say></p>
</main>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
<script src="script.js"></script>
</body>
</html>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.
54 valori da cambiare
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 «Il menu del tesserino» da Animorio e adattala al mio sitoL'anello e il centro del tesserino sono due punti con una massa (0,3 e 1) uniti da un'asta rigida: ogni passo si sommano gravità e forze, i punti si muovono, poi l'asta li riporta alla loro distanza spostandoli in proporzione all'inverso della massa e le velocità si rileggono dalle posizioni (position-based dynamics). Così il tesserino dondola come un pendolo appeso all'anello senza che si scrivano equazioni di angoli. Il conto gira sul ticker di GSAP in passi di 4 millesimi, col fotogramma limitato a 1/30 di secondo, e si ferma quando tutto è fermo.
Il cordino è una molla rigida (CATCH) con uno smorzatore sulla velocità lungo il cordino, ma solo quando è più lungo di CORD: allentato non spinge. Il tesserino parte dentro la bobina, cade libero e il cordino lo prende di colpo: il rimbalzo viene da lì. All'equilibrio è allungato di quanto pesa, (1,3 · GRAVITY) / CATCH. Tirato con la mano la bobina lascia uscire quello che serve; lasciato, la lunghezza torna a CORD su una molla smorzata al punto critico e il cordino, tirando, porta su il tesserino che dondola.
Il tesserino si inclina avanti e indietro come una tessera appesa per il bordo alto: un pendolo fisico lungo D + H² / (12 D), con D dall'anello al centro. Parte inclinato di TIP gradi con una piccola spinta e scende per conto suo. La rotazione sul moschettone è una molla (TWIST) verso 0 o 180 gradi: girandolo si aggiunge una frustata alla velocità, e lei va oltre e torna. Premendo il tesserino, inclinazione e rotazione ricevono un colpetto, più forte quanto più in basso o di lato si preme.
Il tesserino è un contenitore senza misura con preserve-3d, le due facce a backface-visibility hidden (il retro già girato di 180 gradi) e due listelli larghi 2 pixel per lo spessore. A ogni fotogramma riceve una sola transform: la posizione dell'anello, il dondolio letto dall'asta, poi una perspective() centrata sul tesserino con translateY prima e dopo, l'inclinazione intorno all'anello e la rotazione intorno al suo asse. L'opacità va su ogni faccia, mai sul contenitore: appiattirebbe il 3D. Un velo scuro scurisce la faccia che si gira o si china verso di te, e un riflesso a gradiente scivola con gli angoli.
Quando velocità, angoli e allungamento sono sotto una soglia, tutto va esattamente al suo posto e il tesserino si disegna piatto: solo un translate arrotondato e, per il retro, la faccia posteriore senza rotazione. Così le parole sono nitide. Appena qualcosa lo muove torna in 3D e si ridisegna subito, nello stesso fotogramma.
La bobina è un button con aria-haspopup e aria-expanded; davanti c'è un role menu con menuitem, sul retro due menu: le aree di lavoro con menuitemradio e la domanda prima di uscire. Frecce, Home, Fine, Invio, Esc, freccia destra e sinistra per entrare e uscire dal retro, e le prime lettere funzionano come in un menu nativo; la faccia nascosta è inert e il focus passa sull'altra quando arriva davanti. Esc, Tab o un clic fuori riportano il focus alla bobina. Scegliendo un'area di lavoro la fascia, il marchio, il ruolo e il piano si ristampano mentre il davanti è girato.
| Nome | File | Di partenza | Cosa cambia |
|---|---|---|---|
CORD | script.js | 28 | Quanto cordino lascia uscire la bobina quando il tesserino pende, in pixel. |
GRAVITY | script.js | 6400 | Con quanta forza il tesserino cade e dondola, in pixel al secondo quadrato. |
CATCH | script.js | [2400, 0.3] | Il cordino che prende il tesserino: quanto è rigido e quanto frena il rimbalzo (1 = niente rimbalzo). |
TIP | script.js | 70 | Di quanti gradi è inclinato all'indietro il tesserino quando esce dalla bobina. |
LEAN | script.js | 5 | Di quanti gradi pende di lato quando esce, e quindi quanto dondola. |
AIR | script.js | 3.2 | Quanto in fretta si spegne il dondolio. |
TWIST | script.js | [170, 0.42] | Il moschettone che gira il tesserino: quanto è rigido e quanto frena l'oscillazione (1 = niente). |
ROCK | script.js | 1 | Quanto oscilla il tesserino quando lo si preme (0 = per niente). |
GLIDE | script.js | 0.22 | I secondi dell'evidenziazione che va da una voce all'altra. |
Per il menu dell'account di uno strumento per i progetti, uno studio, un coworking, l'app di un evento o di una conferenza: dove avere il proprio tesserino ha senso e il menu si apre poche volte al giorno, così il suo piccolo spettacolo non stanca. Il tesserino pende sotto la bobina: in una barra in alto a destra serve un po' di spazio a sinistra, o una bobina spostata verso il centro.
Tutto è largo 264 pixel e sta in 390 senza sforare; sotto i 360 la scena si rimpicciolisce un poco. Il tocco fa come il clic e accende la voce prima di sceglierla; il tesserino si tira col dito dalla fascia o dalla foto (lì touch-action è none), mentre le voci restano da toccare. La scorciatoia di Settings sul telefono non c'è.
Niente caduta, rimbalzo, dondolio né rotazione: il tesserino compare già appeso con una dissolvenza, passa sul retro e torna sul davanti con una dissolvenza fra le due facce, e uscendo svanisce sul posto. Niente colpetti premendolo, niente trascinamento, niente mosse delle icone; l'evidenziazione salta da una voce all'altra.
GSAP 3.15.0, Host Grotesk (font variabile) 5.3.0.
Compressi con gzip, i tre file della demo pesano 19 KB; le librerie: GSAP 28 KB.
Provata il 6 ottobre 2026
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.
Con l’email e la password del tuo account.
oppure con l’email
Hai fatto l’accesso come .
Con l’account scarichi lo zip di ogni animazione e di ogni pacchetto, gratis.