<!-- 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 2: Thickness</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 4 edges -->
<div class="top edge"></div>
<div class="right edge"></div>
<div class="bottom edge"></div>
<div class="left edge"></div>
</div>
<button>Flip Pikachu</button>
</article>
.card {
--card-depth: 0.667em; /* exaggerated */
border-radius: 0; /* Without special corner logic, a card with thickness cannot have border radius */
}
.card .back {
transform: translateZ(calc(-1 * var(--card-depth))) rotateY(180deg); /* Push the back of the card backward to give space for the edges to live */
}
.edge {
position: absolute;
background-color: var(--blue);
}
/* All of this code is aligning the edges, rotating them into the page */
.right, .left {
width: var(--card-depth);
height: 100%;
inset-block: 0;
} .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 {
width: 100%;
height: var(--card-depth);
inset-inline: 0;
} .top {
inset-block-start: 0;
transform: rotateX(270deg);
transform-origin: center top;
} .bottom {
inset-block-end: 0;
transform: rotateX(90deg);
transform-origin: center bottom;
}
/* ******************************************
* END LEVEL 2 CODE
* ******************************************/
/**
* 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;
/* Overridden, because without special corner logic a card with thickness cannot have a border radius. */
/* border-radius: 0.75em; */
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;
/* Replaced by card thickness code */
/* transform: rotateY(180deg); */
}
.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>Pikachu #025</strong></figcaption>
<img src="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/25.png" alt="Small yellow mouselike creature" />
</figure>
`
back.innerHTML = `
<header>
<p><strong>Pikachu</strong></p>
<p class="type-tag electric">Electric</p>
</header>
<dl>
<dt>HP</dt>
<dd>35</dd>
<dd><div class="hp" role="meter" aria-valuenow="35" aria-valuemin="0" aria-valuemax="255" aria-label="HP" style="--value: 35;"></div></dd>
<dt>Atk</dt>
<dd>55</dd>
<dd><div class="atk" role="meter" aria-valuenow="55" aria-valuemin="0" aria-valuemax="255" aria-label="Attack" style="--value: 55;"></div></dd>
<dt>Def</dt>
<dd>30</dd>
<dd><div class="def" role="meter" aria-valuenow="30" aria-valuemin="0" aria-valuemax="255" aria-label="Defense" style="--value: 30;"></div></dd>
<dt>Sp.Atk</dt>
<dd>50</dd>
<dd><div class="spatk" role="meter" aria-valuenow="50" aria-valuemin="0" aria-valuemax="255" aria-label="Special Attack" style="--value: 50;"></div></dd>
<dt>Sp.Def</dt>
<dd>40</dd>
<dd><div class="spdef" role="meter" aria-valuenow="40" aria-valuemin="0" aria-valuemax="255" aria-label="Special Defense" style="--value: 40;"></div></dd>
<dt>Spd</dt>
<dd>90</dd>
<dd><div class="spd" role="meter" aria-valuenow="90" aria-valuemin="0" aria-valuemax="255" aria-label="Speed" style="--value: 90;"></div></dd>
</dl>
<footer>
<p>The Mouse Pokémon. This forest-dwelling Pokémon stores electricity in its cheeks, so you’ll feel a tingly shock if you touch it.</p>
</footer>
`
}
setupContent()
setupButton()
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.