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 faceted glass bottle you can see into: an octagonal prism of glass around a smaller prism of liquid, a fluted gold cap and a label. A glint sweeps across each time it faces you.
Your edited version
rotateY(n × 45deg) and pushed out to their distance from the axis.(w − d) / 2 / √2, alternately left and right.--w --d --c --h), so the liquid is simply the same prism again, a glass thickness smaller, standing on the thick glass bottom.clip-path.overflow: hidden), moved with translateX while the bottle passes the front.octagonal prism: 8 faces at 45° stepssee-through faces drawn from both sidesa prism inside a prism for the liquidglint = translated gradient in a clipped face<div class="scene">
<div class="bottle">
<i class="shadow"></i>
<div class="prism liquid">
<i style="--n:0"></i>
<i style="--n:1"></i>
<i style="--n:2"></i>
<i style="--n:3"></i>
<i style="--n:4"></i>
<i style="--n:5"></i>
<i style="--n:6"></i>
<i style="--n:7"></i>
<b class="octagon surface"></b>
</div>
<b class="octagon floor"></b>
<div class="prism glass">
<i style="--n:0"><em class="glint"></em></i>
<i style="--n:1"></i>
<i style="--n:2"></i>
<i style="--n:3"></i>
<i style="--n:4"></i>
<i style="--n:5"></i>
<i style="--n:6"></i>
<i style="--n:7"></i>
</div>
<b class="octagon top"></b>
<span class="label"><b>LUMEN</b><small>EAU DE PARFUM</small></span>
<div class="box neck"><i></i><i></i><i></i><i></i></div>
<div class="box cap"><i><em class="glint"></em></i><i></i><i></i><i></i><i></i></div>
</div>
</div>
.scene {
perspective: 800px;
}
.bottle {
--gold: #ffcf87;
--gold-deep: #aa6c16;
position: relative;
width: 84px;
height: 96px;
transform-style: preserve-3d;
animation: sway 8s ease-in-out infinite alternate;
}
.shadow {
position: absolute;
left: -18px;
top: 56px;
width: 120px;
height: 80px;
border-radius: 50%;
background: radial-gradient(closest-side, rgb(80 20 50 / 0.55), transparent);
transform: rotateX(90deg);
}
/* --- the octagonal prism: width, depth, corner cut, height --- */
.prism {
position: absolute;
left: calc((84px - var(--w)) / 2);
width: var(--w);
height: var(--h);
transform-style: preserve-3d;
}
.glass { --w: 84px; --d: 44px; --c: 9px; --h: 96px; top: 0; }
.liquid { --w: 74px; --d: 34px; --c: 6.93px; --h: 50px; top: 33px; } /* 5px glass walls, 13px glass floor */
.prism > i {
position: absolute;
top: 0;
box-sizing: border-box;
left: calc((var(--w) - var(--fw)) / 2);
width: var(--fw);
height: var(--h);
transform: rotateY(calc(var(--n) * 45deg)) translateX(var(--fx, 0px)) translateZ(var(--fz));
}
/* front and back */
.prism > i:nth-child(4n + 1) { --fw: calc(var(--w) - 2 * var(--c)); --fz: calc(var(--d) / 2); }
/* the two sides */
.prism > i:nth-child(4n + 3) { --fw: calc(var(--d) - 2 * var(--c)); --fz: calc(var(--w) / 2); }
/* the four corner cuts: 45deg, but also slid sideways when w != d */
.prism > i:nth-child(even) {
--fw: calc(var(--c) * 1.4142);
--fz: calc((var(--w) / 2 + var(--d) / 2 - var(--c)) / 1.4142);
}
.prism > i:nth-child(4n + 2) { --fx: calc((var(--w) - var(--d)) / 2 / 1.4142); }
.prism > i:nth-child(4n) { --fx: calc((var(--d) - var(--w)) / 2 / 1.4142); }
/* an octagon of the prism's footprint, laid flat */
.octagon {
position: absolute;
left: 0;
width: var(--w, 84px);
height: var(--d, 44px);
--c: 9px;
clip-path: polygon(var(--c) 0, calc(100% - var(--c)) 0, 100% var(--c), 100% calc(100% - var(--c)),
calc(100% - var(--c)) 100%, var(--c) 100%, 0 calc(100% - var(--c)), 0 var(--c));
}
.top {
top: -22px;
background: radial-gradient(ellipse 16px 11px, rgb(255 255 255 / 0.3), transparent), rgb(200 190 255 / 0.18);
transform: rotateX(90deg);
}
.floor {
top: 74px;
background: rgb(190 175 255 / 0.2);
transform: rotateX(-90deg);
}
/* glass: tinted, light edges, drawn from both sides; the lower 13px is the thick bottom */
.glass > i {
--lit: 0.1;
border: 1px solid rgb(236 238 251 / 0.38);
border-radius: 2px;
background:
linear-gradient(transparent calc(100% - 14px), rgb(255 255 255 / 0.35) calc(100% - 13px), rgb(255 255 255 / 0.12) calc(100% - 10px), rgb(255 255 255 / 0.2)),
linear-gradient(90deg, rgb(255 255 255 / calc(var(--lit) * 1.8)), rgb(255 255 255 / var(--lit)) 30%, rgb(255 255 255 / calc(var(--lit) * 0.5)) 70%, rgb(255 255 255 / var(--lit))),
rgb(139 108 255 / 0.08);
}
.glass > i:nth-child(even) { --lit: 0.24; } /* the corner cuts catch the light */
.glass > i:nth-child(3),
.glass > i:nth-child(7) { --lit: 0.05; }
.glass > i:first-child { overflow: hidden; } /* flat face: clipping is safe */
.liquid > i {
background:
linear-gradient(90deg, rgb(255 255 255 / 0.18), transparent 35% 70%, rgb(0 0 0 / 0.18)),
linear-gradient(#ff7a78, #f0569f);
opacity: 0.82;
}
.surface {
--w: 74px;
--d: 34px;
--c: 6.93px;
top: -17px;
background: radial-gradient(ellipse 60% 70% at 40% 40%, rgb(255 255 255 / 0.45), transparent), #ff8a70;
opacity: 0.9;
transform: rotateX(90deg);
}
.glint {
position: absolute;
top: -20%;
left: 0;
width: 22px;
height: 140%;
background: linear-gradient(90deg, transparent, rgb(255 255 255 / 0.7) 45% 55%, transparent);
transform: translateX(-40px) rotate(18deg);
animation: glint 8s linear infinite;
}
.label {
position: absolute;
left: 13px;
top: 26px;
display: grid;
place-items: center;
align-content: center;
gap: 3px;
box-sizing: border-box;
width: 58px;
height: 36px;
border: 1px solid var(--gold);
background: #0d0e17;
box-shadow: inset 0 0 0 2px #0d0e17, inset 0 0 0 2.6px var(--gold-deep);
color: var(--gold);
font-family: Georgia, 'Times New Roman', serif;
line-height: 1;
backface-visibility: hidden;
transform: translateZ(22.6px); /* just in front of the glass */
}
.label b { font-size: 11px; font-weight: 400; letter-spacing: 2.5px; text-indent: 2.5px; }
.label small { font: 4.5px system-ui, sans-serif; letter-spacing: 0.8px; white-space: nowrap; opacity: 0.85; }
/* --- neck and cap: four walls and a lid --- */
.box {
position: absolute;
transform-style: preserve-3d;
}
.box > * { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
.box > :nth-child(1) { transform: translateZ(calc(var(--bd) / 2)); }
.box > :nth-child(2) { transform: rotateY(180deg) translateZ(calc(var(--bd) / 2)); }
.box > :nth-child(3),
.box > :nth-child(4) { left: calc(50% - var(--bd) / 2); width: var(--bd); }
.box > :nth-child(3) { transform: rotateY(90deg) translateZ(calc(var(--bw) / 2)); }
.box > :nth-child(4) { transform: rotateY(-90deg) translateZ(calc(var(--bw) / 2)); }
.box > :nth-child(5) { top: calc(var(--bd) / -2); height: var(--bd); transform: rotateX(90deg); }
.neck {
--bw: 24px;
--bd: 18px;
left: 30px;
top: -7px;
width: 24px;
height: 7px;
}
.neck > * { background: linear-gradient(var(--gold-deep), var(--gold) 50%, var(--gold-deep)); }
.cap {
--bw: 42px;
--bd: 30px;
left: 21px;
top: -39px;
width: 42px;
height: 32px;
}
/* vertical fluting over polished gold */
.cap > * {
background:
repeating-linear-gradient(90deg, rgb(255 255 255 / 0.22) 0 1.5px, transparent 1.5px 3px, rgb(0 0 0 / 0.12) 3px 4.5px, transparent 4.5px 6px),
linear-gradient(90deg, var(--gold-deep), var(--gold) 40%, #fff6dc 50%, var(--gold) 60%, var(--gold-deep));
}
.cap > :nth-child(1) { overflow: hidden; }
.cap > :nth-child(3),
.cap > :nth-child(4) {
background:
repeating-linear-gradient(90deg, rgb(255 255 255 / 0.18) 0 1.5px, transparent 1.5px 3px, rgb(0 0 0 / 0.14) 3px 4.5px, transparent 4.5px 6px),
linear-gradient(90deg, var(--gold-deep), #e2a54f);
}
.cap > :nth-child(5) {
background:
radial-gradient(circle, transparent 0 7px, var(--gold-deep) 7.5px 8.5px, transparent 9px),
linear-gradient(135deg, #fff6dc, var(--gold) 45%, var(--gold-deep));
}
.cap .glint { animation-delay: -0.5s; }
@keyframes sway {
from { transform: rotateX(-12deg) rotateY(-38deg); }
to { transform: rotateX(-12deg) rotateY(38deg); }
}
/* one sweep per half swing, while the bottle passes the front */
@keyframes glint {
0%, 36% { transform: translateX(-40px) rotate(18deg); }
64%, 100% { transform: translateX(90px) rotate(18deg); }
}
@use 'sass:math';
@use 'sass:list';
// A faceted perfume bottle: an octagonal glass prism (a box with its four vertical edges cut off)
// with a smaller octagonal prism of liquid inside, a gold cap and a label.
$W: 84px; // body width
$D: 44px; // body depth
$H: 96px; // body height
$c: 9px; // how much each vertical edge is cut off
$g: 5px; // glass wall thickness
$base: 13px; // the thick glass bottom
$fill: 50px; // liquid height
$sway: 8s;
// Eight faces around the Y axis: front, corner, side, corner, back, corner, side, corner. Each
// face is turned to its angle and pushed out to its distance from the axis (half the depth,
// half the width, or the corner cut's distance).
@mixin octa-faces($w, $d, $c, $h) {
$wide: $w - 2 * $c;
$narrow: $d - 2 * $c;
$cut: $c * math.sqrt(2);
$cut-z: math.div($w * 0.5 + $d * 0.5 - $c, math.sqrt(2));
// A corner cut faces 45deg, but when width != depth its middle is NOT on the 45deg ray from
// the axis: it also slides sideways, by (w - d) / 2 / sqrt(2), alternately left and right.
$slide: math.div(($w - $d) * 0.5, math.sqrt(2));
// width, distance and slide for: front / back, a corner, a side, a corner (by $i % 4)
$widths: ($wide, $cut, $narrow, $cut);
$dists: ($d * 0.5, $cut-z, $w * 0.5, $cut-z);
$slides: (0px, $slide, 0px, -$slide);
@for $i from 0 to 8 {
$a: $i * 45deg;
$fw: list.nth($widths, $i % 4 + 1);
$fz: list.nth($dists, $i % 4 + 1);
$fx: list.nth($slides, $i % 4 + 1);
> i:nth-child(#{$i + 1}) {
left: ($w - $fw) * 0.5;
width: $fw;
transform: rotateY($a) translateX($fx) translateZ($fz);
}
}
> i {
position: absolute;
top: 0;
height: $h;
}
}
// An octagon the size of the prism's footprint (for the lid, the floor, the liquid's surface).
@function octagon($w, $d, $c) {
@return polygon($c 0, $w - $c 0, $w $c, $w $d - $c, $w - $c $d, $c $d, 0 $d - $c, 0 $c);
}
// Four walls and a lid, for the neck and the cap.
@mixin box-sides($w, $d, $h) {
> * {
position: absolute;
top: 0;
left: 0;
width: $w;
height: $h;
}
> :nth-child(1) {
transform: translateZ($d * 0.5);
}
> :nth-child(2) {
transform: rotateY(180deg) translateZ($d * 0.5);
}
> :nth-child(3),
> :nth-child(4) {
left: ($w - $d) * 0.5;
width: $d;
}
> :nth-child(3) {
transform: rotateY(90deg) translateZ($w * 0.5);
}
> :nth-child(4) {
transform: rotateY(-90deg) translateZ($w * 0.5);
}
> :nth-child(5) {
top: -$d * 0.5;
height: $d;
transform: rotateX(90deg);
}
}
.d-perfume {
--gold: color-mix(in srgb, var(--warm) 78%, #fff);
--gold-deep: color-mix(in srgb, var(--warm) 62%, #3a2200);
--edge: color-mix(in srgb, var(--text) 38%, transparent);
position: relative;
width: $W;
height: $H;
transform-style: preserve-3d;
animation: d-perfume-sway $sway ease-in-out infinite alternate;
&__shadow {
position: absolute;
left: -18px;
top: $H - 40px;
width: $W + 36px;
height: 80px;
border-radius: 50%;
background: radial-gradient(closest-side, color-mix(in srgb, var(--hot) 30%, rgb(0 0 0 / 0.45)), transparent);
transform: rotateX(90deg);
}
// --- glass -------------------------------------------------------------------------------
// Faces are see-through and drawn from both sides, so the far walls show through the near
// ones. The lower $base px of every wall is the thick, clearer glass bottom.
&__glass {
position: absolute;
inset: 0;
transform-style: preserve-3d;
@include octa-faces($W, $D, $c, $H);
> i {
--lit: 0.1;
border: 1px solid var(--edge);
border-radius: 2px;
background:
linear-gradient(transparent calc(100% - #{$base + 1px}), rgb(255 255 255 / 0.35) calc(100% - #{$base}), rgb(255 255 255 / 0.12) calc(100% - #{$base - 3px}), rgb(255 255 255 / 0.2)),
linear-gradient(90deg, rgb(255 255 255 / calc(var(--lit) * 1.8)), rgb(255 255 255 / var(--lit)) 30%, rgb(255 255 255 / calc(var(--lit) * 0.5)) 70%, rgb(255 255 255 / var(--lit))),
color-mix(in srgb, var(--accent) 8%, transparent);
}
// the corner cuts catch the light
> i:nth-child(2n) {
--lit: 0.24;
}
> i:nth-child(3),
> i:nth-child(7) {
--lit: 0.05;
}
// front wall: carries the sweeping glint (overflow is fine, this face is flat)
> i:first-child {
overflow: hidden;
}
}
&__glint {
position: absolute;
top: -20%;
left: 0;
width: 22px;
height: 140%;
background: linear-gradient(90deg, transparent, rgb(255 255 255 / 0.7) 45% 55%, transparent);
transform: translateX(-40px) rotate(18deg);
animation: d-perfume-glint $sway linear infinite;
}
&__top,
&__floor {
position: absolute;
left: 0;
top: -$D * 0.5;
width: $W;
height: $D;
clip-path: octagon($W, $D, $c);
background:
radial-gradient(ellipse 16px 11px, rgb(255 255 255 / 0.3), transparent),
color-mix(in srgb, var(--accent) 10%, rgb(255 255 255 / 0.14));
transform: rotateX(90deg);
}
&__floor {
top: $H - $D * 0.5;
background: color-mix(in srgb, var(--accent) 16%, rgb(255 255 255 / 0.1));
transform: rotateX(-90deg);
}
// --- liquid: the same prism, smaller, standing on the thick bottom ---------------------------
&__liquid {
$lw: $W - 2 * $g;
$ld: $D - 2 * $g;
$lc: $c - $g * (math.sqrt(2) - 1); // a parallel cut, $g further in
position: absolute;
left: $g;
top: $H - $base - $fill;
width: $lw;
height: $fill;
transform-style: preserve-3d;
@include octa-faces($lw, $ld, $lc, $fill);
> i {
background:
linear-gradient(90deg, rgb(255 255 255 / 0.18), transparent 35% 70%, rgb(0 0 0 / 0.18)),
linear-gradient(color-mix(in srgb, var(--hot) 55%, var(--warm)), color-mix(in srgb, var(--hot) 80%, var(--accent)));
opacity: 0.82;
}
// the surface of the liquid
b {
position: absolute;
left: 0;
top: -$ld * 0.5;
width: $lw;
height: $ld;
clip-path: octagon($lw, $ld, $lc);
background:
radial-gradient(ellipse 60% 70% at 40% 40%, rgb(255 255 255 / 0.45), transparent),
color-mix(in srgb, var(--hot) 45%, var(--warm));
opacity: 0.9;
transform: rotateX(90deg);
}
}
// --- label -------------------------------------------------------------------------------
&__label {
position: absolute;
left: $c + 4px;
top: 26px;
display: grid;
place-items: center;
align-content: center;
gap: 3px;
width: $W - 2 * $c - 8px;
height: 36px;
border: 1px solid var(--gold);
background: #0d0e17;
box-shadow: inset 0 0 0 2px #0d0e17, inset 0 0 0 2.6px var(--gold-deep);
color: var(--gold);
font-family: Georgia, 'Times New Roman', serif;
line-height: 1;
backface-visibility: hidden;
transform: translateZ($D * 0.5 + 0.6px);
b {
font-size: 11px;
font-weight: 400;
letter-spacing: 2.5px;
text-indent: 2.5px; // letter-spacing adds space after the last letter too; this rebalances it
}
small {
font-family: system-ui, sans-serif;
font-size: 4.5px;
letter-spacing: 0.8px;
white-space: nowrap;
opacity: 0.85;
}
}
// --- neck and cap ------------------------------------------------------------------------
&__neck {
position: absolute;
left: ($W - 24px) * 0.5;
top: -7px;
width: 24px;
height: 7px;
transform-style: preserve-3d;
@include box-sides(24px, 18px, 7px);
> * {
background: linear-gradient(var(--gold-deep), var(--gold) 50%, var(--gold-deep));
}
}
&__cap {
position: absolute;
left: ($W - 42px) * 0.5;
top: -7px - 32px;
width: 42px;
height: 32px;
transform-style: preserve-3d;
@include box-sides(42px, 30px, 32px);
// vertical fluting over a lit gold gradient
> * {
background:
repeating-linear-gradient(90deg, rgb(255 255 255 / 0.22) 0 1.5px, transparent 1.5px 3px, rgb(0 0 0 / 0.12) 3px 4.5px, transparent 4.5px 6px),
linear-gradient(90deg, var(--gold-deep), var(--gold) 40%, #fff6dc 50%, var(--gold) 60%, var(--gold-deep));
}
> :nth-child(1) {
overflow: hidden;
}
> :nth-child(3),
> :nth-child(4) {
background:
repeating-linear-gradient(90deg, rgb(255 255 255 / 0.18) 0 1.5px, transparent 1.5px 3px, rgb(0 0 0 / 0.14) 3px 4.5px, transparent 4.5px 6px),
linear-gradient(90deg, var(--gold-deep), color-mix(in srgb, var(--gold) 70%, var(--gold-deep)));
}
> :nth-child(5) {
background:
radial-gradient(circle, transparent 0 7px, var(--gold-deep) 7.5px 8.5px, transparent 9px),
linear-gradient(135deg, #fff6dc, var(--gold) 45%, var(--gold-deep));
}
.d-perfume__glint {
animation-delay: $sway * -0.06;
}
}
}
@keyframes d-perfume-sway {
from {
transform: rotateX(-12deg) rotateY(-38deg);
}
to {
transform: rotateX(-12deg) rotateY(38deg);
}
}
// One sweep per half swing, while the bottle passes the front (the middle of each half).
@keyframes d-perfume-glint {
0%,
36% {
transform: translateX(-40px) rotate(18deg);
}
64%,
100% {
transform: translateX(90px) rotate(18deg);
}
}