Skip to content

Instantly share code, notes, and snippets.

@rsp2k
Created September 15, 2025 15:28
Show Gist options
  • Save rsp2k/7f669ab97674626597c4ec90e731b7f0 to your computer and use it in GitHub Desktop.
Save rsp2k/7f669ab97674626597c4ec90e731b7f0 to your computer and use it in GitHub Desktop.
Pressure Sensitive Tip Button [Webflow x GSAP]
<span class="arrow arrow--instruction">
<span>hold to flip coin</span>
<svg viewBox="0 0 97 52" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M74.568 0.553803C74.0753 0.881909 73.6295 1.4678 73.3713 2.12401C73.1367 2.70991 72.3858 4.67856 71.6584 6.50658C70.9544 8.35803 69.4526 11.8031 68.3498 14.1936C66.1441 19.0214 65.839 20.2167 66.543 21.576C67.4581 23.3337 69.4527 23.9196 71.3064 22.9821C72.4797 22.3728 74.8965 19.5839 76.9615 16.4435C78.8387 13.5843 78.8387 13.6077 78.1113 18.3418C77.3369 23.4275 76.4687 26.2866 74.5915 30.0364C73.254 32.7316 71.8461 34.6299 69.218 37.3485C65.9563 40.6999 62.2254 42.9732 57.4385 44.4965C53.8718 45.6449 52.3935 45.8324 47.2546 45.8324C43.3594 45.8324 42.1158 45.7386 39.9805 45.2933C32.2604 43.7466 25.3382 40.9577 19.4015 36.9735C15.0839 34.0909 12.5028 31.7004 9.80427 27.9975C6.80073 23.9196 4.36038 17.2403 3.72682 11.475C3.37485 8.1471 3.1402 7.32683 2.43624 7.13934C0.770217 6.71749 0.183578 7.77211 0.0193217 11.5219C-0.26226 18.5996 2.55356 27.1304 7.17619 33.1066C13.8403 41.7545 25.432 48.4103 38.901 51.2696C41.6465 51.8555 42.2566 51.9023 47.4893 51.9023C52.3935 51.9023 53.426 51.832 55.5144 51.3867C62.2723 49.9337 68.5375 46.6292 72.949 42.1998C76.0464 39.1296 78.1113 36.2939 79.8946 32.7081C82.1942 28.0912 83.5317 23.3103 84.2591 17.17C84.3999 15.8576 84.6111 14.7795 84.7284 14.7795C84.8223 14.7795 85.4559 15.1311 86.1364 15.5763C88.037 16.7716 90.3835 17.8965 93.5748 19.0918C96.813 20.3339 97.3996 20.287 96.4141 18.9512C94.9123 16.9122 90.055 11.5219 87.1219 8.63926C84.0949 5.66288 83.8368 5.33477 83.5552 4.1864C83.3909 3.48332 83.0155 2.68649 82.6401 2.31151C82.0065 1.6553 80.4109 1.04595 79.9885 1.30375C79.8712 1.37406 79.2845 1.11626 78.6744 0.717845C77.2431 -0.172727 75.7413 -0.243024 74.568 0.553803Z"
fill="currentColor"
/>
</svg>
</span>
<main>
<button aria-label="Leave a tip" data-tipping="false">
<span class="content">
<span class="scene">
<span class="hole"></span>
<div class="purse">
<div class="coin">
<div class="coin__face coin__face--front">
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<title>Webflow</title>
<path
d="m24 4.515-7.658 14.97H9.149l3.205-6.204h-.144C9.566 16.713 5.621 18.973 0 19.485v-6.118s3.596-.213 5.71-2.435H0V4.515h6.417v5.278l.144-.001 2.622-5.277h4.854v5.244h.144l2.72-5.244H24Z"
fill="currentColor"
/>
</svg>
<!-- <svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 273.6 360"
>
<path
d="M217.02 167.04c18.63-9.48 30.29-26.18 27.57-54-3.67-38.03-36.53-50.78-78.01-54.4V5.88h-32.15v51.35c-8.46 0-17.08.17-25.66.34l-.01-51.68H76.65v52.72c-6.96.14-13.8.28-20.47.28v-.16l-44.33-.01V93s23.74-.45 23.35-.02c13.01.01 17.26 7.56 18.48 14.08l.01 60.08v84.4c-.57 4.09-2.98 10.63-12.08 10.64.41.36-23.38 0-23.38 0l-6.38 38.33h41.82c7.8 0 15.45.13 22.96.19l.03 53.34h32.1v-52.77c8.82.18 17.35.25 25.68.24l-.01 52.54h32.13l.02-53.25c54.02-3.1 91.84-16.7 96.55-67.39 3.79-40.8-15.44-59.02-46.1-66.38zM109.53 95.32c18.13 0 75.14-5.77 75.14 32.07 0 36.26-57 32.03-75.13 32.03v-64.1zm0 167.13v-70.67c21.78-.01 90.09-6.27 90.1 35.32 0 39.87-68.32 35.33-90.1 35.35z"
fill="currentColor"
/>
</svg> -->
</div>
<div class="coin__core"></div>
<div class="coin__core coin__core--rotated"></div>
<div class="coin__face coin__face--rear">
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<title>Webflow</title>
<path
d="m24 4.515-7.658 14.97H9.149l3.205-6.204h-.144C9.566 16.713 5.621 18.973 0 19.485v-6.118s3.596-.213 5.71-2.435H0V4.515h6.417v5.278l.144-.001 2.622-5.277h4.854v5.244h.144l2.72-5.244H24Z"
fill="currentColor"
/>
</svg>
<!-- <svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 273.6 360"
>
<path
d="M217.02 167.04c18.63-9.48 30.29-26.18 27.57-54-3.67-38.03-36.53-50.78-78.01-54.4V5.88h-32.15v51.35c-8.46 0-17.08.17-25.66.34l-.01-51.68H76.65v52.72c-6.96.14-13.8.28-20.47.28v-.16l-44.33-.01V93s23.74-.45 23.35-.02c13.01.01 17.26 7.56 18.48 14.08l.01 60.08v84.4c-.57 4.09-2.98 10.63-12.08 10.64.41.36-23.38 0-23.38 0l-6.38 38.33h41.82c7.8 0 15.45.13 22.96.19l.03 53.34h32.1v-52.77c8.82.18 17.35.25 25.68.24l-.01 52.54h32.13l.02-53.25c54.02-3.1 91.84-16.7 96.55-67.39 3.79-40.8-15.44-59.02-46.1-66.38zM109.53 95.32c18.13 0 75.14-5.77 75.14 32.07 0 36.26-57 32.03-75.13 32.03v-64.1zm0 167.13v-70.67c21.78-.01 90.09-6.27 90.1 35.32 0 39.87-68.32 35.33-90.1 35.35z"
fill="currentColor"
/>
</svg> -->
</div>
</div>
</div>
</span>
<span>Leave tip</span>
</span>
</button>
</main>
<a
class="bear-link"
href="https://twitter.com/intent/follow?screen_name=jh3yy"
target="_blank"
rel="noreferrer noopener"
>
<svg
class="w-9"
viewBox="0 0 969 955"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="161.191"
cy="320.191"
r="133.191"
stroke="currentColor"
stroke-width="20"
></circle>
<circle
cx="806.809"
cy="320.191"
r="133.191"
stroke="currentColor"
stroke-width="20"
></circle>
<circle
cx="695.019"
cy="587.733"
r="31.4016"
fill="currentColor"
></circle>
<circle
cx="272.981"
cy="587.733"
r="31.4016"
fill="currentColor"
></circle>
<path
d="M564.388 712.083C564.388 743.994 526.035 779.911 483.372 779.911C440.709 779.911 402.356 743.994 402.356 712.083C402.356 680.173 440.709 664.353 483.372 664.353C526.035 664.353 564.388 680.173 564.388 712.083Z"
fill="currentColor"
></path>
<rect
x="310.42"
y="448.31"
width="343.468"
height="51.4986"
fill="#FF1E1E"
></rect>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M745.643 288.24C815.368 344.185 854.539 432.623 854.539 511.741H614.938V454.652C614.938 433.113 597.477 415.652 575.938 415.652H388.37C366.831 415.652 349.37 433.113 349.37 454.652V511.741L110.949 511.741C110.949 432.623 150.12 344.185 219.845 288.24C289.57 232.295 384.138 200.865 482.744 200.865C581.35 200.865 675.918 232.295 745.643 288.24Z"
fill="currentColor"
></path>
</svg>
</a>
import gsap from 'https://cdn.skypack.dev/[email protected]'
import { Physics2DPlugin } from 'https://cdn.skypack.dev/[email protected]/Physics2DPlugin'
import { Pane } from 'https://cdn.skypack.dev/[email protected]'
gsap.registerPlugin(Physics2DPlugin)
const button = document.querySelector('[aria-label="Leave a tip"]')
const coin = button.querySelector('.coin')
const config = {
theme: 'light',
power: '',
muted: true,
timeScale: 1.1,
distance: {
lower: 100,
upper: 350,
},
bounce: {
lower: 2,
upper: 12,
},
velocity: {
lower: 300,
upper: 700,
},
rotation: {
lower: 0,
upper: 15,
},
flipSpeed: {
lower: 0.25,
upper: 0.6,
},
spins: {
lower: 1,
upper: 6,
},
rotate: {
lower: 0,
upper: 90,
},
}
const tipSound = new Audio('https://myinstants.com/media/sounds/coin_1.mp3')
tipSound.volume = 0.3
tipSound.muted = config.muted
const tip = () => {
if (button.dataset.tipping === 'true') return
const currentRotation = gsap.getProperty(button, 'rotate')
if (currentRotation < 0) document.documentElement.dataset.flipped = 'true'
button.dataset.tipping = 'true'
const duration = gsap.utils.mapRange(
config.rotation.lower,
config.rotation.upper,
0,
config.flipSpeed.upper
)(Math.abs(currentRotation))
const distance = gsap.utils.snap(
1,
gsap.utils.mapRange(
config.rotation.lower,
config.rotation.upper,
config.distance.lower,
config.distance.upper
)(Math.abs(currentRotation))
)
const velocity = gsap.utils.mapRange(
config.rotation.lower,
config.rotation.upper,
config.velocity.lower,
config.velocity.upper
)(Math.abs(currentRotation))
const bounce = gsap.utils.mapRange(
config.velocity.lower,
config.velocity.upper,
config.bounce.lower,
config.bounce.upper
)(Math.abs(velocity))
const distanceDuration = gsap.utils.mapRange(
config.distance.lower,
config.distance.upper,
config.flipSpeed.lower,
config.flipSpeed.upper
)(distance)
const spin = gsap.utils.snap(
1,
gsap.utils.mapRange(
config.distance.lower,
config.distance.upper,
config.spins.lower,
config.spins.upper
)(distance)
)
const offRotate =
gsap.utils.random(config.rotate.lower, config.rotate.upper, 1) * -1
const hangtime = Math.max(1, duration * 4)
const tl = gsap
.timeline({
onComplete: () => {
if (config.muted === false) {
tipSound.muted = config.muted
tipSound.play()
}
gsap.set(coin, {
yPercent: 100,
})
gsap
.timeline({
onComplete: () => {
gsap.set(button, { clearProps: 'all' })
gsap.set(coin, { clearProps: 'all' })
gsap.set('.purse', { clearProps: 'all' })
button.dataset.tipping = 'false'
},
})
.to(button, {
yPercent: bounce,
repeat: 1,
duration: 0.12,
yoyo: true,
})
.fromTo(
'.hole',
{
scale: 1,
},
{
scale: 0,
duration: 0.2,
delay: 0.2,
}
)
.set(coin, {
clearProps: 'all',
})
.set(coin, {
yPercent: -50,
})
.fromTo(
'.purse',
{
xPercent: -200,
},
{
delay: 0.5,
xPercent: 0,
duration: 0.5,
ease: 'power1.out',
}
)
.fromTo(
coin,
{
rotate: -460,
},
{
rotate: 0,
duration: 0.5,
ease: 'power1.out',
},
'<'
)
.timeScale(config.timeScale)
},
})
.set(button, { transition: 'none' })
.fromTo(
button,
{
rotate: currentRotation,
},
{
rotate: 0,
duration,
ease: 'elastic.out(1.75,0.75)',
}
)
.to(
coin,
{
onUpdate: function () {
const y = gsap.getProperty(coin, 'y')
if (y >= coin.offsetHeight) {
this.progress(1)
tl.progress(1)
}
},
duration: hangtime,
physics2D: {
velocity,
angle: -90,
gravity: 1000,
},
},
`>-${duration * 0.825}`
)
.fromTo(
coin,
{
rotateX: 0,
},
{
duration: distanceDuration * 2,
rotateX: spin * -360,
},
'<'
)
.to(
coin,
{
rotateY: offRotate,
duration: distanceDuration,
},
'<'
)
.to(
coin,
{
'--rx': offRotate,
duration: distanceDuration,
},
'<'
)
.fromTo(
'.hole',
{
scale: 0,
},
{
scale: 1,
duration: 0.2,
},
hangtime * 0.35
)
.timeScale(config.timeScale)
}
button.addEventListener('click', tip)
const ctrl = new Pane({
title: 'config',
expanded: true,
})
const update = () => {
document.documentElement.dataset.theme = config.theme
}
const sync = (event) => {
if (
!document.startViewTransition ||
event.target.controller.view.labelElement.innerText !== 'theme'
)
return update()
document.startViewTransition(() => update())
}
ctrl.addBinding(config, 'timeScale', {
label: 'timescale',
min: 0.1,
max: 2,
step: 0.1,
})
ctrl.addBinding(config, 'muted', {
label: 'muted',
})
ctrl.addBinding(config, 'power', {
label: 'power',
disabled: true,
})
ctrl.addBinding(config, 'theme', {
label: 'theme',
options: {
System: 'system',
Light: 'light',
Dark: 'dark',
},
})
ctrl.on('change', sync)
update()
@import url('https://unpkg.com/normalize.css') layer(normalize);
@import url('https://fonts.googleapis.com/css2?family=Gloria+Hallelujah&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
@layer normalize, base, demo;
@layer demo {
:root {
--ru: 15;
}
*,
*::before,
*::after {
transform-style: preserve-3d;
}
[data-flipped='true'] .arrow {
opacity: 0;
}
.tp-lblv.tp-v-disabled .tp-lblv_l {
/* color: var(--lbl-fg) !important; */
opacity: 1 !important;
}
:root:has([aria-label]:active) .tp-txtv.tp-v-disabled {
clip-path: inset(0 0 0 0);
}
.arrow {
display: inline-block;
opacity: 0.8;
position: fixed;
font-size: 0.875rem;
font-family: 'Gloria Hallelujah', cursive;
transition: opacity 0.26s 0.26s ease-out;
&.arrow--instruction {
top: 50%;
left: 50%;
translate: -190% 150%;
rotate: -10deg;
svg {
color: hsl(0 0% 65%);
scale: 1 1;
top: 130%;
rotate: 20deg;
left: -10%;
width: 80%;
translate: 105% 40%;
position: absolute;
}
}
}
.tp-txtv.tp-v-disabled {
height: 14.3px;
background: repeating-linear-gradient(
90deg,
var(--lbl-fg) 0 3%,
#0000 3% 5%
);
clip-path: inset(0 100% 0 0);
transition: clip-path 0.26s;
input {
display: none;
}
}
main {
scale: 1.2;
transform: translate3d(0, 0, 100vmax);
}
[aria-label] {
touch-action: none;
user-select: none;
-webkit-tap-highlight-color: #0000;
--bg: #1871f4;
background: var(--bg);
border-radius: 6px;
font-size: 0.875rem;
color: #fff;
font-family: inherit;
border: 1px solid color-mix(in oklch, var(--bg), #000 12%);
cursor: pointer;
transform-origin: 75% 50%;
transition: transform 0.26s, box-shadow 0.26s;
padding: 0;
--shadow-color: 0 0% 0%;
box-shadow: 0px 0.6px 0.7px hsl(var(--shadow-color) / 0.14),
0px 2.3px 2.6px -0.8px hsl(var(--shadow-color) / 0.14),
0px 5.9px 6.6px -1.7px hsl(var(--shadow-color) / 0.14),
0px 14.5px 16.3px -2.5px hsl(var(--shadow-color) / 0.14);
.content {
align-items: center;
clip-path: inset(-100vmax 0 1px 0);
display: flex;
gap: 0.75rem;
padding: 0.5rem 0.75rem;
height: 100%;
}
&[data-tipping='false']:active {
transform: rotate(calc(var(--ru) * -1deg));
box-shadow: -0.5px 0.7px 1px hsl(var(--shadow-color) / 0.14),
-1.8px 2.3px 3.3px -0.8px hsl(var(--shadow-color) / 0.14),
-4.6px 6px 8.5px -1.7px hsl(var(--shadow-color) / 0.14),
-11.4px 14.6px 20.8px -2.5px hsl(var(--shadow-color) / 0.14);
}
&:is(:focus-visible, :hover) {
--bg: color-mix(in oklch, #1871f4, #000 5%);
.purse {
rotate: y 360deg;
transition: rotate 0.26s 0.12s ease-out;
}
}
.purse {
height: 100%;
width: 100%;
position: absolute;
inset: 0;
transform-style: preserve-3d;
}
.scene {
--thickness: 4;
display: inline-block;
width: 1.2lh;
aspect-ratio: 1;
position: relative;
transform-style: preserve-3d;
perspective: 100vh;
.hole {
position: absolute;
z-index: 10;
inset: 0;
scale: 0;
transform-style: preserve-3d;
transform: translate3d(0, 0, calc(var(--thickness) * -2px));
transform-origin: 50% 70%;
&::before {
content: '';
position: absolute;
width: 125%;
height: 40%;
border-radius: 50%;
top: 70%;
left: 50%;
translate: -50% -50%;
background: black;
box-shadow: 0 2px hsl(0 0% 20%) inset;
}
&::after {
transform-style: preserve-3d;
content: '';
background: var(--bg);
height: 200%;
top: 0;
left: 50%;
translate: -50% 25%;
width: 121%;
position: absolute;
transform: translate3d(0, 0, calc(var(--thickness) * 5px));
mask: radial-gradient(
125% 32% at 50% 3%,
rgba(0, 0, 0, 0) 50%,
#fff 50%
);
}
}
}
}
.coin {
--depth: 2;
--detail: hsl(43 97% 46%);
--face: #ffdc02;
--side: #f4ae00;
width: 100%;
aspect-ratio: 1;
/* outline: 2px dashed red; */
border-radius: 50%;
position: absolute;
translate: -50% -50%;
top: 50%;
left: 50%;
transform-style: preserve-3d;
.coin__core {
height: 100%;
width: calc(var(--depth) * 2px);
background: var(--side);
position: absolute;
top: 50%;
left: 50%;
translate: -50% -50%;
transform: rotateY(90deg) rotateX(calc((90 - var(--rx, 0)) * -1deg));
transform-style: preserve-3d;
&.coin__core--rotated {
--base: 90;
transform: rotateY(90deg) rotateX(calc((90 - var(--rx, 0)) * 1deg));
}
&::after,
&::before {
content: '';
height: 100%;
width: calc(var(--depth) * 2px);
background: var(--side);
position: absolute;
inset: 0;
transform-style: preserve-3d;
}
&::after {
transform: rotateX(calc((var(--base, 0) - var(--rx, 0)) * 1deg));
}
&::before {
transform: rotateX(calc((var(--base, 0) - var(--rx, 0)) * -1deg));
}
}
.coin__face {
height: 100%;
width: 100%;
position: absolute;
inset: 0;
border-radius: 50%;
transform-style: preserve-3d;
background: var(--face);
display: grid;
place-items: center;
color: var(--detail);
svg {
width: 65%;
scale: -1 1;
translate: -5% 0;
}
&::after {
content: '';
position: absolute;
inset: 0;
border-radius: 50%;
background: var(--side);
backface-visibility: hidden;
}
&.coin__face--front {
transform: translate3d(0, 0, calc((var(--depth) * 1px) + 0.5px))
rotateY(180deg);
}
&.coin__face--rear {
transform: translate3d(0, 0, calc((var(--depth) * -1px) - 0.5px));
}
}
}
}
@layer base {
:root {
--font-size-min: 16;
--font-size-max: 20;
--font-ratio-min: 1.2;
--font-ratio-max: 1.33;
--font-width-min: 375;
--font-width-max: 1500;
}
html {
color-scheme: light dark;
}
[data-theme='light'] {
color-scheme: light only;
}
[data-theme='dark'] {
color-scheme: dark only;
}
:where(.fluid) {
--fluid-min: calc(
var(--font-size-min) * pow(var(--font-ratio-min), var(--font-level, 0))
);
--fluid-max: calc(
var(--font-size-max) * pow(var(--font-ratio-max), var(--font-level, 0))
);
--fluid-preferred: calc(
(var(--fluid-max) - var(--fluid-min)) /
(var(--font-width-max) - var(--font-width-min))
);
--fluid-type: clamp(
(var(--fluid-min) / 16) * 1rem,
((var(--fluid-min) / 16) * 1rem) -
(((var(--fluid-preferred) * var(--font-width-min)) / 16) * 1rem) +
(var(--fluid-preferred) * var(--variable-unit, 100vi)),
(var(--fluid-max) / 16) * 1rem
);
font-size: var(--fluid-type);
}
*,
*:after,
*:before {
box-sizing: border-box;
}
body {
background: light-dark(#fff, #000);
display: grid;
overflow: hidden;
place-items: center;
min-height: 100vh;
font-family: 'SF Pro Text', 'SF Pro Icons', 'AOS Icons', 'Helvetica Neue',
Helvetica, Arial, sans-serif, system-ui;
}
body::before {
--size: 45px;
--line: color-mix(in hsl, canvasText, transparent 80%);
content: '';
height: 100vh;
width: 100vw;
position: fixed;
background: linear-gradient(
90deg,
var(--line) 1px,
transparent 1px var(--size)
)
calc(var(--size) * 0.36) 50% / var(--size) var(--size),
linear-gradient(var(--line) 1px, transparent 1px var(--size)) 0%
calc(var(--size) * 0.32) / var(--size) var(--size);
mask: linear-gradient(-20deg, transparent 50%, white);
top: 0;
transform-style: flat;
pointer-events: none;
z-index: -1;
}
.bear-link {
color: canvasText;
position: fixed;
top: 1rem;
left: 1rem;
width: 48px;
aspect-ratio: 1;
display: grid;
place-items: center;
opacity: 0.8;
}
:where(.x-link, .bear-link):is(:hover, :focus-visible) {
opacity: 1;
}
.bear-link svg {
width: 75%;
}
/* Utilities */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment