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.
Over-ear headphones turning on a stand: the band is a ring of tangent strips, each cup a short cylinder laid on its side. Hover or focus and they turn to face you and light up.
Your edited version
rotateZ, moves out by the radius with translateY(-58 units) and folds flat with rotateX(90deg): together they are the top surface of an arch. Two masked rings close its sides.rotateY(i × 30deg) translateZ(r), a disc on each end), then laid on its side with rotateZ(±90deg) so the top disc faces outward.cos() / sin() of its own angle, so the strips facing up are the brightest.@property values animate: the lap angle --spin and a weight --k. The transform uses --spin × --k, and hover eases --k to 0, which glides from wherever the lap is to the front pose.pointer-events: none, so the hover never flickers.--u, tied to the canvas, so the headset is the same share of a gallery card, the editor and a recording canvas.arc = strips at rotateZ(a) translateY(−r) rotateX(90deg)cylinder built upright, laid down with rotateZ(90deg)shading from sin() / cos() of the strip angle@property blend: hover eases --k from 1 to 0<div class="scene">
<div class="headphones" tabindex="0" role="img" aria-label="Headphones that turn and light up on hover or focus">
<div class="rig">
<i class="shadow"></i>
<div class="band">
<b class="side"></b><b class="side"></b>
<i style="--i:0"></i>
<i style="--i:1"></i>
<i style="--i:2"></i>
<i style="--i:3"></i>
<i style="--i:4"></i>
<i style="--i:5"></i>
<i style="--i:6"></i>
<i style="--i:7"></i>
<i style="--i:8"></i>
<i style="--i:9"></i>
<i style="--i:10"></i>
<i style="--i:11"></i>
</div>
<i class="slider slider-l"></i><i class="slider slider-lf"></i>
<i class="yoke yoke-l"></i>
<div class="cup cup-l">
<i style="--i:0"></i>
<i style="--i:1"></i>
<i style="--i:2"></i>
<i style="--i:3"></i>
<i style="--i:4"></i>
<i style="--i:5"></i>
<i style="--i:6"></i>
<i style="--i:7"></i>
<i style="--i:8"></i>
<i style="--i:9"></i>
<i style="--i:10"></i>
<i style="--i:11"></i>
<b class="outer"><em></em></b>
<b class="cushion"></b>
</div>
<i class="slider slider-r"></i><i class="slider slider-rf"></i>
<i class="yoke yoke-r"></i>
<div class="cup cup-r">
<i style="--i:0"></i>
<i style="--i:1"></i>
<i style="--i:2"></i>
<i style="--i:3"></i>
<i style="--i:4"></i>
<i style="--i:5"></i>
<i style="--i:6"></i>
<i style="--i:7"></i>
<i style="--i:8"></i>
<i style="--i:9"></i>
<i style="--i:10"></i>
<i style="--i:11"></i>
<b class="outer"><em></em></b>
<b class="cushion"></b>
</div>
</div>
</div>
</div>
/* registered, so they can be animated and transitioned */
@property --spin {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@property --k {
syntax: '<number>';
inherits: false;
initial-value: 1;
}
.scene {
/* one base unit: every length below is a multiple of it, so the headset is the same share of a
card, the editor, a full screen and a recording canvas */
--u: 0.34vmin;
perspective: calc(800 * var(--u));
}
/* the static hit area */
.headphones {
--shell: color-mix(in srgb, #8b6cff 34%, #17182a);
--shell-hi: color-mix(in srgb, #8b6cff 55%, #3a3d5c);
--leather: color-mix(in srgb, #8b6cff 14%, #1b1c29);
--leather-hi: color-mix(in srgb, #8b6cff 22%, #3a3c52);
--stitch: color-mix(in srgb, #8b6cff 80%, #fff);
--pad: #16171f;
--metal: #b8bdd2;
--metal-dark: #5c6280;
--glow: #2ee6d6;
display: grid;
place-items: center;
width: calc(216 * var(--u));
height: calc(184 * var(--u));
border-radius: calc(16 * var(--u));
cursor: pointer;
transform-style: preserve-3d;
}
/* 0 × 0: every part is placed around this point */
.rig {
position: relative;
width: 0;
height: 0;
pointer-events: none;
transform-style: preserve-3d;
/* the front pose (-28deg) plus the lap, weighted by --k */
transform: rotateX(-14deg) rotateY(calc(-28deg + var(--spin) * var(--k)));
animation: turn 16s linear infinite;
transition: --k 1.1s cubic-bezier(0.45, 0, 0.2, 1);
}
.headphones:hover .rig,
.headphones:focus-visible .rig {
--k: 0; /* the lap keeps running, it just stops counting */
}
.shadow {
position: absolute;
left: calc(-80 * var(--u));
top: calc(34 * var(--u));
width: calc(160 * var(--u));
height: calc(80 * var(--u));
border-radius: 50%;
background: radial-gradient(closest-side, rgb(0 0 0 / 0.45), transparent);
transform: rotateX(90deg);
}
/* --- the band: 12 strips over 200deg, radius 58 units --- */
.band {
position: absolute;
top: calc(-22 * var(--u));
transform-style: preserve-3d;
}
.band i {
--a: calc(-100deg + (var(--i) + 0.5) * 16.667deg);
position: absolute;
left: calc(-8.8 * var(--u));
top: calc(-11 * var(--u));
width: calc(17.6 * var(--u)); /* one side of the polygon, a hair wider: no seams */
height: calc(22 * var(--u)); /* the band's width, front to back */
background:
linear-gradient(rgb(0 0 0 / calc(0.5 - 0.46 * cos(var(--a)))) 0 0), /* shade: none on top */
repeating-linear-gradient(90deg, var(--stitch) 0 calc(2 * var(--u)), transparent calc(2 * var(--u)) calc(4 * var(--u))) 0 calc(3 * var(--u)) / 100% calc(1 * var(--u)) no-repeat,
repeating-linear-gradient(90deg, var(--stitch) 0 calc(2 * var(--u)), transparent calc(2 * var(--u)) calc(4 * var(--u))) 0 calc(18 * var(--u)) / 100% calc(1 * var(--u)) no-repeat,
linear-gradient(var(--leather), var(--leather-hi) 45%, var(--leather));
transform: rotateZ(var(--a)) translateY(calc(-58 * var(--u))) rotateX(90deg);
}
/* the band's side walls: a ring, masked to the same 200deg */
.side {
position: absolute;
left: calc(-58 * var(--u));
top: calc(-58 * var(--u));
width: calc(116 * var(--u));
height: calc(116 * var(--u));
background: radial-gradient(closest-side, transparent calc(100% - 6 * var(--u)), var(--leather) calc(100% - 5.5 * var(--u)), color-mix(in srgb, var(--leather) 70%, #000) 100%, transparent 100%);
mask: conic-gradient(from -100deg, #000 0 200deg, transparent 0);
transform: translateZ(calc(11 * var(--u)));
}
.side + .side {
transform: translateZ(calc(-11 * var(--u)));
}
/* --- metal: sliders (two crossed planes) and yokes (half rings) --- */
.slider {
position: absolute;
top: calc(-18 * var(--u));
left: calc(-5 * var(--u));
width: calc(10 * var(--u));
height: calc(20 * var(--u));
background: linear-gradient(90deg, var(--metal-dark), var(--metal) 45%, #eef0fa 55%, var(--metal-dark));
}
.slider-l { transform: translateX(calc(-55 * var(--u))) rotateY(90deg); }
.slider-r { transform: translateX(calc(55 * var(--u))) rotateY(90deg); }
.slider-lf { left: calc(-1.5 * var(--u)); width: calc(3 * var(--u)); transform: translateX(calc(-55 * var(--u))); }
.slider-rf { left: calc(-1.5 * var(--u)); width: calc(3 * var(--u)); transform: translateX(calc(55 * var(--u))); }
.yoke {
position: absolute;
left: calc(-35 * var(--u));
top: 0;
width: calc(70 * var(--u));
height: calc(70 * var(--u));
background: radial-gradient(closest-side, transparent calc(100% - 4 * var(--u)), var(--metal) calc(100% - 3.5 * var(--u)), var(--metal-dark) 100%, transparent 100%);
mask: conic-gradient(from -90deg, #000 0 180deg, transparent 0);
}
.yoke-l { transform: translateX(calc(-55 * var(--u))) rotateY(90deg); }
.yoke-r { transform: translateX(calc(55 * var(--u))) rotateY(90deg); }
/* --- cups: built standing (axis Y), then laid down; top disc = outside --- */
.cup {
--dir: 1; /* which way "up" went: flips the light */
position: absolute;
top: calc(35 * var(--u));
transform-style: preserve-3d;
transform: translateX(calc(55 * var(--u))) rotateZ(90deg);
}
.cup-l {
--dir: -1;
transform: translateX(calc(-55 * var(--u))) rotateZ(-90deg);
}
/* 12 strips, radius 31 units, 26 units thick: 17 units shell, a seam, 9 units cushion */
.cup i {
position: absolute;
left: calc(-8.6 * var(--u));
top: calc(-13 * var(--u));
width: calc(17.2 * var(--u));
height: calc(26 * var(--u));
backface-visibility: hidden;
background:
linear-gradient(rgb(0 0 0 / calc(0.3 + 0.28 * var(--dir) * sin(var(--i) * 30deg))) 0 0),
linear-gradient(var(--shell-hi) 0 calc(2 * var(--u)), var(--shell) calc(4 * var(--u)) calc(15 * var(--u)), var(--shell-hi) calc(16.5 * var(--u)), transparent calc(16.5 * var(--u))),
linear-gradient(transparent calc(17 * var(--u)), #2a2c3c calc(18 * var(--u)), var(--pad) calc(21 * var(--u)), #0b0c12);
transform: rotateY(calc(var(--i) * 30deg)) translateZ(calc(31 * var(--u)));
}
/* the seam light: its own layer, so hover only changes opacity */
.cup i::after {
content: '';
position: absolute;
inset: calc(16 * var(--u)) 0 auto;
height: calc(1.5 * var(--u));
background: var(--glow);
opacity: 0.25;
transition: opacity 0.5s;
}
.outer,
.cushion {
position: absolute;
left: calc(-31.5 * var(--u));
top: calc(-31.5 * var(--u));
width: calc(63 * var(--u));
height: calc(63 * var(--u));
border-radius: 50%;
backface-visibility: hidden;
}
.outer {
background:
radial-gradient(circle, var(--metal) 0 calc(2.5 * var(--u)), transparent calc(3 * var(--u))),
radial-gradient(circle, transparent 0 calc(7 * var(--u)), var(--metal) calc(7.5 * var(--u)) calc(8.5 * var(--u)), transparent calc(9 * var(--u))),
radial-gradient(circle, transparent 0 83%, var(--metal-dark) 85%, #eef0fa 90%, var(--metal) 94%, var(--metal-dark)),
radial-gradient(circle at 35% 30%, var(--shell-hi), var(--shell) 60%, color-mix(in srgb, var(--shell) 60%, #000));
transform: rotateX(90deg) translateZ(calc(13 * var(--u)));
}
.outer em {
position: absolute;
inset: calc(9 * var(--u));
border: calc(2 * var(--u)) solid var(--glow);
border-radius: 50%;
box-shadow: 0 0 calc(8 * var(--u)) var(--glow), inset 0 0 calc(8 * var(--u)) var(--glow);
opacity: 0.2;
transition: opacity 0.5s;
}
.cushion {
background: radial-gradient(circle, #06070b 0 52%, #2a2c3c 60%, var(--pad) 72%, #2a2c3c 86%, #0b0c12);
transform: rotateX(-90deg) translateZ(calc(13 * var(--u)));
}
.headphones:hover .outer em,
.headphones:focus-visible .outer em,
.headphones:hover .cup i::after,
.headphones:focus-visible .cup i::after {
opacity: 1;
}
/* -180 → 180: blending back to the pose never takes more than half a turn */
@keyframes turn {
from { --spin: -180deg; }
to { --spin: 180deg; }
}