Pure CSS Scenes & objects #loop

Laptop lid in pure CSS

The lid hinges on the back edge. Its two sides are separate faces: shell and screen.

  • 56 lines of CSS
  • 5 lines of HTML
  • No JavaScript
  • No dependencies
  • Snippet under MIT
GitHub

How it works

  1. First make a floor: rotateX(64deg) rotateZ(28deg) on the base. Everything inside now lives on that tilted plane.
  2. The lid has the same footprint and lies flat on the base when closed. transform-origin: top puts the hinge on the back edge.
  3. A single element has one look from both sides, so the lid is two pseudo-elements: shell on the front, screen pre-flipped with rotateY(180deg), both backface-visibility: hidden.
  4. Open past vertical (104°) and the screen side turns toward the camera.
  5. The loop starts open and closes, then opens again. A paused card holds the first frame, and an open laptop is the pose that reads as a laptop; shut, it is a slab low in the frame.
  6. Every length is a multiple of one base unit, --u, tied to the canvas: the base is 220 × 145 units, so the laptop is the same share of a gallery card, the editor and a recording canvas.

Key techniques

  • floor plane via rotateX + rotateZ
  • transform-origin: top
  • two-sided element with backface-visibility

Copy-paste code

HTML 5 lines

<div class="scene">
  <div class="laptop">
    <div class="lid"></div>
  </div>
</div>

CSS 56 lines

.scene {
  /* one base unit: every length below is a multiple of it, so the laptop is the same share of a
     gallery card, the editor, a full screen and a recording canvas */
  --u: 0.27vmin;
  perspective: calc(900 * var(--u));
}

.laptop {
  position: relative;
  width: calc(220 * var(--u));
  height: calc(145 * var(--u));
  border-radius: calc(10 * var(--u));
  background: linear-gradient(#c9cede, #9aa1b8);
  transform-style: preserve-3d;
  /* the lid stands up out of the top of the base, so the base sits below the middle and the
     laptop plus its open lid is what ends up centred, not the shut base on its own */
  transform: translateY(calc(62 * var(--u))) rotateX(64deg) rotateZ(28deg);
}

.lid {
  position: absolute;
  inset: 0;
  transform-origin: top center;
  transform-style: preserve-3d;
  animation: open 3.6s ease-in-out infinite alternate;
}

.lid::before,
.lid::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: calc(10 * var(--u));
  backface-visibility: hidden;
}

/* outer shell */
.lid::before {
  background: linear-gradient(#b9bfd2, #8e95ad);
  transform: translateZ(calc(1 * var(--u)));
}

/* screen — the other side */
.lid::after {
  border: calc(7 * var(--u)) solid #14161f;
  background: linear-gradient(135deg, #8b6cff, #2ee6d6);
  box-shadow: 0 0 calc(30 * var(--u)) #8b6cff;
  transform: rotateY(180deg);
}

/* open first: a paused card holds the pose at 0%, and an open laptop is the one that reads as a
   laptop and as the trick this model is about. Shut, it is a slab low in the frame. */
@keyframes open {
  0%, 12%   { transform: rotateX(104deg); }
  85%, 100% { transform: rotateX(0deg); }
}
Standalone snippet · plain CSS, no build step, no dependencies.