
Il pulsante che copia
Pulsanti, Testo, SVG, Fisica
NuovaGratis
I campi della carta di un checkout con la carta disegnata sopra: ogni cifra scritta viene coniata nella plastica, si alza in rilievo nuda e poi si riempie d’argento o d’oro, sotto una lampadina che segue il cursore. Le prime cifre scelgono il circuito e il materiale, la luce rilegge il numero da destra come il controllo di Luhn e per il codice di sicurezza la carta si gira.
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 card field</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/hubot-sans@5.3.0/wdth.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/martian-mono@5.3.0/wdth.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the card fields of a checkout, with the card they describe drawn above them. Every figure typed is struck
into the plastic and stands up in relief, lit by a small lamp that follows the caret; the first figures
choose the network and its material, a full number is read by the light from right to left (the Luhn
check), and the card turns over for the security code. The networks and their marks are invented -->
<form class="pay" action="#" novalidate data-pay data-autoclic>
<div class="pay__card" aria-hidden="true" data-stage>
<div class="card__shadow" data-shadow></div>
<div class="card is-blank" data-net="blank" data-card>
<div class="card__face card__face--front" data-face="front">
<div class="card__mats" data-mats></div>
<div class="card__lamp" data-lamp></div>
<div class="card__glint" data-glint></div>
<svg class="card__art" viewBox="0 0 428 270" data-art>
<defs>
<linearGradient id="cc-chip" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#f6e2a8"/>
<stop offset="0.5" stop-color="#d5ad5c"/>
<stop offset="1" stop-color="#a87f37"/>
</linearGradient>
<!-- the relief: the alpha of the figures is a height map, lit by a point light (the lamp) -->
<filter id="cc-emboss" filterUnits="userSpaceOnUse" x="0" y="96" width="428" height="174" color-interpolation-filters="sRGB">
<feGaussianBlur in="SourceAlpha" stdDeviation="0.95" result="bump"/>
<feDiffuseLighting in="bump" surfaceScale="2.6" diffuseConstant="1.05" lighting-color="#ffffff" result="shade">
<fePointLight x="80" y="20" z="220" data-light/>
</feDiffuseLighting>
<feSpecularLighting in="bump" surfaceScale="2.6" specularConstant="1" specularExponent="16" lighting-color="#ffffff" result="glint">
<fePointLight x="80" y="20" z="220" data-light/>
</feSpecularLighting>
<feComposite in="glint" in2="SourceAlpha" operator="in" result="glintIn"/>
<feComposite in="SourceGraphic" in2="shade" operator="arithmetic" k1="0.9" k2="0.42" k3="0" k4="0" result="lit"/>
<feComposite in="glintIn" in2="lit" operator="arithmetic" k1="0" k2="0.85" k3="1" k4="0" result="relief"/>
<feOffset in="bump" dx="-0.8" dy="1.3" result="off" data-cast/>
<feComponentTransfer in="off" result="shadow"><feFuncA type="linear" slope="0.55"/></feComponentTransfer>
<feMerge><feMergeNode in="shadow"/><feMergeNode in="relief"/></feMerge>
</filter>
<!-- the chip catches the same lamp -->
<filter id="cc-metal" filterUnits="userSpaceOnUse" x="28" y="72" width="70" height="56" color-interpolation-filters="sRGB">
<feGaussianBlur in="SourceAlpha" stdDeviation="0.6" result="bump"/>
<feSpecularLighting in="bump" surfaceScale="1.6" specularConstant="0.55" specularExponent="22" lighting-color="#fff4d6" result="glint">
<fePointLight x="80" y="20" z="220" data-light/>
</feSpecularLighting>
<feComposite in="glint" in2="SourceAlpha" operator="in" result="glintIn"/>
<feComposite in="glintIn" in2="SourceGraphic" operator="arithmetic" k1="0" k2="0.45" k3="1" k4="0"/>
</filter>
</defs>
<g class="chip" filter="url(#cc-metal)">
<rect x="34" y="78" width="56" height="42" rx="8" fill="url(#cc-chip)"/>
<path class="chip__lines" d="M34 92.5h15.5a5 5 0 0 1 5 5v0M34 105.5h15.5a5 5 0 0 0 5-5M90 92.5H74.5a5 5 0 0 0-5 5M90 105.5H74.5a5 5 0 0 1-5-5M54.5 78v9.5M69.5 78v9.5M54.5 120v-9.5M69.5 120v-9.5M54.5 87.5h15v23h-15z"/>
</g>
<g class="card__print" data-print>
<text x="168" y="194">VALID</text>
<text x="168" y="201.5">THRU</text>
</g>
<!-- the Atrium cards print their code on the front, above the number -->
<text class="card__frontcode" x="394" y="134" text-anchor="end" data-frontcode></text>
<g class="card__marks" data-marks></g>
<g class="card__relief" filter="url(#cc-emboss)" data-relief>
<g class="card__number" data-number></g>
<g class="card__exp" data-exp></g>
<g class="card__name" data-name></g>
</g>
</svg>
</div>
<div class="card__face card__face--back" data-face="back">
<div class="card__mats" data-mats></div>
<div class="card__lamp" data-lamp></div>
<div class="card__glint" data-glint></div>
<svg class="card__art" viewBox="0 0 428 270">
<defs>
<pattern id="cc-panel" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(35)">
<rect width="6" height="6" fill="#f1efe9"/>
<path d="M0 3h6" stroke="#dcd8ce" stroke-width="1.2"/>
</pattern>
<linearGradient id="cc-stripe" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#2a2a2e"/>
<stop offset="0.5" stop-color="#0d0d0f"/>
<stop offset="1" stop-color="#1c1c20"/>
</linearGradient>
</defs>
<rect x="0" y="30" width="428" height="48" fill="url(#cc-stripe)"/>
<rect x="26" y="102" width="272" height="38" rx="3" fill="url(#cc-panel)"/>
<rect class="card__codebox" x="304" y="102" width="74" height="38" rx="3" data-codebox/>
<g class="card__code" transform="translate(22.4 0) skewX(-10)" data-code></g>
<g class="card__small">
<text x="26" y="184">This card remains the property of the issuer and is used under</text>
<text x="26" y="196">the cardholder agreement. If you find it, please hand it in at</text>
<text x="26" y="208">any bank, or cut it in half and throw it away.</text>
</g>
<g class="card__marks card__marks--back" data-marks></g>
</svg>
</div>
</div>
</div>
<div class="pay__fields">
<div class="field field--number" data-field="number">
<input class="field__input field__input--mono" id="cc-number" name="cardnumber" type="text" inputmode="numeric" autocomplete="cc-number" autocorrect="off" spellcheck="false" enterkeyhint="next" placeholder="1234 1234 1234 1234" aria-describedby="pay-note" data-input="number">
<label class="field__label" for="cc-number">Card number</label>
<svg class="field__tick" viewBox="0 0 20 20" aria-hidden="true" data-tick><path d="M4.6 10.4l3.6 3.6 7.2-8"/></svg>
<span class="field__badge" aria-hidden="true" data-badge></span>
</div>
<div class="pay__row">
<div class="field" data-field="exp">
<input class="field__input field__input--mono" id="cc-exp" name="cc-exp" type="text" inputmode="numeric" autocomplete="cc-exp" autocorrect="off" spellcheck="false" enterkeyhint="next" placeholder="MM / YY" aria-describedby="pay-note" data-input="exp">
<label class="field__label" for="cc-exp">Expiry date</label>
</div>
<div class="field field--code" data-field="code">
<input class="field__input field__input--mono" id="cc-csc" name="cvc" type="text" inputmode="numeric" autocomplete="cc-csc" autocorrect="off" spellcheck="false" enterkeyhint="next" placeholder="3 digits" aria-describedby="pay-note" data-input="code">
<label class="field__label" for="cc-csc">Security code</label>
<span class="field__hint" aria-hidden="true" data-hint>
<span class="hint__card" data-hint-card>
<svg class="hint__side hint__side--back" viewBox="0 0 30 20"><rect x="0.75" y="0.75" width="28.5" height="18.5" rx="3"/><path class="hint__stripe" d="M0.75 4.5h28.5v3.6H0.75z"/><rect class="hint__box" x="18" y="11" width="8" height="4.4" rx="1"/></svg>
<svg class="hint__side hint__side--front" viewBox="0 0 30 20"><rect x="0.75" y="0.75" width="28.5" height="18.5" rx="3"/><rect class="hint__box" x="19" y="4" width="7.5" height="4" rx="1"/><path class="hint__line" d="M4 13.5h18"/></svg>
</span>
</span>
</div>
</div>
<div class="field" data-field="name">
<input class="field__input" id="cc-name" name="ccname" type="text" autocomplete="cc-name" autocapitalize="words" autocorrect="off" spellcheck="false" enterkeyhint="done" maxlength="26" placeholder="As it is printed on the card" aria-describedby="pay-note" data-input="name">
<label class="field__label" for="cc-name">Name on card</label>
</div>
</div>
<p class="pay__note" id="pay-note" aria-live="polite" data-note>Encrypted and sent straight to your bank.</p>
<button class="go" type="submit" data-go>
<span class="go__bg" aria-hidden="true" data-go-bg></span>
<span class="go__label" data-go-label>Pay</span>
<span class="go__state" aria-hidden="true" data-wait><svg class="go__spin" viewBox="0 0 24 24" data-spin><circle cx="12" cy="12" r="8.5"/></svg><span>Authorizing</span></span>
<span class="go__state" aria-hidden="true" data-done><svg class="go__tick" viewBox="0 0 24 24"><path d="M6.2 12.6l3.9 3.9 7.8-8.9"/></svg><span>Paid</span></span>
</button>
<span class="sr" aria-live="polite" data-said></span>
</form>
</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.
46 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 campo della carta» da Animorio e adattala al mio sitoI quattro campi sono input veri con le loro etichette, autocomplete cc-number, cc-exp, cc-csc e cc-name (così il browser e i gestori delle password li riempiono) e inputmode numeric per la tastiera dei numeri. A ogni input il numero si ripulisce e si divide nei gruppi del circuito, e il cursore torna dopo lo stesso numero di cifre che aveva prima. Cancellando uno spazio non succederebbe niente, perché lo spazio tornerebbe subito: nel beforeinput si cancella invece la cifra prima. La data aggiunge lo zero a un mese di una cifra sola e la barra, e capisce anche 12/2029 incollato.
Le regole sono quelle vere (4, 51-55 e 2221-2720, 34 e 37, 6011, 644-649 e 65), i nomi e i marchi sono inventati. Ogni circuito ha la sua lunghezza, i suoi gruppi, la lunghezza e il posto del codice, l’argento o l’oro. I materiali sono livelli sovrapposti nelle due facce: il nuovo si scopre con una maschera CSS a gradiente che scorre in 0,7 secondi, e sul bordo della maschera passa una banda di luce in overlay che ne nasconde il taglio; la stessa banda cambia la lamina di ogni cifra che attraversa. I numeri di altri circuiti restano sulla carta bianca, da 16 a 19 cifre, e il controllo di Luhn vale anche per loro.
Cifre e lettere sono testi SVG in un gruppo col filtro: l’alfa sfocato di un pixel è una mappa d’altezza, feDiffuseLighting e feSpecularLighting la illuminano con un fePointLight (la lampada), il colore della lamina si moltiplica per l’ombreggiatura e un feOffset dello stesso rilievo fa l’ombra, spostata dalla parte opposta alla lampada. Ogni cifra è un gruppo che lo script sposta, con dentro la lettera che si anima: entra con l’opacità (che è anche l’altezza del rilievo) e una scala da 1,3, prima nel colore della plastica e poi, in FOIL secondi, in quello della lamina. Il carattere è Martian Mono: per 19 cifre o per un nome lungo si stringe sull’asse della larghezza prima di rimpicciolire.
Sul ticker di GSAP girano a mano le molle della carta (le due rotazioni, la profondità e il giro) e della lampada (x, y e altezza), in passi di 4 millesimi col fotogramma limitato a 1/30 di secondo. Una cifra coniata dà alla carta una spinta attorno al punto colpito, tanto più forte quanto più il punto è lontano dal centro (STRIKE). La lampada va sulla casella della prossima cifra; la sua pozza sul materiale è un gradiente in soft-light, e sul metallo spazzolato una striscia verticale in screen, come fa davvero un metallo spazzolato. Per il codice la carta gira di 180 gradi sulla molla TURN, e la faccia che non si vede è nascosta anche dallo script nel momento in cui la carta è di taglio.
Un numero completo si rilegge in READ secondi: la lampada scende radente e va da destra a sinistra, e le cifre che il controllo di Luhn raddoppia fanno un saltino quando lei passa; il verdetto arriva con la luce sulla prima cifra. Gli errori si mostrano lasciando il campo, o subito quando non possono essere giusti (un mese 13, una carta scaduta), e se ne vanno appena il campo torna giusto; la riga sotto i campi è aria-live e i campi prendono aria-invalid. Pay controlla tutto, aspetta WAIT secondi e mostra Paid. Un clic da script (detail 0, l’anteprima della libreria) scrive senza prendere il focus, un passo per clic: il numero di prova, la data, il codice, il nome, il pagamento, poi un numero con un refuso e la sua correzione.
| Nome | File | Di partenza | Cosa cambia |
|---|---|---|---|
AMOUNT | script.js | '$64.00' | Quanto addebita il pulsante. |
STRIKE | script.js | 3.4 | Quanto sobbalza la carta quando una cifra viene coniata vicino al bordo, in gradi (0: ferma). |
FOIL | script.js | 0.4 | I secondi che l’argento o l’oro impiega a riempire una cifra appena coniata. |
LAMP | script.js | [70, 0.78] | La molla della lampada: quanto è rigida e quanto è frenata (1 = senza rimbalzo). |
TURN | script.js | [95, 0.62] | La molla che gira la carta per il codice di sicurezza. |
READ | script.js | 0.95 | I secondi in cui la luce rilegge il numero da destra a sinistra (il controllo di Luhn). |
WAIT | script.js | 1.5 | I secondi in cui la banca risponde, nella demo. |
HOLD | script.js | 3.4 | I secondi in cui la carta pagata resta prima che il modulo si svuoti. |
SAMPLE | script.js | '4242 4242 4242 4242' | Il numero di prova che l’anteprima della libreria scrive da sola. |
HOLDER | script.js | 'Ines Okafor' | Il nome che scrive l’anteprima. |
Nella cassa di un negozio o di un abbonamento, nella pagina per aggiungere una carta a un conto, nella prenotazione di un albergo o di un volo: ovunque si scrive una carta e aiuta vederla mentre la si scrive. Per un negozio vero si mettono i circuiti e i loro marchi al posto di quelli inventati, e il pagamento vero al posto di quello finto (la funzione submit). I dati di una carta vera vanno mandati al servizio di pagamento, mai al proprio server; i servizi che danno i campi già pronti dentro un iframe non lasciano leggere le cifre, e lì la carta può seguire solo quello che quei campi raccontano (il circuito, completo o no, l’errore).
Il modulo prende la larghezza dello schermo (358 pixel su un telefono di 390) e la carta resta in proporzione, 358 per 226. I campi restano alti 60 pixel, col testo a 16 pixel così iPhone non ingrandisce la pagina al tocco, e la tastiera è quella dei numeri. Toccando una parte della carta si va al suo campo; la lampada che segue il puntatore, sul telefono, non c’è: segue solo il cursore.
Niente colpi, sobbalzi o molle: le cifre compaiono in dissolvenza già nel loro colore, il materiale cambia in dissolvenza, la carta passa dal davanti al retro con una dissolvenza invece di girarsi, la lampada va subito al suo posto, la lettura di Luhn dà subito il verdetto e i campi non tremano.
GSAP 3.15.0, Hubot Sans (font variabile) 5.3.0, Martian Mono (font variabile) 5.3.0.
Compressi con gzip, i tre file della demo pesano 22 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.