Open demo

CSS code

HTML
<div class="duo mc">
  <div><span class="lbl">Medal turned by an @property angle</span>
  <div class="mc-medal">
    <div class="mc-scene">
      <div class="mc-coin">
        <i class="mc-edge" style="--z:-4px" aria-hidden="true"></i><i class="mc-edge" style="--z:-2px" aria-hidden="true"></i><i class="mc-edge" style="--z:0px" aria-hidden="true"></i><i class="mc-edge" style="--z:2px" aria-hidden="true"></i><i class="mc-edge" style="--z:4px" aria-hidden="true"></i>
        <div class="mc-face mc-front"><span class="mc-k">Harbour Half</span><span class="mc-big">2026</span><span class="mc-sm">Finisher</span></div>
        <div class="mc-face mc-backface"><span class="mc-k">21.1 km</span><span class="mc-big">1:42</span><span class="mc-sm">Bib 4471 · 18 s</span></div>
      </div>
    </div>
    <div class="mc-controls">
      <fieldset class="mc-seg"><legend class="sr-only">Medal side</legend>
        <label><input type="radio" name="mc-side" checked><span>Front</span></label>
        <label><input class="mc-show-back" type="radio" name="mc-side"><span>Back</span></label>
      </fieldset>
      <label class="mc-switch"><input class="mc-spin" type="checkbox" role="switch"><span aria-hidden="true"></span>Spin</label>
    </div>
  </div></div>

  <div><span class="lbl">Diorama that follows the pointer</span>
  <div class="mc-dio" role="img" aria-label="A paper diorama of three violet hills under a pale moon">
    <div class="mc-room">
      <div class="mc-wall-back"></div><div class="mc-floor"></div><div class="mc-ceil"></div><div class="mc-wall-l"></div><div class="mc-wall-r"></div>
      <div class="mc-hill mc-hill-1"></div><div class="mc-hill mc-hill-2"></div><div class="mc-hill mc-hill-3"></div>
    </div>
    <div class="mc-cells"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
  </div>
  <p class="mc-cap"><strong>Into the Hills</strong>, a print series by Studio Lumen. Move the pointer over the box to look around inside it.</p></div>
</div>

<div class="mc"><span class="lbl">Weather card with depth from its container</span>
<div class="slots">
  <div class="mc-slot slot-narrow">
    <article class="mc-wx" aria-labelledby="mc-city-1">
      <div class="mc-art" aria-hidden="true"><span class="mc-sun"></span><span class="mc-cloud"></span></div>
      <div class="mc-wx-main"><p class="mc-city" id="mc-city-1">Reykjavík</p><p class="mc-temp">6°</p><p class="mc-cond">Bright spells, wind 24 km/h</p></div>
      <ul class="mc-days"><li>Thu<b>7°</b></li><li>Fri<b>4°</b></li><li>Sat<b>5°</b></li></ul>
    </article>
  </div>
  <div class="mc-slot slot-wide">
    <article class="mc-wx" aria-labelledby="mc-city-2">
      <div class="mc-art" aria-hidden="true"><span class="mc-sun"></span><span class="mc-cloud"></span></div>
      <div class="mc-wx-main"><p class="mc-city" id="mc-city-2">Reykjavík</p><p class="mc-temp">6°</p><p class="mc-cond">Bright spells, wind 24 km/h</p></div>
      <ul class="mc-days"><li>Thu<b>7°</b></li><li>Fri<b>4°</b></li><li>Sat<b>5°</b></li></ul>
    </article>
  </div>
</div></div>
CSS
/* Modern CSS 3D, no JavaScript.
   A. @property registers --mc-ry as an <angle>, so the medal can transition and animate one rotation on its own; :has() reads
      the Front / Back radios and the Spin switch, which the visitor has to turn on, and which is hidden under reduced motion.
   B. A diorama whose perspective-origin follows the pointer: a 5 by 5 grid of hover cells sets --px / --py through :has().
   C. A weather card that takes its depth from its container: translateZ in cqi units, and a wider layout from @container. */
@property --mc-ry{syntax:"<angle>";inherits:true;initial-value:0deg}
.mc{--mc-ink:#0f172a;--mc-muted:#475569;--mc-focus:#1d4ed8;color:var(--mc-ink)}
.mc :where(h3,p,ul,fieldset,legend){margin:0;padding:0;border:0;list-style:none}
.mc .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}

