<!-- Javascript is used to set the content in order to reduce noise and show the core HTML structure -->
<article id="demo" class="flip-demo perspective-container">
  <h2>Level 3: Rounded Corners</h2>
  <div class="card">
    <section class="front face" aria-hidden="false"></section>
    <section class="back face" aria-hidden="true"></section>
    <!-- NEW! We need divs that represent the corners. -->
    <div class="top-right corner">
      <div style="--i: 0;"></div>
      <div style="--i: 1;"></div>
      <div style="--i: 2;"></div>
    </div>
    <div class="bottom-right corner">
      <div style="--i: 0;"></div>
      <div style="--i: 1;"></div>
      <div style="--i: 2;"></div>
    </div>
    <div class="bottom-left corner">
      <div style="--i: 0;"></div>
      <div style="--i: 1;"></div>
      <div style="--i: 2;"></div>
    </div>
    <div class="top-left corner">
      <div style="--i: 0;"></div>
      <div style="--i: 1;"></div>
      <div style="--i: 2;"></div>
    </div>

    <div class="top edge"></div>
    <div class="right edge"></div>
    <div class="bottom edge"></div>
    <div class="left edge"></div>
  </div>
  <button>Flip Raichu</button>
</article>
.card {
  --corner-granularity: 3; /* The number of faces used to simulate a round corner. More faces means more smooth. */
  --border-radius: 1.5em; /* exaggerated */
  border-radius: var(--border-radius);
}

.corner > * {
  background-color: red; /* Red is just to highlight the corners */
}

/* We have to override the edges so they do not overlap the corners */
.right, .left {
  inset-block: var(--border-radius);
  height: calc(100% - 2 * var(--border-radius));
} .top, .bottom {
  inset-inline: var(--border-radius);
  width: calc(100% - 2 * var(--border-radius));
}

.corner {
  --n: var(--corner-granularity);
  --r: var(--border-radius);
  
  position: absolute;
  transform-style: preserve-3d;
}

/* A corner is composed of a finite number of flat faces that, when arranged in just the right way, looks rounded. We need to do it this way because curved 3D surfaces do not exist in CSS. */
.corner > * {
  position: absolute;
  inset-block-end: 0;
  width: var(--card-depth);
  height: calc(2 * var(--r) * sin(45deg / var(--n)));
  transform-origin: bottom center;
  
  /* This math constructs a single corner. I derived it on a paper somewhere and threw it away, so you'll have to derive it yourself if you want to understand what's happening (: */
  transform:
    translateZ(calc(var(--r) * cos(var(--i) * 90deg / var(--n))))
    translateY(calc(-1 * var(--r) * sin(var(--i) * 90deg / var(--n))))
    rotateX(calc(45deg * (2 * var(--i) + 1) / var(--n)));
}

/* The rest of this code slots the corners where they belong. */
.top-right {
  inset-block-start: 0;
  inset-inline-end: 0;

  transform:
    rotateY(90deg)
    translateZ(calc(-1 * var(--r)))
    translateY(var(--r));
} .bottom-right {
  inset-block-end: 0;
  inset-inline-end: 0;

  transform:
    rotateY(90deg)
    rotateX(270deg)
    translateZ(calc(-1 * var(--r)))
    translateY(var(--r));
} .bottom-left {
  inset-block-end: 0;
  inset-inline-start: 0;

  transform:
    rotateY(90deg)
    rotateX(180deg)
    translateZ(calc(-1 * var(--r)))
    translateY(var(--r));
} .top-left {
  inset-block-start: 0;
  inset-inline-start: 0;

  transform:
    rotateY(90deg)
    rotateX(90deg)
    translateZ(calc(-1 * var(--r)))
    translateY(var(--r));
}

/* ******************************************
 * END LEVEL 3 CODE
 * ******************************************/

/**
 * Card Thickness CSS (see: https://codepen.io/auroratide/pen/KKEEqzG)
 */
.card {
   --card-depth: 0.667em;
}

.card .back {
   transform: translateZ(calc(-1 * var(--card-depth))) rotateY(180deg);
}

.edge {
  position: absolute;
  background-color: var(--blue);
}

