Magnetic button
CSS + JSInside its field the button shifts and leans toward the pointer while its label floats higher, so the two slide apart. JS only reports the pointer position; release it and a springy transition takes it home.
Press it. The edge is a generated box-shadow stack that collapses on :active.
Your edited version
--u, tied to the canvas, so the cap, its type and the depth of its side are the same share of a gallery card, the editor and a recording canvas. The numbers below are those units.box-shadows going straight down, one every 4 units. Each copy is the whole cap, so they overlap into one solid slab.<button> is a static, invisible hit target; the visible cap is a <span> inside it. A pressed element that moves itself can slide out from under the pointer.:active, move the cap down with translateY and shrink the stack by the same amount — the base appears to stay put.rotateX tilt sells the perspective.box-shadow stack:active on a static hit targetrotateX tilt<div class="scene">
<button class="push" type="button"><span>PUSH</span></button>
</div>
.scene {
/* one base unit: every length below is a multiple of it, so the button is the same share of a
card, the editor, a full screen and a recording canvas */
--u: 0.28vmin;
perspective: calc(600 * var(--u));
}
/* static hit target (its bottom padding covers where the cap travels to). It is lifted a little
because the edge hangs below the cap: lifted, the solid button is centred. The drop shadow is
kept tight so it adds little to what is drawn beyond what the eye sees */
.push {
padding: 0 0 calc(32 * var(--u));
border: 0;
background: none;
cursor: pointer;
transform: translateY(calc(-10 * var(--u))) rotateX(30deg);
}
/* the visible cap */
.push span {
display: block;
padding: calc(56 * var(--u)) calc(22 * var(--u));
border-radius: calc(20 * var(--u));
font: 900 calc(26 * var(--u)) / 1.2 Inter, system-ui;
letter-spacing: 0.14em;
color: #fff;
background: linear-gradient(#ff4d9d, #d63a80);
pointer-events: none;
box-shadow:
0 calc(4 * var(--u)) 0 #8f2a58, 0 calc(8 * var(--u)) 0 #8f2a58,
0 calc(12 * var(--u)) 0 #8f2a58, 0 calc(16 * var(--u)) 0 #8f2a58,
0 calc(20 * var(--u)) 0 #8f2a58, 0 calc(24 * var(--u)) 0 #8f2a58,
0 calc(28 * var(--u)) 0 #8f2a58, 0 calc(32 * var(--u)) 0 #8f2a58,
0 calc(36 * var(--u)) 0 #8f2a58, 0 calc(40 * var(--u)) 0 #8f2a58,
0 calc(48 * var(--u)) calc(24 * var(--u)) rgb(0 0 0 / 0.5);
transition: transform 0.08s, box-shadow 0.08s;
}
.push:active span {
transform: translateY(calc(32 * var(--u)));
box-shadow:
0 calc(4 * var(--u)) 0 #8f2a58, 0 calc(8 * var(--u)) 0 #8f2a58,
0 calc(16 * var(--u)) calc(14 * var(--u)) rgb(0 0 0 / 0.5);
}