/* A. medal */
.mc-medal{position:relative;display:grid;justify-items:center;gap:18px;padding:24px;border-radius:22px;background:#fff;box-shadow:0 22px 36px -28px rgba(15,23,42,.5)}
.mc-scene{display:grid;place-items:center;height:230px;perspective:900px}
.mc-coin{position:relative;width:180px;height:180px;transform-style:preserve-3d;transform:rotateX(-10deg) rotateY(var(--mc-ry));transition:--mc-ry 1s cubic-bezier(.3,.8,.25,1)}
.mc-coin > *{position:absolute;inset:0;border-radius:50%}
.mc-edge{background:#a16207;transform:translateZ(var(--z))}
.mc-face{display:grid;place-content:center;justify-items:center;gap:2px;text-align:center;-webkit-backface-visibility:hidden;backface-visibility:hidden;
  background:radial-gradient(circle at 35% 30%,#fef3c7,#fbbf24 45%,#d97706);background-color:#fbbf24;color:#422006;box-shadow:inset 0 0 0 7px #f59e0b,inset 0 0 0 9px #b45309}
.mc-front{transform:translateZ(5px)}
.mc-backface{transform:rotateY(180deg) translateZ(5px)}
.mc-k{font-size:.66rem;font-weight:800;letter-spacing:.2em;text-transform:uppercase}
.mc-big{font-size:2.4rem;font-weight:900;line-height:1;letter-spacing:-.02em}
.mc-sm{font-size:.82rem;font-weight:700}
.mc-medal:has(.mc-show-back:checked) .mc-coin{--mc-ry:180deg}
.mc-medal:has(.mc-spin:checked) .mc-coin{animation:mc-turn 7s linear infinite}
@keyframes mc-turn{from{--mc-ry:0deg}to{--mc-ry:360deg}}
.mc-controls{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:12px 18px}
.mc-seg{display:inline-flex;padding:4px;border-radius:999px;background:#e2e8f0}
.mc-seg label{position:relative;cursor:pointer}
.mc-seg input,.mc-switch input{position:absolute;opacity:0;width:1px;height:1px}
.mc-seg span{display:block;padding:7px 16px;border-radius:999px;font-size:.86rem;font-weight:700;color:var(--mc-muted)}
.mc-seg input:checked + span{background:#fff;color:var(--mc-ink);box-shadow:0 1px 3px rgba(15,23,42,.2)}
.mc-seg input:focus-visible + span,.mc-switch input:focus-visible + span{outline:3px solid var(--mc-focus);outline-offset:2px}
.mc-switch{position:relative;display:inline-flex;align-items:center;gap:10px;font-size:.88rem;font-weight:700;cursor:pointer}
.mc-switch span{position:relative;width:38px;height:22px;border-radius:999px;background:#64748b;transition:background-color .2s}
.mc-switch span::after{content:"";position:absolute;top:3px;left:3px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s}
.mc-switch input:checked + span{background:#15803d}
.mc-switch input:checked + span::after{transform:translateX(16px)}

/* B. diorama: perspective-origin follows the pointer. The hills are SVG backgrounds (clip-path would break the 3D sorting). */
.mc-dio{--px:50%;--py:45%;position:relative;width:100%;aspect-ratio:4/3;border-radius:18px;overflow:hidden;background:#1e1b4b;
  perspective:520px;perspective-origin:var(--px) var(--py);transition:perspective-origin .5s ease-out}
.mc-room{position:absolute;inset:0;transform-style:preserve-3d}
.mc-room > *{position:absolute}
.mc-wall-back{inset:0;transform:translateZ(-220px);background:radial-gradient(circle at 70% 26%,#fef9c3 0 20px,rgba(254,249,195,.25) 21px 32px,transparent 33px),linear-gradient(#1e1b4b,#5b21b6 55%,#f0abfc)}
.mc-floor{left:0;right:0;bottom:0;height:220px;transform-origin:bottom;transform:rotateX(90deg);background:linear-gradient(#2e1065,#170b3b)}
.mc-ceil{left:0;right:0;top:0;height:220px;transform-origin:top;transform:rotateX(-90deg);background:#1e1b4b}
.mc-wall-l{left:0;top:0;bottom:0;width:220px;transform-origin:left;transform:rotateY(90deg);background:linear-gradient(90deg,#1e1b4b,#312e81)}
.mc-wall-r{right:0;top:0;bottom:0;width:220px;transform-origin:right;transform:rotateY(-90deg);background:linear-gradient(-90deg,#1e1b4b,#312e81)}
.mc-hill{left:0;right:0;bottom:0}
.mc-hill-1{height:72%;transform:translateZ(-170px);background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cpolygon points='0,70 14,38 27,60 44,22 60,55 74,30 90,58 100,44 100,100 0,100' fill='%23a78bfa'/%3E%3C/svg%3E") 0 0/100% 100% no-repeat}
.mc-hill-2{height:58%;transform:translateZ(-110px);background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cpolygon points='0,62 18,52 32,74 50,48 66,70 82,50 100,66 100,100 0,100' fill='%237c3aed'/%3E%3C/svg%3E") 0 0/100% 100% no-repeat}
.mc-hill-3{height:44%;transform:translateZ(-50px);background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cpolygon points='0,80 10,70 12,52 14,70 30,78 52,74 70,82 84,66 86,48 88,66 100,74 100,100 0,100' fill='%233b0764'/%3E%3C/svg%3E") 0 0/100% 100% no-repeat}
.mc-cells{position:absolute;inset:0;display:grid;grid-template:repeat(5,1fr)/repeat(5,1fr)}
.mc-dio:has(.mc-cells > :nth-child(5n+1):hover){--px:5%}
.mc-dio:has(.mc-cells > :nth-child(5n+2):hover){--px:28%}
.mc-dio:has(.mc-cells > :nth-child(5n+4):hover){--px:72%}
.mc-dio:has(.mc-cells > :nth-child(5n):hover){--px:95%}
.mc-dio:has(.mc-cells > :nth-child(-n+5):hover){--py:5%}
.mc-dio:has(.mc-cells > :nth-child(n+6):nth-child(-n+10):hover){--py:25%}
.mc-dio:has(.mc-cells > :nth-child(n+16):nth-child(-n+20):hover){--py:70%}
.mc-dio:has(.mc-cells > :nth-child(n+21):hover){--py:95%}
.mc-cap{margin-top:14px;font-size:.9rem;line-height:1.5;color:var(--mc-muted)}
.mc-cap strong{color:var(--mc-ink)}

/* C. weather card: depth in container units */
.mc-slot{container-type:inline-size}
.mc-wx{position:relative;display:grid;gap:14px;padding:22px;border-radius:22px;background:linear-gradient(155deg,#0369a1,#1e3a8a);color:#fff;
  transform:perspective(900px) rotateX(7deg) rotateY(-11deg);transform-style:preserve-3d;box-shadow:0 30px 40px -30px rgba(30,58,138,.8)}
.mc-art{position:relative;height:96px;transform-style:preserve-3d}
.mc-sun{position:absolute;left:12%;top:6px;width:64px;height:64px;border-radius:50%;background:radial-gradient(circle at 40% 35%,#fef08a,#facc15 60%,#eab308);transform:translateZ(3cqi);box-shadow:0 0 30px rgba(250,204,21,.6)}
.mc-cloud{position:absolute;left:28%;top:34px;width:110px;height:40px;border-radius:999px;background:#f8fafc;transform:translateZ(8cqi);box-shadow:0 12px 18px -8px rgba(15,23,42,.45)}
.mc-cloud::before,.mc-cloud::after{content:"";position:absolute;border-radius:50%;background:inherit}
.mc-cloud::before{left:16px;top:-22px;width:46px;height:46px}
.mc-cloud::after{left:48px;top:-32px;width:56px;height:56px}
.mc-wx-main{transform:translateZ(5cqi)}
.mc-city{font-size:.8rem;font-weight:800;letter-spacing:.16em;text-transform:uppercase;color:#bae6fd}
.mc-temp{font-size:3.2rem;font-weight:900;line-height:1;letter-spacing:-.03em}
.mc-cond{margin-top:2px;font-size:.92rem;color:#e0f2fe}
.mc-days{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;transform:translateZ(2cqi)}
.mc-days li{display:grid;gap:2px;padding:8px 10px;border-radius:12px;background:rgba(15,23,42,.35);font-size:.8rem;color:#e0f2fe}
.mc-days b{font-size:1rem;color:#fff}
@container (min-width:480px){
  .mc-wx{grid-template-columns:190px 1fr;align-items:center;column-gap:24px;padding:28px;transform:perspective(1100px) rotateX(6deg) rotateY(-15deg)}
  .mc-art{grid-row:span 2;height:150px}
  .mc-sun{width:84px;height:84px}
  .mc-cloud{top:70px;left:22%;width:130px}
  .mc-temp{font-size:4rem}
}

@media (prefers-reduced-motion:reduce){
  .mc-coin,.mc-dio,.mc-switch span,.mc-switch span::after{transition:none}
  .mc-medal:has(.mc-spin:checked) .mc-coin{animation:none}
  .mc-switch{display:none}
}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div class="row row-cols-1 row-cols-md-2 duo mc">
  <div class="col"><span class="lbl">Medal turned by an @property angle</span>
  <div class="card mc-medal"><div class="card-body">
    <div class="mc-scene">
      <div class="mc-coin">
        <i class="mc-edge" style="--z:-4px" aria-hidden="true"></i><i class="mc-edge" style="--z:-2px" aria-hidden="true"></i><i class="mc-edge" style="--z:0px" aria-hidden="true"></i><i class="mc-edge" style="--z:2px" aria-hidden="true"></i><i class="mc-edge" style="--z:4px" aria-hidden="true"></i>
        <div class="mc-face mc-front"><span class="mc-k">Harbour Half</span><span class="mc-big">2026</span><span class="mc-sm">Finisher</span></div>
        <div class="mc-face mc-backface"><span class="mc-k">21.1 km</span><span class="mc-big">1:42</span><span class="mc-sm">Bib 4471 · 18 s</span></div>
      </div>
    </div>
    <div class="mc-controls">
      <fieldset class="btn-group mc-seg"><legend class="visually-hidden">Medal side</legend>
        <input class="btn-check" type="radio" name="mc-side" id="mc-front" autocomplete="off" checked><label class="btn" for="mc-front">Front</label>
        <input class="btn-check mc-show-back" type="radio" name="mc-side" id="mc-back" autocomplete="off"><label class="btn" for="mc-back">Back</label>
      </fieldset>
      <div class="form-check form-switch mc-switch"><input class="form-check-input mc-spin" type="checkbox" role="switch" id="mc-spin"><label class="form-check-label" for="mc-spin">Spin</label></div>
    </div>
  </div></div></div>

  <div class="col"><span class="lbl">Diorama that follows the pointer</span>
  <div class="mc-dio" role="img" aria-label="A paper diorama of three violet hills under a pale moon">
    <div class="mc-room">
      <div class="mc-wall-back"></div><div class="mc-floor"></div><div class="mc-ceil"></div><div class="mc-wall-l"></div><div class="mc-wall-r"></div>
      <div class="mc-hill mc-hill-1"></div><div class="mc-hill mc-hill-2"></div><div class="mc-hill mc-hill-3"></div>
    </div>
    <div class="mc-cells"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
  </div>
  <p class="mc-cap mb-0"><strong>Into the Hills</strong>, a print series by Studio Lumen. Move the pointer over the box to look around inside it.</p></div>
</div>

<div class="mc"><span class="lbl">Weather card with depth from its container</span>
<div class="slots">
  <div class="mc-slot slot-narrow">
    <article class="card mc-wx" aria-labelledby="mc-city-1">
      <div class="mc-art" aria-hidden="true"><span class="mc-sun"></span><span class="mc-cloud"></span></div>
      <div class="mc-wx-main"><p class="mc-city" id="mc-city-1">Reykjavík</p><p class="mc-temp">6°</p><p class="mc-cond">Bright spells, wind 24 km/h</p></div>
      <ul class="mc-days list-unstyled mb-0"><li>Thu<b>7°</b></li><li>Fri<b>4°</b></li><li>Sat<b>5°</b></li></ul>
    </article>
  </div>
  <div class="mc-slot slot-wide">
    <article class="card mc-wx" aria-labelledby="mc-city-2">
      <div class="mc-art" aria-hidden="true"><span class="mc-sun"></span><span class="mc-cloud"></span></div>
      <div class="mc-wx-main"><p class="mc-city" id="mc-city-2">Reykjavík</p><p class="mc-temp">6°</p><p class="mc-cond">Bright spells, wind 24 km/h</p></div>
      <ul class="mc-days list-unstyled mb-0"><li>Thu<b>7°</b></li><li>Fri<b>4°</b></li><li>Sat<b>5°</b></li></ul>
    </article>
  </div>
</div></div>
CSS
/* Modern CSS 3D on Bootstrap .card, .btn-group with .btn-check and .form-switch, no JavaScript.
   A. @property registers --mc-ry as an <angle>, so the medal can transition and animate one rotation on its own; :has() reads
      the Front / Back radios and the Spin switch, which the visitor has to turn on, and which is hidden under reduced motion.
   B. A diorama whose perspective-origin follows the pointer: a 5 by 5 grid of hover cells sets --px / --py through :has().
   C. A weather card that takes its depth from its container: translateZ in cqi units, and a wider layout from @container. */
@property --mc-ry{syntax:"<angle>";inherits:true;initial-value:0deg}
.mc{--mc-ink:#0f172a;--mc-muted:#475569;--mc-focus:#1d4ed8;color:var(--mc-ink)}
.mc :where(h3,p,ul,fieldset,legend){margin:0;padding:0;border:0;list-style:none}

/* A. medal */
.mc-medal{--bs-card-border-width:0;--bs-card-border-radius:22px;box-shadow:0 22px 36px -28px rgba(15,23,42,.5)}
.mc-medal .card-body{display:grid;justify-items:center;gap:18px;padding:24px}
.mc-scene{display:grid;place-items:center;height:230px;perspective:900px}
.mc-coin{position:relative;width:180px;height:180px;transform-style:preserve-3d;transform:rotateX(-10deg) rotateY(var(--mc-ry));transition:--mc-ry 1s cubic-bezier(.3,.8,.25,1)}
.mc-coin > *{position:absolute;inset:0;border-radius:50%}
.mc-edge{background:#a16207;transform:translateZ(var(--z))}
.mc-face{display:grid;place-content:center;justify-items:center;gap:2px;text-align:center;-webkit-backface-visibility:hidden;backface-visibility:hidden;
  background:radial-gradient(circle at 35% 30%,#fef3c7,#fbbf24 45%,#d97706);background-color:#fbbf24;color:#422006;box-shadow:inset 0 0 0 7px #f59e0b,inset 0 0 0 9px #b45309}
.mc-front{transform:translateZ(5px)}
.mc-backface{transform:rotateY(180deg) translateZ(5px)}
.mc-k{font-size:.66rem;font-weight:800;letter-spacing:.2em;text-transform:uppercase}
.mc-big{font-size:2.4rem;font-weight:900;line-height:1;letter-spacing:-.02em}
.mc-sm{font-size:.82rem;font-weight:700}
.mc-medal:has(.mc-show-back:checked) .mc-coin{--mc-ry:180deg}
.mc-medal:has(.mc-spin:checked) .mc-coin{animation:mc-turn 7s linear infinite}
@keyframes mc-turn{from{--mc-ry:0deg}to{--mc-ry:360deg}}
.mc-controls{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:12px 18px}
.mc-seg{gap:0;padding:4px;border-radius:999px;background:#e2e8f0}
.mc-seg .btn{--bs-btn-padding-x:16px;--bs-btn-padding-y:7px;--bs-btn-font-size:.86rem;--bs-btn-font-weight:700;--bs-btn-line-height:normal;--bs-btn-color:var(--mc-muted);--bs-btn-border-width:0;--bs-btn-hover-color:var(--mc-ink);
  --bs-btn-active-bg:#fff;--bs-btn-active-color:var(--mc-ink);border-radius:999px!important}
.mc-seg .btn-check:checked + .btn{box-shadow:0 1px 3px rgba(15,23,42,.2)}
.mc-seg .btn-check:focus-visible + .btn{outline:3px solid var(--mc-focus);outline-offset:2px;box-shadow:none}
.mc-switch{display:inline-flex;align-items:center;gap:10px;margin:0;font-size:.88rem;font-weight:700}
.mc-switch .form-check-input{width:38px;height:22px;margin:0 0 0 -2.5em;background-color:#64748b;border-color:#64748b;
  --bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}
.mc-switch .form-check-input:checked{background-color:#15803d;border-color:#15803d}
.mc-switch .form-check-input:focus-visible{outline:3px solid var(--mc-focus);outline-offset:2px;box-shadow:none}
.mc-switch .form-check-label{cursor:pointer}

/* B. diorama: perspective-origin follows the pointer. The hills are SVG backgrounds (clip-path would break the 3D sorting). */
.mc-dio{--px:50%;--py:45%;position:relative;width:100%;aspect-ratio:4/3;border-radius:18px;overflow:hidden;background:#1e1b4b;
  perspective:520px;perspective-origin:var(--px) var(--py);transition:perspective-origin .5s ease-out}
.mc-room{position:absolute;inset:0;transform-style:preserve-3d}
.mc-room > *{position:absolute}
.mc-wall-back{inset:0;transform:translateZ(-220px);background:radial-gradient(circle at 70% 26%,#fef9c3 0 20px,rgba(254,249,195,.25) 21px 32px,transparent 33px),linear-gradient(#1e1b4b,#5b21b6 55%,#f0abfc)}
.mc-floor{left:0;right:0;bottom:0;height:220px;transform-origin:bottom;transform:rotateX(90deg);background:linear-gradient(#2e1065,#170b3b)}
.mc-ceil{left:0;right:0;top:0;height:220px;transform-origin:top;transform:rotateX(-90deg);background:#1e1b4b}
.mc-wall-l{left:0;top:0;bottom:0;width:220px;transform-origin:left;transform:rotateY(90deg);background:linear-gradient(90deg,#1e1b4b,#312e81)}
.mc-wall-r{right:0;top:0;bottom:0;width:220px;transform-origin:right;transform:rotateY(-90deg);background:linear-gradient(-90deg,#1e1b4b,#312e81)}
.mc-hill{left:0;right:0;bottom:0}
.mc-hill-1{height:72%;transform:translateZ(-170px);background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cpolygon points='0,70 14,38 27,60 44,22 60,55 74,30 90,58 100,44 100,100 0,100' fill='%23a78bfa'/%3E%3C/svg%3E") 0 0/100% 100% no-repeat}
.mc-hill-2{height:58%;transform:translateZ(-110px);background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cpolygon points='0,62 18,52 32,74 50,48 66,70 82,50 100,66 100,100 0,100' fill='%237c3aed'/%3E%3C/svg%3E") 0 0/100% 100% no-repeat}
.mc-hill-3{height:44%;transform:translateZ(-50px);background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cpolygon points='0,80 10,70 12,52 14,70 30,78 52,74 70,82 84,66 86,48 88,66 100,74 100,100 0,100' fill='%233b0764'/%3E%3C/svg%3E") 0 0/100% 100% no-repeat}
.mc-cells{position:absolute;inset:0;display:grid;grid-template:repeat(5,1fr)/repeat(5,1fr)}
.mc-dio:has(.mc-cells > :nth-child(5n+1):hover){--px:5%}
.mc-dio:has(.mc-cells > :nth-child(5n+2):hover){--px:28%}
.mc-dio:has(.mc-cells > :nth-child(5n+4):hover){--px:72%}
.mc-dio:has(.mc-cells > :nth-child(5n):hover){--px:95%}
.mc-dio:has(.mc-cells > :nth-child(-n+5):hover){--py:5%}
.mc-dio:has(.mc-cells > :nth-child(n+6):nth-child(-n+10):hover){--py:25%}
.mc-dio:has(.mc-cells > :nth-child(n+16):nth-child(-n+20):hover){--py:70%}
.mc-dio:has(.mc-cells > :nth-child(n+21):hover){--py:95%}
.mc-cap{margin-top:14px;font-size:.9rem;line-height:1.5;color:var(--mc-muted)}
.mc-cap strong{color:var(--mc-ink)}

/* C. weather card: depth in container units */
.mc-slot{container-type:inline-size}
.mc-wx{--bs-card-border-width:0;--bs-card-border-radius:22px;--bs-card-color:#fff;color:#fff;display:grid;gap:14px;padding:22px;background:linear-gradient(155deg,#0369a1,#1e3a8a);
  transform:perspective(900px) rotateX(7deg) rotateY(-11deg);transform-style:preserve-3d;box-shadow:0 30px 40px -30px rgba(30,58,138,.8)}
.mc-art{position:relative;height:96px;transform-style:preserve-3d}
.mc-sun{position:absolute;left:12%;top:6px;width:64px;height:64px;border-radius:50%;background:radial-gradient(circle at 40% 35%,#fef08a,#facc15 60%,#eab308);transform:translateZ(3cqi);box-shadow:0 0 30px rgba(250,204,21,.6)}
.mc-cloud{position:absolute;left:28%;top:34px;width:110px;height:40px;border-radius:999px;background:#f8fafc;transform:translateZ(8cqi);box-shadow:0 12px 18px -8px rgba(15,23,42,.45)}
.mc-cloud::before,.mc-cloud::after{content:"";position:absolute;border-radius:50%;background:inherit}
.mc-cloud::before{left:16px;top:-22px;width:46px;height:46px}
.mc-cloud::after{left:48px;top:-32px;width:56px;height:56px}
.mc-wx-main{transform:translateZ(5cqi)}
.mc-city{font-size:.8rem;font-weight:800;letter-spacing:.16em;text-transform:uppercase;color:#bae6fd}
.mc-temp{font-size:3.2rem;font-weight:900;line-height:1;letter-spacing:-.03em}
.mc-cond{margin-top:2px;font-size:.92rem;color:#e0f2fe}
.mc-days{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;transform:translateZ(2cqi)}
.mc-days li{display:grid;gap:2px;padding:8px 10px;border-radius:12px;background:rgba(15,23,42,.35);font-size:.8rem;color:#e0f2fe}
.mc-days b{font-size:1rem;color:#fff}
@container (min-width:480px){
  .mc-wx{grid-template-columns:190px 1fr;align-items:center;column-gap:24px;padding:28px;transform:perspective(1100px) rotateX(6deg) rotateY(-15deg)}
  .mc-art{grid-row:span 2;height:150px}
  .mc-sun{width:84px;height:84px}
  .mc-cloud{top:70px;left:22%;width:130px}
  .mc-temp{font-size:4rem}
}

@media (prefers-reduced-motion:reduce){
  .mc-coin,.mc-dio,.mc-switch .form-check-input{transition:none}
  .mc-medal:has(.mc-spin:checked) .mc-coin{animation:none}
  .mc-switch{display:none}
}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div class="grid grid-cols-[repeat(auto-fit,minmax(min(100%,340px),1fr))] gap-12">
  <div><span class="mb-[18px] block text-[.72rem] tracking-[.12em] text-[#475569] uppercase">Medal turned by an @property angle</span>
  <div class="group relative grid justify-items-center gap-[18px] rounded-[22px] bg-white p-6 shadow-[0_22px_36px_-28px_rgba(15,23,42,.5)]">
    <div class="grid h-[230px] place-items-center perspective-[900px]">
      <div class="relative size-[180px] transition-[--mc-ry] duration-1000 ease-[cubic-bezier(.3,.8,.25,1)] transform-3d [transform:rotateX(-10deg)_rotateY(var(--mc-ry))] group-has-[.mc-show-back:checked]:[--mc-ry:180deg] motion-safe:group-has-[.mc-spin:checked]:animate-[mc-turn_7s_linear_infinite] motion-reduce:transition-none">
        <i class="absolute inset-0 rounded-full bg-[#a16207] [transform:translateZ(-4px)]" aria-hidden="true"></i><i class="absolute inset-0 rounded-full bg-[#a16207] [transform:translateZ(-2px)]" aria-hidden="true"></i><i class="absolute inset-0 rounded-full bg-[#a16207] [transform:translateZ(0px)]" aria-hidden="true"></i><i class="absolute inset-0 rounded-full bg-[#a16207] [transform:translateZ(2px)]" aria-hidden="true"></i><i class="absolute inset-0 rounded-full bg-[#a16207] [transform:translateZ(4px)]" aria-hidden="true"></i>
        <div class="absolute inset-0 grid place-content-center justify-items-center gap-0.5 rounded-full bg-[#fbbf24] bg-[radial-gradient(circle_at_35%_30%,#fef3c7,#fbbf24_45%,#d97706)] text-center text-[#422006] shadow-[inset_0_0_0_7px_#f59e0b,inset_0_0_0_9px_#b45309] backface-hidden [transform:translateZ(5px)]"><span class="text-[.66rem] font-extrabold tracking-[.2em] uppercase">Harbour Half</span><span class="text-[2.4rem] leading-none font-black tracking-[-.02em]">2026</span><span class="text-[.82rem] font-bold">Finisher</span></div>
        <div class="absolute inset-0 grid place-content-center justify-items-center gap-0.5 rounded-full bg-[#fbbf24] bg-[radial-gradient(circle_at_35%_30%,#fef3c7,#fbbf24_45%,#d97706)] text-center text-[#422006] shadow-[inset_0_0_0_7px_#f59e0b,inset_0_0_0_9px_#b45309] backface-hidden [transform:rotateY(180deg)_translateZ(5px)]"><span class="text-[.66rem] font-extrabold tracking-[.2em] uppercase">21.1 km</span><span class="text-[2.4rem] leading-none font-black tracking-[-.02em]">1:42</span><span class="text-[.82rem] font-bold">Bib 4471 · 18 s</span></div>
      </div>
    </div>
    <div class="flex flex-wrap items-center justify-center gap-x-[18px] gap-y-3">
      <fieldset class="inline-flex rounded-full bg-[#e2e8f0] p-1"><legend class="sr-only">Medal side</legend>
        <label class="relative cursor-pointer"><input class="peer absolute size-px opacity-0" type="radio" name="mcw-side" checked><span class="block rounded-full px-4 py-[7px] text-[.86rem] font-bold text-[#475569] peer-checked:bg-white peer-checked:text-[#0f172a] peer-checked:shadow-[0_1px_3px_rgba(15,23,42,.2)] peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#1d4ed8]">Front</span></label>
        <label class="relative cursor-pointer"><input class="mc-show-back peer absolute size-px opacity-0" type="radio" name="mcw-side"><span class="block rounded-full px-4 py-[7px] text-[.86rem] font-bold text-[#475569] peer-checked:bg-white peer-checked:text-[#0f172a] peer-checked:shadow-[0_1px_3px_rgba(15,23,42,.2)] peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#1d4ed8]">Back</span></label>
      </fieldset>
      <label class="relative inline-flex cursor-pointer items-center gap-2.5 text-[.88rem] font-bold motion-reduce:hidden"><input class="mc-spin peer absolute size-px opacity-0" type="checkbox" role="switch"><span class="relative h-[22px] w-[38px] rounded-full bg-[#64748b] transition-colors duration-200 peer-checked:bg-[#15803d] peer-focus-visible:outline-3 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[#1d4ed8] after:absolute after:top-[3px] after:left-[3px] after:size-4 after:rounded-full after:bg-white after:transition-transform after:duration-200 after:content-[''] peer-checked:after:translate-x-4" aria-hidden="true"></span>Spin</label>
    </div>
  </div></div>

  <div><span class="mb-[18px] block text-[.72rem] tracking-[.12em] text-[#475569] uppercase">Diorama that follows the pointer</span>
  <div class="relative aspect-[4/3] w-full overflow-hidden rounded-[18px] bg-[#1e1b4b] perspective-[520px] [--px:50%] [--py:45%] [perspective-origin:var(--px)_var(--py)] transition-[perspective-origin] duration-500 ease-out motion-reduce:transition-none [&:has(.mc-cells>:nth-child(5n+1):hover)]:[--px:5%] [&:has(.mc-cells>:nth-child(5n+2):hover)]:[--px:28%] [&:has(.mc-cells>:nth-child(5n+4):hover)]:[--px:72%] [&:has(.mc-cells>:nth-child(5n):hover)]:[--px:95%] [&:has(.mc-cells>:nth-child(-n+5):hover)]:[--py:5%] [&:has(.mc-cells>:nth-child(n+6):nth-child(-n+10):hover)]:[--py:25%] [&:has(.mc-cells>:nth-child(n+16):nth-child(-n+20):hover)]:[--py:70%] [&:has(.mc-cells>:nth-child(n+21):hover)]:[--py:95%]" role="img" aria-label="A paper diorama of three violet hills under a pale moon">
    <div class="absolute inset-0 transform-3d">
      <div class="absolute inset-0 bg-[radial-gradient(circle_at_70%_26%,#fef9c3_0_20px,rgba(254,249,195,.25)_21px_32px,transparent_33px),linear-gradient(#1e1b4b,#5b21b6_55%,#f0abfc)] [transform:translateZ(-220px)]"></div>
      <div class="absolute right-0 bottom-0 left-0 h-[220px] origin-bottom bg-[linear-gradient(#2e1065,#170b3b)] [transform:rotateX(90deg)]"></div>
      <div class="absolute top-0 right-0 left-0 h-[220px] origin-top bg-[#1e1b4b] [transform:rotateX(-90deg)]"></div>
      <div class="absolute top-0 bottom-0 left-0 w-[220px] origin-left bg-[linear-gradient(90deg,#1e1b4b,#312e81)] [transform:rotateY(90deg)]"></div>
      <div class="absolute top-0 right-0 bottom-0 w-[220px] origin-right bg-[linear-gradient(-90deg,#1e1b4b,#312e81)] [transform:rotateY(-90deg)]"></div>
      <div class="mc-hill-1 absolute right-0 bottom-0 left-0 h-[72%] [transform:translateZ(-170px)]"></div>
      <div class="mc-hill-2 absolute right-0 bottom-0 left-0 h-[58%] [transform:translateZ(-110px)]"></div>
      <div class="mc-hill-3 absolute right-0 bottom-0 left-0 h-[44%] [transform:translateZ(-50px)]"></div>
    </div>
    <div class="mc-cells absolute inset-0 grid grid-cols-5 grid-rows-5"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
  </div>
  <p class="mt-3.5 text-[.9rem] leading-normal text-[#475569]"><strong class="text-[#0f172a]">Into the Hills</strong>, a print series by Studio Lumen. Move the pointer over the box to look around inside it.</p></div>
</div>

<div><span class="mb-[18px] block text-[.72rem] tracking-[.12em] text-[#475569] uppercase">Weather card with depth from its container</span>
<div class="grid grid-cols-[repeat(auto-fit,minmax(min(100%,280px),1fr))] gap-7">
  <div class="@container max-w-[300px]">
    <article class="relative grid gap-3.5 rounded-[22px] bg-[linear-gradient(155deg,#0369a1,#1e3a8a)] p-[22px] text-white shadow-[0_30px_40px_-30px_rgba(30,58,138,.8)] transform-3d [transform:perspective(900px)_rotateX(7deg)_rotateY(-11deg)] @min-[480px]:grid-cols-[190px_1fr] @min-[480px]:items-center @min-[480px]:gap-x-6 @min-[480px]:p-7 @min-[480px]:[transform:perspective(1100px)_rotateX(6deg)_rotateY(-15deg)]" aria-labelledby="mcw-city-1">
      <div class="relative h-24 transform-3d @min-[480px]:row-span-2 @min-[480px]:h-[150px]" aria-hidden="true"><span class="absolute top-1.5 left-[12%] size-16 rounded-full bg-[radial-gradient(circle_at_40%_35%,#fef08a,#facc15_60%,#eab308)] shadow-[0_0_30px_rgba(250,204,21,.6)] [transform:translateZ(3cqi)] @min-[480px]:size-[84px]"></span><span class="absolute top-[34px] left-[28%] h-10 w-[110px] rounded-full bg-[#f8fafc] shadow-[0_12px_18px_-8px_rgba(15,23,42,.45)] [transform:translateZ(8cqi)] before:absolute before:top-[-22px] before:left-4 before:size-[46px] before:rounded-full before:bg-inherit before:content-[''] after:absolute after:top-[-32px] after:left-12 after:size-14 after:rounded-full after:bg-inherit after:content-[''] @min-[480px]:top-[70px] @min-[480px]:left-[22%] @min-[480px]:w-[130px]"></span></div>
      <div class="[transform:translateZ(5cqi)]"><p class="text-[.8rem] font-extrabold tracking-[.16em] text-[#bae6fd] uppercase" id="mcw-city-1">Reykjavík</p><p class="text-[3.2rem] leading-none font-black tracking-[-.03em] @min-[480px]:text-[4rem]">6°</p><p class="mt-0.5 text-[.92rem] text-[#e0f2fe]">Bright spells, wind 24 km/h</p></div>
      <ul class="grid grid-cols-3 gap-2 [transform:translateZ(2cqi)]"><li class="grid gap-0.5 rounded-xl bg-[rgba(15,23,42,.35)] px-2.5 py-2 text-[.8rem] text-[#e0f2fe]">Thu<b class="text-base text-white">7°</b></li><li class="grid gap-0.5 rounded-xl bg-[rgba(15,23,42,.35)] px-2.5 py-2 text-[.8rem] text-[#e0f2fe]">Fri<b class="text-base text-white">4°</b></li><li class="grid gap-0.5 rounded-xl bg-[rgba(15,23,42,.35)] px-2.5 py-2 text-[.8rem] text-[#e0f2fe]">Sat<b class="text-base text-white">5°</b></li></ul>
    </article>
  </div>
  <div class="@container col-span-2 max-w-[620px] max-[700px]:col-auto">
    <article class="relative grid gap-3.5 rounded-[22px] bg-[linear-gradient(155deg,#0369a1,#1e3a8a)] p-[22px] text-white shadow-[0_30px_40px_-30px_rgba(30,58,138,.8)] transform-3d [transform:perspective(900px)_rotateX(7deg)_rotateY(-11deg)] @min-[480px]:grid-cols-[190px_1fr] @min-[480px]:items-center @min-[480px]:gap-x-6 @min-[480px]:p-7 @min-[480px]:[transform:perspective(1100px)_rotateX(6deg)_rotateY(-15deg)]" aria-labelledby="mcw-city-2">
      <div class="relative h-24 transform-3d @min-[480px]:row-span-2 @min-[480px]:h-[150px]" aria-hidden="true"><span class="absolute top-1.5 left-[12%] size-16 rounded-full bg-[radial-gradient(circle_at_40%_35%,#fef08a,#facc15_60%,#eab308)] shadow-[0_0_30px_rgba(250,204,21,.6)] [transform:translateZ(3cqi)] @min-[480px]:size-[84px]"></span><span class="absolute top-[34px] left-[28%] h-10 w-[110px] rounded-full bg-[#f8fafc] shadow-[0_12px_18px_-8px_rgba(15,23,42,.45)] [transform:translateZ(8cqi)] before:absolute before:top-[-22px] before:left-4 before:size-[46px] before:rounded-full before:bg-inherit before:content-[''] after:absolute after:top-[-32px] after:left-12 after:size-14 after:rounded-full after:bg-inherit after:content-[''] @min-[480px]:top-[70px] @min-[480px]:left-[22%] @min-[480px]:w-[130px]"></span></div>
      <div class="[transform:translateZ(5cqi)]"><p class="text-[.8rem] font-extrabold tracking-[.16em] text-[#bae6fd] uppercase" id="mcw-city-2">Reykjavík</p><p class="text-[3.2rem] leading-none font-black tracking-[-.03em] @min-[480px]:text-[4rem]">6°</p><p class="mt-0.5 text-[.92rem] text-[#e0f2fe]">Bright spells, wind 24 km/h</p></div>
      <ul class="grid grid-cols-3 gap-2 [transform:translateZ(2cqi)]"><li class="grid gap-0.5 rounded-xl bg-[rgba(15,23,42,.35)] px-2.5 py-2 text-[.8rem] text-[#e0f2fe]">Thu<b class="text-base text-white">7°</b></li><li class="grid gap-0.5 rounded-xl bg-[rgba(15,23,42,.35)] px-2.5 py-2 text-[.8rem] text-[#e0f2fe]">Fri<b class="text-base text-white">4°</b></li><li class="grid gap-0.5 rounded-xl bg-[rgba(15,23,42,.35)] px-2.5 py-2 text-[.8rem] text-[#e0f2fe]">Sat<b class="text-base text-white">5°</b></li></ul>
    </article>
  </div>
</div></div>
CSS
/* @property registers the medal's angle so it can transition and animate on its own. The hills are SVG backgrounds
   (clip-path would break the 3D sorting). :has(), container queries and cqi units are utilities in the markup. */
@property --mc-ry{syntax:"<angle>";inherits:true;initial-value:0deg}
@keyframes mc-turn{from{--mc-ry:0deg}to{--mc-ry:360deg}}
@utility mc-hill-1{background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cpolygon points='0,70 14,38 27,60 44,22 60,55 74,30 90,58 100,44 100,100 0,100' fill='%23a78bfa'/%3E%3C/svg%3E") 0 0/100% 100% no-repeat}
@utility mc-hill-2{background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cpolygon points='0,62 18,52 32,74 50,48 66,70 82,50 100,66 100,100 0,100' fill='%237c3aed'/%3E%3C/svg%3E") 0 0/100% 100% no-repeat}
@utility mc-hill-3{background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cpolygon points='0,80 10,70 12,52 14,70 30,78 52,74 70,82 84,66 86,48 88,66 100,74 100,100 0,100' fill='%233b0764'/%3E%3C/svg%3E") 0 0/100% 100% no-repeat}

About this set

Three 3D effects that newer CSS features make possible without JavaScript. The medal is a gold race medal with a front and a back. Its turn is a custom property, –mc-ry, registered with @property as an angle, so it can transition when Back is picked and animate a full spin while the transform simply reads it. :has() connects the Front and Back radios and the Spin switch to the medal wherever they sit in the markup, and the spin only runs after the visitor turns it on. The diorama is a paper-cut scene of three hills inside a box; a five by five grid of invisible hover cells updates two custom properties through :has(), and perspective-origin follows them, so moving the pointer lets you look around inside the box. The weather card takes its depth from its container: the sun, cloud, temperature and forecast sit at translateZ distances in cqi units, and an @container rule switches to a wide layout from 480 pixels. The same card is shown in a narrow and a wide slot. Each effect falls back to a static design in browsers without these features.

What’s included

  • Angle registered with @property, so one rotation can transition and animate
  • :has() links the radios and the Spin switch to the medal without JavaScript
  • perspective-origin that follows the pointer through a :has() hover grid
  • translateZ depth in cqi units and an @container wide layout
  • Spin switch hidden under prefers-reduced-motion

Accessibility

The medal controls are a radio group with a legend and a checkbox with role="switch", each with a 3px focus outline on the visible control. The diorama is one image with a text alternative and a caption below it, since its effect depends on a pointer. The weather cards are articles named by their city. Under prefers-reduced-motion the spin switch is hidden, the medal turns without a transition and the diorama view changes without easing.

Customise it

Change the spin speed in the mc-turn animation and the turn time in the --mc-ry transition, the look-around range in the --px and --py rules, and the depth of each weather layer in cqi. The wide layout starts at the @container (min-width: 480px) rule, or the @min-[480px] variants in Tailwind.