.right, .left {
   width: var(--card-depth);
} .right {
   inset-inline-end: 0;
   transform: rotateY(270deg);
   transform-origin: right center;
} .left {
   inset-inline-start: 0;
   transform: rotateY(90deg);
   transform-origin: left center;
}

.top, .bottom {
   height: var(--card-depth);
} .top {
   inset-block-start: 0;
   transform: rotateX(270deg);
   transform-origin: center top;
} .bottom {
   inset-block-end: 0;
   transform: rotateX(90deg);
   transform-origin: center bottom;
}

/**
 * Verticality CSS (see: https://codepen.io/auroratide/pen/xxBBdmo)
 */
@keyframes flip-to-front {
  0% { transform: translateZ(calc(-1 * var(--card-depth))) rotateY(-180deg); }
  50% { transform: translateZ(var(--flip-height)) rotateY(-270deg); }
  100% { transform: translateZ(0em) rotateY(-360deg); }
}

@keyframes flip-to-back {
  0% { transform: translateZ(0em) rotateY(0deg); }
  50% { transform: translateZ(var(--flip-height)) rotateY(-90deg); }
  100% { transform: translateZ(calc(-1 * var(--card-depth))) rotateY(-180deg); }
}

.card {
  --flip-height: 17.5em;
  
  animation-duration: 1.5s;
  animation-fill-mode: both;
  animation-timing-function: linear;
}

/**
 * BASE CARD CSS
 */
.card {
  width: 100%;
  max-width: 15em;
  aspect-ratio: 5 / 7;
  position: relative;
  transform-style: preserve-3d;
}

.card section {
  width: 100%;
  height: 100%;
  border-radius: inherit;
}

.card .face { backface-visibility: hidden; }

.card .back {
  position: absolute;
  inset: 0;
}

.perspective-container {
  perspective: 100em;
  perspective-origin: center;
}

/**
 * Base demo css. Not important for the demo; just to look pretty.
 */
:root {
  --blue: oklch(51% 0.129 259);
  --white: oklch(100% 0 0);
  --yellow: oklch(86% 0.18 89);
  --grey: oklch(32% 0 0);
  --electric: oklch(66% 0.137 91);
}

*, *::before, *::after { box-sizing: border-box; }

body {
  font-family:  -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
  background: radial-gradient(circle at 5vw 70vh, hsl(321 86% 91% / 0.5), hsl(321 86% 91% / 0) 67%),
        radial-gradient(circle at 50vw 5vh, hsl(120 50% 75% / 0.25), hsl(120 50% 75% / 0) 67%),
        radial-gradient(circle at 80vw 95vh, hsl(45 80% 65% / 0.25), hsl(45 50% 65% / 0) 67%);
  background-size: 100vw 100vh;
  background-size: 100dvw 100dvh;
  background-repeat: no-repeat;
}

.card p { margin: 0; }

.card section {
  border: 0.5em solid var(--blue);
  box-shadow: 0 0 0.15em 0.1em oklch(0% 0 0 / 0.75) inset;
  background: radial-gradient(var(--white) 25%, var(--yellow));
}

.front figure {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding-block: 1em;
}

.front figcaption {
  font-size: 1.333em;
  flex: 1;
  display: flex;
  align-items: center;
}

.front img {
  flex: 6;
  display: block;
  width: 80%;
  margin: 0 auto;
  object-fit: contain;
}

.back { padding: 1em 0.5em; }

.back header {
  display: flex;
  align-items: center;
}

.back header > :first-child {
  flex: 1;
}

.type-tag {
  color: white;
  font-size: 85%;
  font-weight: bold;
  text-align: center;
  line-height: 1;
  padding: 0.25em 1em;
  border-radius: 1em;
}

.type-tag.electric { background-color: var(--electric); }

.back dl {
  display: grid;
  grid-template-columns: 5.5ch 4ch 1fr;
}

.back dt { font-weight: bold; }
.back dt + dd { text-align: end; }
.back dd { margin: 0; }
.back dd + dd { padding-inline: 0.5em; }
.back [role="meter"] {
  height: 90%;
  width: calc(100% * (var(--value) / 255));
  border: 0.0625em solid;
}

.back footer {
  font-size: 0.875em;
  line-height: 1.25;
}

