Phone mockup
Pure CSSA phone with real thickness: screen, back, four thin walls and a stack of rounded slabs that fills the corners. It turns to show both sides.
A thick business card with painted edges. Hover or focus and it flips to show the back; the logo and the text are lifted off the paper with translateZ, so they shift against it as it turns.
Your edited version
translateZ(±2 units), the back turned with rotateY(180deg). Both hide their backface, so each shows only while it faces you.preserve-3d, and the logo and the lines of text are lifted off it with translateZ(1.5–6 units). As the card turns, they slide against the paper.transform. The flip is a plain transition with a small overshoot.pointer-events: none, so the flip cannot pull the card out from under the pointer.--u, tied to the canvas, so the card is the same share of a gallery card, the editor and a recording canvas.two faces at ±depth/2, back turned 180degrounded slabs + walls for the edgeembossing = children lifted with translateZflip and idle sway on separate elements<div class="scene">
<div class="bizcard" tabindex="0">
<div class="float">
<i class="shadow"></i>
<div class="card">
<i class="slab" style="--i:0"></i><i class="slab" style="--i:1"></i><i class="slab" style="--i:2"></i><i class="slab" style="--i:3"></i>
<i class="wall wall-t"></i><i class="wall wall-b"></i><i class="wall wall-l"></i><i class="wall wall-r"></i>
<div class="front">
<b class="logo"><i></i><i></i><i></i></b>
<strong>PRISM</strong><small>DESIGN STUDIO</small>
</div>
<div class="back">
<b></b>
<strong>Alex Morgan</strong><small>Creative director</small>
<span>alex@prism.studio</span><span>+1 555 0142</span><span>prism.studio</span>
</div>
</div>
</div>
</div>
</div>
/* how far the card is flipped: 0 = front, 1 = back (registered, so it can transition) */
@property --flip {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
.scene {
/* one base unit: every length below is a multiple of it, so the card is the same share of a
card, the editor, a full screen and a recording canvas */
--u: 0.4vmin;
perspective: calc(800 * var(--u));
}
/* the static hit area */
.bizcard {
--ink: #0f1020;
--edge: #ff4d9d;
--foil: #ffd291;
--foil-deep: #aa6c16;
display: grid;
place-items: center;
width: calc(220 * var(--u));
height: calc(170 * var(--u));
border-radius: calc(16 * var(--u));
cursor: pointer;
font-family: Inter, system-ui, sans-serif;
transform-style: preserve-3d;
}
/* the tilt and a slow idle sway */
.float {
position: relative;
width: calc(176 * var(--u));
height: calc(100 * var(--u));
pointer-events: none;
transform-style: preserve-3d;
animation: float 6s ease-in-out infinite alternate;
}
.shadow {
position: absolute;
inset: calc(10 * var(--u)) calc(-6 * var(--u)) calc(-16 * var(--u));
border-radius: 50%;
background: radial-gradient(closest-side, rgb(0 0 0 / 0.38), transparent);
transform: translateZ(calc(-36 * var(--u)));
}
/* the flip, on its own element */
.card {
position: absolute;
inset: 0;
transform-style: preserve-3d;
transform: rotateY(calc(var(--flip) * 180deg));
/* quick through the side-on middle (where thin layers flicker), then overshoot and settle */
transition: --flip 0.9s linear(0, 0.03 18%, 0.5 42%, 0.97 60%, 1.05 74%, 1);
}
.bizcard:hover .card,
.bizcard:focus-visible .card {
--flip: 1;
}
/* 4 units thick: four rounded slabs fill the corners of the edge... */
.slab {
position: absolute;
inset: calc(0.5 * var(--u)); /* at rest the slabs draw the edge: they face you, so it stays one clean line */
border-radius: calc(6 * var(--u));
background: var(--edge);
transform: translateZ(calc((var(--i) - 1.5) * 1 * var(--u)));
}
/* ...four walls give the straight parts a lit surface (inset by the 6 units radius) */
/* the walls only show near side-on (mid-flip); at rest a thin wall seen side-on breaks into dashes */
.wall {
position: absolute;
opacity: clamp(0, 1 - pow(var(--flip) - 0.5, 2) * 40, 1);
background: linear-gradient(90deg, #b3366e, var(--edge) 50%, #ff82ba);
}
.wall-t, .wall-b { left: calc(6 * var(--u)); top: calc(50% - 2 * var(--u)); width: calc(164 * var(--u)); height: calc(4 * var(--u)); }
.wall-l, .wall-r { top: calc(6 * var(--u)); left: calc(50% - 2 * var(--u)); width: calc(4 * var(--u)); height: calc(88 * var(--u)); }
.wall-t { transform: rotateX(90deg) translateZ(calc(50 * var(--u))); }
.wall-b { transform: rotateX(-90deg) translateZ(calc(50 * var(--u))); }
.wall-l { transform: rotateY(-90deg) translateZ(calc(88 * var(--u))); }
.wall-r { transform: rotateY(90deg) translateZ(calc(88 * var(--u))); }
/* each printed side is 3D itself, so its print can be lifted off it */
.front,
.back {
position: absolute;
inset: 0;
box-sizing: border-box;
display: flex;
flex-direction: column;
border-radius: calc(6 * var(--u));
box-shadow: inset 0 0 0 calc(1 * var(--u)) rgb(255 255 255 / 0.24); /* a faint light rim: the outline reads smooth */
transform-style: preserve-3d;
backface-visibility: hidden;
}
.front > *,
.back > * {
backface-visibility: hidden;
/* the lifted print fades out while the card is side-on (--flip near 0.5) */
opacity: clamp(0, pow(var(--flip) - 0.5, 2) * 60, 1);
}
.front {
align-items: center;
justify-content: center;
gap: calc(3 * var(--u));
background:
radial-gradient(circle at 50% 38%, rgb(139 108 255 / 0.38), transparent 55%),
repeating-linear-gradient(135deg, rgb(255 255 255 / 0.035) 0 calc(1 * var(--u)), transparent calc(1 * var(--u)) calc(6 * var(--u))),
var(--ink);
transform: translateZ(calc(2 * var(--u)));
}
.front strong {
margin-top: calc(6 * var(--u));
color: var(--foil);
font-size: calc(13 * var(--u));
font-weight: 800;
letter-spacing: calc(5 * var(--u));
text-indent: calc(5 * var(--u)); /* balances the space after the last letter */
text-shadow: 0 calc(1 * var(--u)) 0 var(--foil-deep);
transform: translateZ(calc(3 * var(--u)));
}
.front small {
color: rgb(255 255 255 / 0.6);
font-size: calc(5.5 * var(--u));
font-weight: 600;
letter-spacing: calc(2.5 * var(--u));
text-indent: calc(2.5 * var(--u));
transform: translateZ(calc(1.5 * var(--u)));
}
/* foil prism logo, lifted the most */
.logo {
position: relative;
width: calc(34 * var(--u));
height: calc(30 * var(--u));
transform-style: preserve-3d;
transform: translateZ(calc(6 * var(--u)));
}
.logo i {
position: absolute;
clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
.logo i:nth-child(1) { inset: 0; background: linear-gradient(135deg, #fff6dc, var(--foil) 40%, var(--foil-deep)); }
.logo i:nth-child(2) { inset: calc(9 * var(--u)) calc(7.5 * var(--u)) calc(3 * var(--u)); background: var(--ink); }
.logo i:nth-child(3) {
top: calc(15 * var(--u));
left: calc(30 * var(--u));
width: calc(26 * var(--u));
height: calc(9 * var(--u));
clip-path: polygon(0 40%, 100% 0, 100% 100%, 0 60%); /* the spectrum fanning out */
background: linear-gradient(#ff4d9d 0 33%, #ffb547 0 66%, #2ee6d6 0);
}
.back {
justify-content: center;
padding: calc(12 * var(--u)) calc(14 * var(--u));
color: #fff;
background:
/* deep enough for the white details (4.5:1 and more): the bright #8b6cff to #c55dcb was 3.7:1 */
radial-gradient(circle at 100% 0%, rgb(255 181 71 / 0.35), transparent 50%),
linear-gradient(135deg, #6a45f5, #a3339f);
transform: rotateY(180deg) translateZ(calc(2 * var(--u)));
}
.back strong { font-size: calc(13 * var(--u)); font-weight: 800; line-height: 1; text-shadow: 0 calc(1 * var(--u)) 0 rgb(0 0 0 / 0.25); transform: translateZ(calc(3 * var(--u))); }
.back small { margin: calc(3 * var(--u)) 0 calc(9 * var(--u)); font-size: calc(6.5 * var(--u)); font-weight: 600; letter-spacing: calc(1.5 * var(--u)); text-transform: uppercase; opacity: 0.85; transform: translateZ(calc(2 * var(--u))); }
.back span {
display: flex;
align-items: center;
gap: calc(5 * var(--u));
margin-top: calc(3 * var(--u));
font-size: calc(6.5 * var(--u));
font-weight: 500;
transform: translateZ(calc(1.5 * var(--u)));
}
.back span::before {
content: '';
width: calc(4 * var(--u));
height: calc(4 * var(--u));
border-radius: 50%;
background: #fff;
box-shadow: 0 0 0 calc(1.5 * var(--u)) rgb(255 77 157 / 0.6);
}
/* the small mark in the corner */
.back b {
position: absolute;
top: calc(12 * var(--u));
right: calc(14 * var(--u));
width: calc(18 * var(--u));
height: calc(16 * var(--u));
clip-path: polygon(50% 0, 100% 100%, 0 100%);
background: linear-gradient(135deg, #fff, rgb(255 255 255 / 0.55));
transform: translateZ(calc(4 * var(--u)));
}
@keyframes float {
from { transform: translateY(calc(-3 * var(--u))) rotateX(16deg) rotateY(-20deg) rotateZ(-5deg); }
to { transform: translateY(calc(3 * var(--u))) rotateX(10deg) rotateY(-8deg) rotateZ(-3deg); }
}