[role="meter"].hp {
  background-color: oklch(63% 0.258 29);
  border-color: oklch(46% 0.187 29);
}
[role="meter"].atk {
  background-color: oklch(71% 0.163 52);
  border-color: oklch(46% 0.187 29);
}
[role="meter"].def {
  background-color: oklch(87% 0.167 94);
  border-color: oklch(63% 0.119 94);
}
[role="meter"].spatk {
  background-color: oklch(67% 0.15 265);
  border-color: oklch(49% 0.105 266);
}
[role="meter"].spdef {
  background-color: oklch(76% 0.174 137);
  border-color: oklch(55% 0.123 137);
}
[role="meter"].spd {
  background-color: oklch(69% 0.198 6);
  border-color: oklch(50% 0.14 5);
}

.flip-demo {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.card { margin-block-end: 1em; }

button {
  font-size: 1.25em;
  border: none;
  color: var(--white);
  background-color: var(--grey);
  padding: 0.75em 1.25em;
  border-radius: 1.75em;
  box-shadow: 0 0.1em 0.5em oklch(0% 0 0 / 0.333);
  cursor: pointer;
}

button:hover,
button:focus {
  color: var(--grey);
  background-color: var(--white);
}

button:active { box-shadow: none; }
function flipCard(card) {
  card.classList.toggle("facedown")
  const isFacedown = card.classList.contains("facedown")

  card.style.animationName = isFacedown
    ? "flip-to-back"
    : "flip-to-front"
  
  setAccessibility(card)
}

function setAccessibility(card) {
  const isFacedown = card.classList.contains("facedown")
  const front = card.querySelector(".front")
  const back = card.querySelector(".back")
  
  front.setAttribute("aria-hidden", isFacedown.toString())
  back.setAttribute("aria-hidden", (!isFacedown).toString())
}

/**
 * Just setup. Not important for the demo.
 */
const demo = document.querySelector("#demo")

function setupButton() {
  const button = demo.querySelector("button")
  const card = demo.querySelector(".card")
  
  button.addEventListener("click", () => flipCard(card))
}

function setupContent() {
  const front = demo.querySelector(".front")
  const back = demo.querySelector(".back")
  
  front.innerHTML = `
    <figure>
      <figcaption><strong>Raichu #026</strong></figcaption>
      <img src="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/26.png" alt="Small orange mouselike creature" />
    </figure>
  `
  
  back.innerHTML = `
    <header>
      <p><strong>Raichu</strong></p>
      <p class="type-tag electric">Electric</p>
    </header>
    <dl>
      <dt>HP</dt>
      <dd>60</dd>
      <dd><div class="hp" role="meter" aria-valuenow="60" aria-valuemin="0" aria-valuemax="255" aria-label="HP" style="--value: 60;"></div></dd>
      <dt>Atk</dt>
      <dd>90</dd>
      <dd><div class="atk" role="meter" aria-valuenow="90" aria-valuemin="0" aria-valuemax="255" aria-label="Attack" style="--value: 90;"></div></dd>
      <dt>Def</dt>
      <dd>55</dd>
      <dd><div class="def" role="meter" aria-valuenow="55" aria-valuemin="0" aria-valuemax="255" aria-label="Defense" style="--value: 55;"></div></dd>
      <dt>Sp.Atk</dt>
      <dd>90</dd>
      <dd><div class="spatk" role="meter" aria-valuenow="90" aria-valuemin="0" aria-valuemax="255" aria-label="Special Attack" style="--value: 90;"></div></dd>
      <dt>Sp.Def</dt>
      <dd>80</dd>
      <dd><div class="spdef" role="meter" aria-valuenow="80" aria-valuemin="0" aria-valuemax="255" aria-label="Special Defense" style="--value: 80;"></div></dd>
      <dt>Spd</dt>
      <dd>110</dd>
      <dd><div class="spd" role="meter" aria-valuenow="110" aria-valuemin="0" aria-valuemax="255" aria-label="Speed" style="--value: 110;"></div></dd>
    </dl>
    <footer>
      <p>The Mouse Pokémon. When electricity builds up inside its body, it becomes feisty. It also glows in the dark.</p>
    </footer>
  `
}

setupContent()
setupButton()

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.