15 / 20
All 20 in 20 CSS 3D Effects
- 01CSS 3D Transform Pipeline Reference Build
- 02Perspective Property Versus Perspective Function
- 03Perspective Origin as the Camera Position
- 04Transform Function Order Changes the Result
- 05Backface Visibility and Two Sided Elements
- 06Preserve 3D and What Flattens a Scene
- 07Building a CSS Cube from Six Faces
- 08Transform Origin in Three Dimensions
- 09TranslateZ Versus Scale for Depth
- 10Z Fighting on Coplanar 3D Surfaces
- 11Rotate3d and the Transform Matrix
- 12Ring Layout with Rotate and TranslateZ
- 13Depth Layering with Stacked Z Planes
- 14Pointer Tilt Cards with Parallax Depth Layers
- 15Page Turn Flipbook with Hinged Rotation
- 16Cube Face Navigation with Drag and Auto Rotate
- 17Extruded Synth Panel with Draggable Faders
- 18Perspective Floor Grid Geometry
- 19Nested 3D Contexts in a Pointer Steered Scene
- 20Helix Distribution with Trigonometric Angles
Page Turn Flipbook with Hinged Rotation
A four-spread journal that turns on a spine hinge: transform-origin at the gutter turns rotateY into a page turn, each leaf carries a front and a back, and the script's real job is the stacking order — z-index recomputed on every turn so turned leaves fall behind the ones still to come. Prev and next buttons, click-a-half, and arrow keys all drive the same single index.
Untitled UI Pro: a 10,000+ component Figma UI kit · from $129 one-time
Untitled UI · Affiliate link
The code
<section class="d3-15" aria-label="Page-turn flipbook demo">
<div class="d3-15__theme">
<input class="d3-15__theme-input" type="checkbox" id="d3-15-theme">
<label class="d3-15__theme-label" for="d3-15-theme" aria-label="Toggle light and dark theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 1 0 17z" fill="currentColor"/></svg>
</label>
</div>
<div class="card">
<div class="book-scene">
<div class="book" data-d3-15-book>
<div class="book-shadow" aria-hidden="true"></div>
<div class="left-cover" data-d3-15-cover>
<div class="cover-art">
<div class="cover-eyebrow">FORMA STUDIO</div>
<div class="cover-rule"></div>
<div class="cover-title">FORMA<span class="cover-vol">Vol. VI</span></div>
<div class="cover-rule"></div>
<div class="cover-sub">Design · Architecture · Thought</div>
</div>
</div>
<div class="page" data-page="0">
<div class="page-front paper-warm">
<div class="pg pg-warm">
<div class="spread-label">Chapter One</div>
<div class="spread-title sm">The Weight<br />of Silence</div>
<div class="spread-rule"></div>
<p class="spread-body">There is a moment before speech when the world holds its breath. Architecture inhabits this pause — not the buildings themselves, but the spaces they carve from air.</p>
<p class="spread-body mt">The corridor narrows, draws the eye forward. Light slants from an unseen source. To walk through is to be composed by the building.</p>
<span class="pg-num right">1</span>
<span class="spread-big-num">I</span>
</div>
</div>
<div class="page-back paper-cool">
<div class="pg pg-cool">
<div class="spread-label">Essay II</div>
<div class="spread-title xs">Form<br />Without<br />Apology</div>
<div class="spread-rule alt"></div>
<p class="spread-body alt">To design is to make a claim about the world as it should be — not as it is. The blueprint precedes the building, and in that gap lives everything called imagination.</p>
<span class="pg-num left">2</span>
</div>
</div>
</div>
<div class="page" data-page="1">
<div class="page-front paper-sage">
<div class="pg pg-sage">
<div class="spread-label">Visual Field</div>
<div class="spread-img sage-img">
<div class="circle-outer">
<div class="circle-inner"></div>
</div>
</div>
<div class="spread-pullquote sage">"Growth is not linear. It is fractal — repeating at every scale."</div>
<p class="spread-body sage-body">The forest does not plan its canopy. Each tree simply grows toward light.</p>
<span class="pg-num right sage">3</span>
</div>
</div>
<div class="page-back paper-rose">
<div class="pg pg-rose">
<div class="spread-label">Interlude</div>
<div class="spread-title sm rose">Red<br />Notation</div>
<div class="spread-rule rose"></div>
<p class="spread-body rose-body">Color arrives before language. Red is felt in the chest before named by the tongue. The ancient painters knew this — their ochre hands pressed flat on cave walls were not decoration. They were declaration.</p>
<span class="pg-num left rose">4</span>
</div>
</div>
</div>
<div class="page" data-page="2">
<div class="page-front paper-warm">
<div class="pg pg-warm">
<div class="spread-label">Final Study</div>
<div class="spread-title sm">Endings<br />as Form</div>
<div class="spread-rule"></div>
<p class="spread-body">Every designed thing must eventually end — the page turn, the last note, the exit from the building. The best designs make their endings feel inevitable, not arbitrary.</p>
<div class="spread-pullquote warm">"The final line is the first thing written."</div>
<span class="pg-num right">5</span>
</div>
</div>
<div class="page-back paper-ink">
<div class="pg pg-ink">
<div class="colophon-label">COLOPHON</div>
<div class="colophon-title">FORMA</div>
<div class="colophon-sub">A Journal of Design Thought</div>
<p class="colophon-body">Issue VI · Printed on FSC-certified stock · Typography set in Playfair Display and EB Garamond · All rights reserved ©</p>
<span class="pg-num left ink">6</span>
</div>
</div>
</div>
</div>
<div class="nav">
<button class="nav-btn" type="button" data-d3-15-prev disabled>← Prev</button>
<span class="page-counter" data-d3-15-counter>Spread 1 of 4</span>
<button class="nav-btn" type="button" data-d3-15-next>Next →</button>
</div>
</div>
</div>
</section><section class="d3-15" aria-label="Page-turn flipbook demo">
<div class="d3-15__theme">
<input class="d3-15__theme-input" type="checkbox" id="d3-15-theme">
<label class="d3-15__theme-label" for="d3-15-theme" aria-label="Toggle light and dark theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 1 0 17z" fill="currentColor"/></svg>
</label>
</div>
<div class="card">
<div class="book-scene">
<div class="book" data-d3-15-book>
<div class="book-shadow" aria-hidden="true"></div>
<div class="left-cover" data-d3-15-cover>
<div class="cover-art">
<div class="cover-eyebrow">FORMA STUDIO</div>
<div class="cover-rule"></div>
<div class="cover-title">FORMA<span class="cover-vol">Vol. VI</span></div>
<div class="cover-rule"></div>
<div class="cover-sub">Design · Architecture · Thought</div>
</div>
</div>
<div class="page" data-page="0">
<div class="page-front paper-warm">
<div class="pg pg-warm">
<div class="spread-label">Chapter One</div>
<div class="spread-title sm">The Weight<br />of Silence</div>
<div class="spread-rule"></div>
<p class="spread-body">There is a moment before speech when the world holds its breath. Architecture inhabits this pause — not the buildings themselves, but the spaces they carve from air.</p>
<p class="spread-body mt">The corridor narrows, draws the eye forward. Light slants from an unseen source. To walk through is to be composed by the building.</p>
<span class="pg-num right">1</span>
<span class="spread-big-num">I</span>
</div>
</div>
<div class="page-back paper-cool">
<div class="pg pg-cool">
<div class="spread-label">Essay II</div>
<div class="spread-title xs">Form<br />Without<br />Apology</div>
<div class="spread-rule alt"></div>
<p class="spread-body alt">To design is to make a claim about the world as it should be — not as it is. The blueprint precedes the building, and in that gap lives everything called imagination.</p>
<span class="pg-num left">2</span>
</div>
</div>
</div>
<div class="page" data-page="1">
<div class="page-front paper-sage">
<div class="pg pg-sage">
<div class="spread-label">Visual Field</div>
<div class="spread-img sage-img">
<div class="circle-outer">
<div class="circle-inner"></div>
</div>
</div>
<div class="spread-pullquote sage">"Growth is not linear. It is fractal — repeating at every scale."</div>
<p class="spread-body sage-body">The forest does not plan its canopy. Each tree simply grows toward light.</p>
<span class="pg-num right sage">3</span>
</div>
</div>
<div class="page-back paper-rose">
<div class="pg pg-rose">
<div class="spread-label">Interlude</div>
<div class="spread-title sm rose">Red<br />Notation</div>
<div class="spread-rule rose"></div>
<p class="spread-body rose-body">Color arrives before language. Red is felt in the chest before named by the tongue. The ancient painters knew this — their ochre hands pressed flat on cave walls were not decoration. They were declaration.</p>
<span class="pg-num left rose">4</span>
</div>
</div>
</div>
<div class="page" data-page="2">
<div class="page-front paper-warm">
<div class="pg pg-warm">
<div class="spread-label">Final Study</div>
<div class="spread-title sm">Endings<br />as Form</div>
<div class="spread-rule"></div>
<p class="spread-body">Every designed thing must eventually end — the page turn, the last note, the exit from the building. The best designs make their endings feel inevitable, not arbitrary.</p>
<div class="spread-pullquote warm">"The final line is the first thing written."</div>
<span class="pg-num right">5</span>
</div>
</div>
<div class="page-back paper-ink">
<div class="pg pg-ink">
<div class="colophon-label">COLOPHON</div>
<div class="colophon-title">FORMA</div>
<div class="colophon-sub">A Journal of Design Thought</div>
<p class="colophon-body">Issue VI · Printed on FSC-certified stock · Typography set in Playfair Display and EB Garamond · All rights reserved ©</p>
<span class="pg-num left ink">6</span>
</div>
</div>
</div>
</div>
<div class="nav">
<button class="nav-btn" type="button" data-d3-15-prev disabled>← Prev</button>
<span class="page-counter" data-d3-15-counter>Spread 1 of 4</span>
<button class="nav-btn" type="button" data-d3-15-next>Next →</button>
</div>
</div>
</div>
</section>@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;0,900;1,400;1,700&family=EB+Garamond:ital,wght@0,400;0,500;1,400;1,500&display=swap');
.d3-15 {
--d3-15-desk: #1c1410;
--d3-15-desk-wash: rgba(180,120,40,0.06);
--d3-15-spine-deep: rgba(0,0,0,0.40);
--d3-15-spine-soft: rgba(0,0,0,0.10);
--d3-15-page-shade: rgba(0,0,0,0.14);
--d3-15-cover-shade: rgba(0,0,0,0.20);
--d3-15-book-shadow: rgba(0,0,0,0.50);
--d3-15-paper-warm: #f5ece0;
--d3-15-paper-cool: #ede8f0;
--d3-15-paper-rose: #f5e8e8;
--d3-15-paper-sage: #e8f0e8;
--d3-15-paper-ink: #1a1510;
--d3-15-ink-warm: #2a1f14;
--d3-15-ink-cool: #2a2432;
--d3-15-ink-rose: #2a1418;
--d3-15-ink-sage: #1a2a1e;
--d3-15-ink-page: #e8d8c0;
--d3-15-folio-warm: #8a6a4a;
--d3-15-folio-cool: #7a6898;
--d3-15-folio-rose: #8a4050;
--d3-15-folio-sage: #4a7a50;
--d3-15-folio-ink: rgba(200,180,140,0.40);
--d3-15-rule-cool: #4a3868;
--d3-15-rule-rose: #8a3040;
--d3-15-quote-warm-line: #8a6a4a;
--d3-15-quote-warm-ink: #4a3424;
--d3-15-quote-sage-line: #3a6a40;
--d3-15-quote-sage-ink: #2a4a2e;
--d3-15-title-rose: #5a1828;
--d3-15-body-cool: #3a2a50;
--d3-15-body-rose: #3a1820;
--d3-15-body-sage: #2a3a26;
--d3-15-sage-img-1: #2a4a2e;
--d3-15-sage-img-2: #1a3020;
--d3-15-circle-line: rgba(120,200,120,0.40);
--d3-15-circle-fill: rgba(120,200,120,0.20);
--d3-15-cover-1: #1a0f08;
--d3-15-cover-2: #0e0a06;
--d3-15-cover-wash: rgba(180,120,40,0.12);
--d3-15-gold-dim: rgba(200,160,80,0.55);
--d3-15-gold-rule: rgba(200,160,80,0.35);
--d3-15-cover-ink: #f0e4cc;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: rgba(220,190,140,1);
--d3-15-nav-hover: rgba(200,170,120,0.08);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.05);
--d3-15-ui-line: rgba(200,170,120,0.28);
--d3-15-ui-ink: rgba(220,190,140,0.92);
width: 100%;
min-height: max(100vh, 600px);
display: block;
position: relative;
background: var(--d3-15-desk);
font-family: 'EB Garamond', Georgia, serif;
overflow: hidden;
perspective: 2400px;
box-sizing: border-box;
}
.d3-15 *,
.d3-15 *::before,
.d3-15 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.d3-15:has(.d3-15__theme-input:checked) {
--d3-15-desk: #ded0bb;
--d3-15-desk-wash: rgba(120,80,20,0.10);
--d3-15-spine-deep: rgba(58,38,16,0.34);
--d3-15-spine-soft: rgba(58,38,16,0.08);
--d3-15-page-shade: rgba(78,54,24,0.13);
--d3-15-cover-shade: rgba(70,46,20,0.18);
--d3-15-book-shadow: rgba(72,50,22,0.30);
--d3-15-paper-warm: #fdf7ec;
--d3-15-paper-cool: #f4f0fa;
--d3-15-paper-rose: #fdefef;
--d3-15-paper-sage: #eef8ef;
--d3-15-paper-ink: #241b13;
--d3-15-ink-warm: #241a10;
--d3-15-ink-cool: #241d2e;
--d3-15-ink-rose: #2a1014;
--d3-15-ink-sage: #14261a;
--d3-15-ink-page: #f2e5ce;
--d3-15-folio-warm: #6d4f2f;
--d3-15-folio-cool: #5d4a80;
--d3-15-folio-rose: #77303f;
--d3-15-folio-sage: #35603f;
--d3-15-folio-ink: rgba(216,198,160,0.55);
--d3-15-rule-cool: #3d2b5c;
--d3-15-rule-rose: #77242f;
--d3-15-quote-warm-line: #7a5836;
--d3-15-quote-warm-ink: #3b2a17;
--d3-15-quote-sage-line: #2b5733;
--d3-15-quote-sage-ink: #1e3d22;
--d3-15-title-rose: #58101f;
--d3-15-body-cool: #2f2044;
--d3-15-body-rose: #34121a;
--d3-15-body-sage: #22321d;
--d3-15-sage-img-1: #23482a;
--d3-15-sage-img-2: #12301b;
--d3-15-circle-line: rgba(184,255,196,0.45);
--d3-15-circle-fill: rgba(184,255,196,0.22);
--d3-15-cover-1: #241408;
--d3-15-cover-2: #150c05;
--d3-15-cover-wash: rgba(220,160,60,0.16);
--d3-15-gold-dim: rgba(228,188,106,0.72);
--d3-15-gold-rule: rgba(228,188,106,0.45);
--d3-15-cover-ink: #f8eed8;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: #2a1e08;
--d3-15-nav-hover: rgba(90,62,20,0.10);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.60);
--d3-15-ui-line: rgba(70,48,22,0.22);
--d3-15-ui-ink: #3a2a12;
}
@media (prefers-color-scheme: light) {
.d3-15:has(.d3-15__theme-input:checked) {
--d3-15-desk: #ded0bb;
--d3-15-desk-wash: rgba(120,80,20,0.10);
--d3-15-spine-deep: rgba(58,38,16,0.34);
--d3-15-spine-soft: rgba(58,38,16,0.08);
--d3-15-page-shade: rgba(78,54,24,0.13);
--d3-15-cover-shade: rgba(70,46,20,0.18);
--d3-15-book-shadow: rgba(72,50,22,0.30);
--d3-15-paper-warm: #fdf7ec;
--d3-15-paper-cool: #f4f0fa;
--d3-15-paper-rose: #fdefef;
--d3-15-paper-sage: #eef8ef;
--d3-15-paper-ink: #241b13;
--d3-15-ink-warm: #241a10;
--d3-15-ink-cool: #241d2e;
--d3-15-ink-rose: #2a1014;
--d3-15-ink-sage: #14261a;
--d3-15-ink-page: #f2e5ce;
--d3-15-folio-warm: #6d4f2f;
--d3-15-folio-cool: #5d4a80;
--d3-15-folio-rose: #77303f;
--d3-15-folio-sage: #35603f;
--d3-15-folio-ink: rgba(216,198,160,0.55);
--d3-15-rule-cool: #3d2b5c;
--d3-15-rule-rose: #77242f;
--d3-15-quote-warm-line: #7a5836;
--d3-15-quote-warm-ink: #3b2a17;
--d3-15-quote-sage-line: #2b5733;
--d3-15-quote-sage-ink: #1e3d22;
--d3-15-title-rose: #58101f;
--d3-15-body-cool: #2f2044;
--d3-15-body-rose: #34121a;
--d3-15-body-sage: #22321d;
--d3-15-sage-img-1: #23482a;
--d3-15-sage-img-2: #12301b;
--d3-15-circle-line: rgba(184,255,196,0.45);
--d3-15-circle-fill: rgba(184,255,196,0.22);
--d3-15-cover-1: #241408;
--d3-15-cover-2: #150c05;
--d3-15-cover-wash: rgba(220,160,60,0.16);
--d3-15-gold-dim: rgba(228,188,106,0.72);
--d3-15-gold-rule: rgba(228,188,106,0.45);
--d3-15-cover-ink: #f8eed8;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: #2a1e08;
--d3-15-nav-hover: rgba(90,62,20,0.10);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.60);
--d3-15-ui-line: rgba(70,48,22,0.22);
--d3-15-ui-ink: #3a2a12;
}
}
@media (prefers-color-scheme: dark) {
.d3-15:has(.d3-15__theme-input:checked) {
--d3-15-desk: #ded0bb;
--d3-15-desk-wash: rgba(120,80,20,0.10);
--d3-15-spine-deep: rgba(58,38,16,0.34);
--d3-15-spine-soft: rgba(58,38,16,0.08);
--d3-15-page-shade: rgba(78,54,24,0.13);
--d3-15-cover-shade: rgba(70,46,20,0.18);
--d3-15-book-shadow: rgba(72,50,22,0.30);
--d3-15-paper-warm: #fdf7ec;
--d3-15-paper-cool: #f4f0fa;
--d3-15-paper-rose: #fdefef;
--d3-15-paper-sage: #eef8ef;
--d3-15-paper-ink: #241b13;
--d3-15-ink-warm: #241a10;
--d3-15-ink-cool: #241d2e;
--d3-15-ink-rose: #2a1014;
--d3-15-ink-sage: #14261a;
--d3-15-ink-page: #f2e5ce;
--d3-15-folio-warm: #6d4f2f;
--d3-15-folio-cool: #5d4a80;
--d3-15-folio-rose: #77303f;
--d3-15-folio-sage: #35603f;
--d3-15-folio-ink: rgba(216,198,160,0.55);
--d3-15-rule-cool: #3d2b5c;
--d3-15-rule-rose: #77242f;
--d3-15-quote-warm-line: #7a5836;
--d3-15-quote-warm-ink: #3b2a17;
--d3-15-quote-sage-line: #2b5733;
--d3-15-quote-sage-ink: #1e3d22;
--d3-15-title-rose: #58101f;
--d3-15-body-cool: #2f2044;
--d3-15-body-rose: #34121a;
--d3-15-body-sage: #22321d;
--d3-15-sage-img-1: #23482a;
--d3-15-sage-img-2: #12301b;
--d3-15-circle-line: rgba(184,255,196,0.45);
--d3-15-circle-fill: rgba(184,255,196,0.22);
--d3-15-cover-1: #241408;
--d3-15-cover-2: #150c05;
--d3-15-cover-wash: rgba(220,160,60,0.16);
--d3-15-gold-dim: rgba(228,188,106,0.72);
--d3-15-gold-rule: rgba(228,188,106,0.45);
--d3-15-cover-ink: #f8eed8;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: #2a1e08;
--d3-15-nav-hover: rgba(90,62,20,0.10);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.60);
--d3-15-ui-line: rgba(70,48,22,0.22);
--d3-15-ui-ink: #3a2a12;
}
}
.d3-15 .d3-15__theme {
position: absolute;
inset-block-start: clamp(0.75rem, 3vw, 1.5rem);
inset-inline-end: clamp(0.75rem, 3vw, 1.5rem);
z-index: 30;
}
.d3-15 .d3-15__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
z-index: 1;
}
.d3-15 .d3-15__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--d3-15-ui-line);
border-radius: 12px;
background: var(--d3-15-ui-face);
color: var(--d3-15-ui-ink);
pointer-events: none;
}
.d3-15 .d3-15__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.d3-15 .d3-15__theme-input:focus-visible + .d3-15__theme-label {
outline: 2px solid currentColor;
outline-offset: 3px;
}
.d3-15 .card {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 28px 16px;
}
.d3-15 .card::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse 60% 50% at 50% 40%, var(--d3-15-desk-wash) 0%, transparent 70%);
pointer-events: none;
}
.d3-15 .book-scene {
transform-style: preserve-3d;
transform: rotateX(5deg) rotateY(-4deg);
transition: transform 0.6s ease;
position: relative;
z-index: 1;
}
.d3-15 .book-scene:hover {
transform: rotateX(4deg) rotateY(-2deg);
}
.d3-15 .book {
width: 560px;
height: 360px;
position: relative;
transform-style: preserve-3d;
}
.d3-15 .book::before {
content: '';
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 12px;
transform: translateX(-50%) translateZ(1px);
background: linear-gradient(to right, var(--d3-15-spine-deep) 0%, var(--d3-15-spine-soft) 30%, var(--d3-15-spine-soft) 70%, var(--d3-15-spine-deep) 100%);
z-index: 20;
pointer-events: none;
}
.d3-15 .page {
position: absolute;
width: 280px;
height: 360px;
top: 0;
left: 280px;
transform-origin: left center;
transform-style: preserve-3d;
transition: transform 1.1s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.d3-15 .page.flipped {
transform: rotateY(-180deg);
}
.d3-15 .page-front,
.d3-15 .page-back {
position: absolute;
inset: 0;
backface-visibility: hidden;
overflow: hidden;
}
.d3-15 .page-back {
transform: rotateY(180deg);
}
.d3-15 .left-cover {
position: absolute;
left: 0;
top: 0;
width: 280px;
height: 360px;
z-index: 0;
border-radius: 4px 0 0 4px;
overflow: hidden;
}
.d3-15 .pg {
width: 100%;
height: 100%;
padding: 30px 26px;
display: flex;
flex-direction: column;
position: relative;
}
.d3-15 .pg-warm {
color: var(--d3-15-ink-warm);
}
.d3-15 .pg-cool {
color: var(--d3-15-ink-cool);
}
.d3-15 .pg-rose {
color: var(--d3-15-ink-rose);
}
.d3-15 .pg-sage {
color: var(--d3-15-ink-sage);
}
.d3-15 .pg-ink {
color: var(--d3-15-ink-page);
}
.d3-15 .pg-num {
font-family: 'EB Garamond', serif;
font-size: 10px;
letter-spacing: 2px;
position: absolute;
bottom: 16px;
}
.d3-15 .pg-num.right {
right: 22px;
color: var(--d3-15-folio-warm);
}
.d3-15 .pg-num.left {
left: 22px;
color: var(--d3-15-folio-cool);
}
.d3-15 .pg-num.rose {
color: var(--d3-15-folio-rose);
}
.d3-15 .pg-num.sage {
color: var(--d3-15-folio-sage);
}
.d3-15 .pg-num.ink {
color: var(--d3-15-folio-ink);
}
.d3-15 .page-front::after,
.d3-15 .page-back::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
width: 40px;
pointer-events: none;
}
.d3-15 .page-front::after {
right: 0;
background: linear-gradient(to right, transparent, var(--d3-15-page-shade));
}
.d3-15 .page-back::after {
left: 0;
background: linear-gradient(to left, transparent, var(--d3-15-page-shade));
}
.d3-15 .left-cover::after {
content: '';
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 30px;
background: linear-gradient(to right, transparent, var(--d3-15-cover-shade));
}
.d3-15 .paper-warm {
background: var(--d3-15-paper-warm);
}
.d3-15 .paper-cool {
background: var(--d3-15-paper-cool);
}
.d3-15 .paper-rose {
background: var(--d3-15-paper-rose);
}
.d3-15 .paper-sage {
background: var(--d3-15-paper-sage);
}
.d3-15 .paper-ink {
background: var(--d3-15-paper-ink);
}
.d3-15 .spread-label {
font-family: 'EB Garamond', serif;
font-size: 9px;
letter-spacing: 4px;
text-transform: uppercase;
opacity: 0.72;
margin-bottom: 16px;
}
.d3-15 .spread-title {
font-family: 'Playfair Display', serif;
font-weight: 900;
line-height: 1.1;
margin-bottom: 14px;
}
.d3-15 .spread-title.sm {
font-size: 26px;
}
.d3-15 .spread-title.xs {
font-size: 22px;
}
.d3-15 .spread-title.rose {
color: var(--d3-15-title-rose);
}
.d3-15 .spread-body {
font-family: 'EB Garamond', serif;
font-size: 13px;
line-height: 1.78;
opacity: 0.78;
}
.d3-15 .spread-body.mt {
margin-top: 10px;
}
.d3-15 .spread-body.alt {
color: var(--d3-15-body-cool);
}
.d3-15 .spread-body.rose-body {
color: var(--d3-15-body-rose);
}
.d3-15 .spread-body.sage-body {
color: var(--d3-15-body-sage);
}
.d3-15 .spread-pullquote {
font-family: 'Playfair Display', serif;
font-style: italic;
font-size: 16px;
line-height: 1.5;
border-left: 2px solid currentColor;
padding-left: 14px;
margin: 16px 0;
opacity: 0.85;
}
.d3-15 .spread-pullquote.warm {
border-color: var(--d3-15-quote-warm-line);
color: var(--d3-15-quote-warm-ink);
}
.d3-15 .spread-pullquote.sage {
border-color: var(--d3-15-quote-sage-line);
color: var(--d3-15-quote-sage-ink);
}
.d3-15 .spread-rule {
width: 40px;
height: 1px;
background: currentColor;
opacity: 0.3;
margin: 14px 0;
}
.d3-15 .spread-rule.alt {
background: var(--d3-15-rule-cool);
}
.d3-15 .spread-rule.rose {
background: var(--d3-15-rule-rose);
}
.d3-15 .spread-img {
width: 100%;
height: 100px;
border-radius: 4px;
margin: 14px 0;
overflow: hidden;
flex-shrink: 0;
}
.d3-15 .sage-img {
height: 72px;
background: linear-gradient(135deg, var(--d3-15-sage-img-1), var(--d3-15-sage-img-2));
display: flex;
align-items: center;
justify-content: center;
}
.d3-15 .circle-outer {
width: 52px;
height: 52px;
border: 2px solid var(--d3-15-circle-line);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.d3-15 .circle-inner {
width: 32px;
height: 32px;
background: var(--d3-15-circle-fill);
border-radius: 50%;
}
.d3-15 .spread-big-num {
font-family: 'Playfair Display', serif;
font-size: 80px;
font-weight: 900;
line-height: 0.9;
opacity: 0.07;
position: absolute;
right: 16px;
bottom: 30px;
}
.d3-15 .cover-art {
width: 100%;
height: 100%;
background: linear-gradient(148deg, var(--d3-15-cover-1), var(--d3-15-cover-2));
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 26px;
border-radius: 4px 0 0 4px;
position: relative;
overflow: hidden;
}
.d3-15 .cover-art::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse 70% 60% at 50% 40%, var(--d3-15-cover-wash) 0%, transparent 70%);
}
.d3-15 .cover-eyebrow {
font-family: 'EB Garamond', serif;
font-size: 9px;
letter-spacing: 5px;
color: var(--d3-15-gold-dim);
margin-bottom: 26px;
z-index: 1;
}
.d3-15 .cover-rule {
width: 56px;
height: 1px;
background: var(--d3-15-gold-rule);
margin: 0 0 26px;
z-index: 1;
}
.d3-15 .cover-title {
font-family: 'Playfair Display', serif;
font-size: 34px;
font-weight: 900;
color: var(--d3-15-cover-ink);
text-align: center;
line-height: 1.1;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.d3-15 .cover-vol {
font-style: italic;
font-weight: 400;
font-size: 20px;
opacity: 0.7;
margin-top: 6px;
}
.d3-15 .cover-rule:nth-of-type(2),
.d3-15 .cover-title + .cover-rule {
margin: 26px 0;
}
.d3-15 .cover-sub {
font-family: 'EB Garamond', serif;
font-style: italic;
font-size: 11px;
color: var(--d3-15-gold-dim);
letter-spacing: 1px;
z-index: 1;
}
.d3-15 .colophon-label {
font-family: 'EB Garamond', serif;
font-size: 9px;
letter-spacing: 4px;
opacity: 0.45;
margin-bottom: 18px;
}
.d3-15 .colophon-title {
font-family: 'Playfair Display', serif;
font-size: 18px;
font-weight: 700;
opacity: 0.9;
margin-bottom: 8px;
}
.d3-15 .colophon-sub {
font-family: 'Playfair Display', serif;
font-style: italic;
font-size: 12px;
opacity: 0.6;
margin-bottom: 22px;
}
.d3-15 .colophon-body {
font-family: 'EB Garamond', serif;
font-size: 11px;
line-height: 1.8;
opacity: 0.58;
}
.d3-15 .nav {
display: flex;
align-items: center;
gap: 18px;
margin-top: 26px;
position: relative;
z-index: 100;
}
.d3-15 .nav-btn {
display: flex;
align-items: center;
gap: 8px;
font-family: 'EB Garamond', serif;
font-size: 12px;
letter-spacing: 2px;
color: var(--d3-15-nav-ink);
background: none;
border: none;
cursor: pointer;
padding: 9px 18px;
border-radius: 4px;
transition: color 0.2s, background 0.2s;
}
.d3-15 .nav-btn:hover {
color: var(--d3-15-nav-ink-hot);
background: var(--d3-15-nav-hover);
}
.d3-15 .nav-btn:disabled {
opacity: 0.35;
cursor: default;
}
.d3-15 .page-counter {
font-family: 'EB Garamond', serif;
font-size: 12px;
color: var(--d3-15-counter);
letter-spacing: 2px;
min-width: 80px;
text-align: center;
}
.d3-15 .book-shadow {
position: absolute;
bottom: -18px;
left: 10%;
right: 10%;
height: 28px;
background: radial-gradient(ellipse at center, var(--d3-15-book-shadow) 0%, transparent 70%);
filter: blur(12px);
z-index: -1;
}
@media (max-width: 720px) {
.d3-15 .book {
width: 360px;
height: 280px;
}
.d3-15 .page,
.d3-15 .left-cover {
width: 180px;
height: 280px;
}
.d3-15 .page {
left: 180px;
}
.d3-15 .pg {
padding: 22px 18px;
}
.d3-15 .spread-title.sm {
font-size: 20px;
}
.d3-15 .spread-title.xs {
font-size: 18px;
}
.d3-15 .cover-title {
font-size: 26px;
}
}
@media (prefers-reduced-motion: reduce) {
.d3-15 .book-scene,
.d3-15 .page {
transition: none !important;
}
}@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;0,900;1,400;1,700&family=EB+Garamond:ital,wght@0,400;0,500;1,400;1,500&display=swap');
.d3-15 {
--d3-15-desk: #1c1410;
--d3-15-desk-wash: rgba(180,120,40,0.06);
--d3-15-spine-deep: rgba(0,0,0,0.40);
--d3-15-spine-soft: rgba(0,0,0,0.10);
--d3-15-page-shade: rgba(0,0,0,0.14);
--d3-15-cover-shade: rgba(0,0,0,0.20);
--d3-15-book-shadow: rgba(0,0,0,0.50);
--d3-15-paper-warm: #f5ece0;
--d3-15-paper-cool: #ede8f0;
--d3-15-paper-rose: #f5e8e8;
--d3-15-paper-sage: #e8f0e8;
--d3-15-paper-ink: #1a1510;
--d3-15-ink-warm: #2a1f14;
--d3-15-ink-cool: #2a2432;
--d3-15-ink-rose: #2a1418;
--d3-15-ink-sage: #1a2a1e;
--d3-15-ink-page: #e8d8c0;
--d3-15-folio-warm: #8a6a4a;
--d3-15-folio-cool: #7a6898;
--d3-15-folio-rose: #8a4050;
--d3-15-folio-sage: #4a7a50;
--d3-15-folio-ink: rgba(200,180,140,0.40);
--d3-15-rule-cool: #4a3868;
--d3-15-rule-rose: #8a3040;
--d3-15-quote-warm-line: #8a6a4a;
--d3-15-quote-warm-ink: #4a3424;
--d3-15-quote-sage-line: #3a6a40;
--d3-15-quote-sage-ink: #2a4a2e;
--d3-15-title-rose: #5a1828;
--d3-15-body-cool: #3a2a50;
--d3-15-body-rose: #3a1820;
--d3-15-body-sage: #2a3a26;
--d3-15-sage-img-1: #2a4a2e;
--d3-15-sage-img-2: #1a3020;
--d3-15-circle-line: rgba(120,200,120,0.40);
--d3-15-circle-fill: rgba(120,200,120,0.20);
--d3-15-cover-1: #1a0f08;
--d3-15-cover-2: #0e0a06;
--d3-15-cover-wash: rgba(180,120,40,0.12);
--d3-15-gold-dim: rgba(200,160,80,0.55);
--d3-15-gold-rule: rgba(200,160,80,0.35);
--d3-15-cover-ink: #f0e4cc;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: rgba(220,190,140,1);
--d3-15-nav-hover: rgba(200,170,120,0.08);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.05);
--d3-15-ui-line: rgba(200,170,120,0.28);
--d3-15-ui-ink: rgba(220,190,140,0.92);
width: 100%;
min-height: max(100vh, 600px);
display: block;
position: relative;
background: var(--d3-15-desk);
font-family: 'EB Garamond', Georgia, serif;
overflow: hidden;
perspective: 2400px;
box-sizing: border-box;
}
.d3-15 *,
.d3-15 *::before,
.d3-15 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.d3-15:has(.d3-15__theme-input:checked) {
--d3-15-desk: #ded0bb;
--d3-15-desk-wash: rgba(120,80,20,0.10);
--d3-15-spine-deep: rgba(58,38,16,0.34);
--d3-15-spine-soft: rgba(58,38,16,0.08);
--d3-15-page-shade: rgba(78,54,24,0.13);
--d3-15-cover-shade: rgba(70,46,20,0.18);
--d3-15-book-shadow: rgba(72,50,22,0.30);
--d3-15-paper-warm: #fdf7ec;
--d3-15-paper-cool: #f4f0fa;
--d3-15-paper-rose: #fdefef;
--d3-15-paper-sage: #eef8ef;
--d3-15-paper-ink: #241b13;
--d3-15-ink-warm: #241a10;
--d3-15-ink-cool: #241d2e;
--d3-15-ink-rose: #2a1014;
--d3-15-ink-sage: #14261a;
--d3-15-ink-page: #f2e5ce;
--d3-15-folio-warm: #6d4f2f;
--d3-15-folio-cool: #5d4a80;
--d3-15-folio-rose: #77303f;
--d3-15-folio-sage: #35603f;
--d3-15-folio-ink: rgba(216,198,160,0.55);
--d3-15-rule-cool: #3d2b5c;
--d3-15-rule-rose: #77242f;
--d3-15-quote-warm-line: #7a5836;
--d3-15-quote-warm-ink: #3b2a17;
--d3-15-quote-sage-line: #2b5733;
--d3-15-quote-sage-ink: #1e3d22;
--d3-15-title-rose: #58101f;
--d3-15-body-cool: #2f2044;
--d3-15-body-rose: #34121a;
--d3-15-body-sage: #22321d;
--d3-15-sage-img-1: #23482a;
--d3-15-sage-img-2: #12301b;
--d3-15-circle-line: rgba(184,255,196,0.45);
--d3-15-circle-fill: rgba(184,255,196,0.22);
--d3-15-cover-1: #241408;
--d3-15-cover-2: #150c05;
--d3-15-cover-wash: rgba(220,160,60,0.16);
--d3-15-gold-dim: rgba(228,188,106,0.72);
--d3-15-gold-rule: rgba(228,188,106,0.45);
--d3-15-cover-ink: #f8eed8;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: #2a1e08;
--d3-15-nav-hover: rgba(90,62,20,0.10);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.60);
--d3-15-ui-line: rgba(70,48,22,0.22);
--d3-15-ui-ink: #3a2a12;
}
@media (prefers-color-scheme: light) {
.d3-15:has(.d3-15__theme-input:checked) {
--d3-15-desk: #ded0bb;
--d3-15-desk-wash: rgba(120,80,20,0.10);
--d3-15-spine-deep: rgba(58,38,16,0.34);
--d3-15-spine-soft: rgba(58,38,16,0.08);
--d3-15-page-shade: rgba(78,54,24,0.13);
--d3-15-cover-shade: rgba(70,46,20,0.18);
--d3-15-book-shadow: rgba(72,50,22,0.30);
--d3-15-paper-warm: #fdf7ec;
--d3-15-paper-cool: #f4f0fa;
--d3-15-paper-rose: #fdefef;
--d3-15-paper-sage: #eef8ef;
--d3-15-paper-ink: #241b13;
--d3-15-ink-warm: #241a10;
--d3-15-ink-cool: #241d2e;
--d3-15-ink-rose: #2a1014;
--d3-15-ink-sage: #14261a;
--d3-15-ink-page: #f2e5ce;
--d3-15-folio-warm: #6d4f2f;
--d3-15-folio-cool: #5d4a80;
--d3-15-folio-rose: #77303f;
--d3-15-folio-sage: #35603f;
--d3-15-folio-ink: rgba(216,198,160,0.55);
--d3-15-rule-cool: #3d2b5c;
--d3-15-rule-rose: #77242f;
--d3-15-quote-warm-line: #7a5836;
--d3-15-quote-warm-ink: #3b2a17;
--d3-15-quote-sage-line: #2b5733;
--d3-15-quote-sage-ink: #1e3d22;
--d3-15-title-rose: #58101f;
--d3-15-body-cool: #2f2044;
--d3-15-body-rose: #34121a;
--d3-15-body-sage: #22321d;
--d3-15-sage-img-1: #23482a;
--d3-15-sage-img-2: #12301b;
--d3-15-circle-line: rgba(184,255,196,0.45);
--d3-15-circle-fill: rgba(184,255,196,0.22);
--d3-15-cover-1: #241408;
--d3-15-cover-2: #150c05;
--d3-15-cover-wash: rgba(220,160,60,0.16);
--d3-15-gold-dim: rgba(228,188,106,0.72);
--d3-15-gold-rule: rgba(228,188,106,0.45);
--d3-15-cover-ink: #f8eed8;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: #2a1e08;
--d3-15-nav-hover: rgba(90,62,20,0.10);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.60);
--d3-15-ui-line: rgba(70,48,22,0.22);
--d3-15-ui-ink: #3a2a12;
}
}
@media (prefers-color-scheme: dark) {
.d3-15:has(.d3-15__theme-input:checked) {
--d3-15-desk: #ded0bb;
--d3-15-desk-wash: rgba(120,80,20,0.10);
--d3-15-spine-deep: rgba(58,38,16,0.34);
--d3-15-spine-soft: rgba(58,38,16,0.08);
--d3-15-page-shade: rgba(78,54,24,0.13);
--d3-15-cover-shade: rgba(70,46,20,0.18);
--d3-15-book-shadow: rgba(72,50,22,0.30);
--d3-15-paper-warm: #fdf7ec;
--d3-15-paper-cool: #f4f0fa;
--d3-15-paper-rose: #fdefef;
--d3-15-paper-sage: #eef8ef;
--d3-15-paper-ink: #241b13;
--d3-15-ink-warm: #241a10;
--d3-15-ink-cool: #241d2e;
--d3-15-ink-rose: #2a1014;
--d3-15-ink-sage: #14261a;
--d3-15-ink-page: #f2e5ce;
--d3-15-folio-warm: #6d4f2f;
--d3-15-folio-cool: #5d4a80;
--d3-15-folio-rose: #77303f;
--d3-15-folio-sage: #35603f;
--d3-15-folio-ink: rgba(216,198,160,0.55);
--d3-15-rule-cool: #3d2b5c;
--d3-15-rule-rose: #77242f;
--d3-15-quote-warm-line: #7a5836;
--d3-15-quote-warm-ink: #3b2a17;
--d3-15-quote-sage-line: #2b5733;
--d3-15-quote-sage-ink: #1e3d22;
--d3-15-title-rose: #58101f;
--d3-15-body-cool: #2f2044;
--d3-15-body-rose: #34121a;
--d3-15-body-sage: #22321d;
--d3-15-sage-img-1: #23482a;
--d3-15-sage-img-2: #12301b;
--d3-15-circle-line: rgba(184,255,196,0.45);
--d3-15-circle-fill: rgba(184,255,196,0.22);
--d3-15-cover-1: #241408;
--d3-15-cover-2: #150c05;
--d3-15-cover-wash: rgba(220,160,60,0.16);
--d3-15-gold-dim: rgba(228,188,106,0.72);
--d3-15-gold-rule: rgba(228,188,106,0.45);
--d3-15-cover-ink: #f8eed8;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: #2a1e08;
--d3-15-nav-hover: rgba(90,62,20,0.10);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.60);
--d3-15-ui-line: rgba(70,48,22,0.22);
--d3-15-ui-ink: #3a2a12;
}
}
.d3-15 .d3-15__theme {
position: absolute;
inset-block-start: clamp(0.75rem, 3vw, 1.5rem);
inset-inline-end: clamp(0.75rem, 3vw, 1.5rem);
z-index: 30;
}
.d3-15 .d3-15__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
z-index: 1;
}
.d3-15 .d3-15__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--d3-15-ui-line);
border-radius: 12px;
background: var(--d3-15-ui-face);
color: var(--d3-15-ui-ink);
pointer-events: none;
}
.d3-15 .d3-15__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.d3-15 .d3-15__theme-input:focus-visible + .d3-15__theme-label {
outline: 2px solid currentColor;
outline-offset: 3px;
}
.d3-15 .card {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 28px 16px;
}
.d3-15 .card::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse 60% 50% at 50% 40%, var(--d3-15-desk-wash) 0%, transparent 70%);
pointer-events: none;
}
.d3-15 .book-scene {
transform-style: preserve-3d;
transform: rotateX(5deg) rotateY(-4deg);
transition: transform 0.6s ease;
position: relative;
z-index: 1;
}
.d3-15 .book-scene:hover {
transform: rotateX(4deg) rotateY(-2deg);
}
.d3-15 .book {
width: 560px;
height: 360px;
position: relative;
transform-style: preserve-3d;
}
.d3-15 .book::before {
content: '';
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 12px;
transform: translateX(-50%) translateZ(1px);
background: linear-gradient(to right, var(--d3-15-spine-deep) 0%, var(--d3-15-spine-soft) 30%, var(--d3-15-spine-soft) 70%, var(--d3-15-spine-deep) 100%);
z-index: 20;
pointer-events: none;
}
.d3-15 .page {
position: absolute;
width: 280px;
height: 360px;
top: 0;
left: 280px;
transform-origin: left center;
transform-style: preserve-3d;
transition: transform 1.1s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.d3-15 .page.flipped {
transform: rotateY(-180deg);
}
.d3-15 .page-front,
.d3-15 .page-back {
position: absolute;
inset: 0;
backface-visibility: hidden;
overflow: hidden;
}
.d3-15 .page-back {
transform: rotateY(180deg);
}
.d3-15 .left-cover {
position: absolute;
left: 0;
top: 0;
width: 280px;
height: 360px;
z-index: 0;
border-radius: 4px 0 0 4px;
overflow: hidden;
}
.d3-15 .pg {
width: 100%;
height: 100%;
padding: 30px 26px;
display: flex;
flex-direction: column;
position: relative;
}
.d3-15 .pg-warm {
color: var(--d3-15-ink-warm);
}
.d3-15 .pg-cool {
color: var(--d3-15-ink-cool);
}
.d3-15 .pg-rose {
color: var(--d3-15-ink-rose);
}
.d3-15 .pg-sage {
color: var(--d3-15-ink-sage);
}
.d3-15 .pg-ink {
color: var(--d3-15-ink-page);
}
.d3-15 .pg-num {
font-family: 'EB Garamond', serif;
font-size: 10px;
letter-spacing: 2px;
position: absolute;
bottom: 16px;
}
.d3-15 .pg-num.right {
right: 22px;
color: var(--d3-15-folio-warm);
}
.d3-15 .pg-num.left {
left: 22px;
color: var(--d3-15-folio-cool);
}
.d3-15 .pg-num.rose {
color: var(--d3-15-folio-rose);
}
.d3-15 .pg-num.sage {
color: var(--d3-15-folio-sage);
}
.d3-15 .pg-num.ink {
color: var(--d3-15-folio-ink);
}
.d3-15 .page-front::after,
.d3-15 .page-back::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
width: 40px;
pointer-events: none;
}
.d3-15 .page-front::after {
right: 0;
background: linear-gradient(to right, transparent, var(--d3-15-page-shade));
}
.d3-15 .page-back::after {
left: 0;
background: linear-gradient(to left, transparent, var(--d3-15-page-shade));
}
.d3-15 .left-cover::after {
content: '';
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 30px;
background: linear-gradient(to right, transparent, var(--d3-15-cover-shade));
}
.d3-15 .paper-warm {
background: var(--d3-15-paper-warm);
}
.d3-15 .paper-cool {
background: var(--d3-15-paper-cool);
}
.d3-15 .paper-rose {
background: var(--d3-15-paper-rose);
}
.d3-15 .paper-sage {
background: var(--d3-15-paper-sage);
}
.d3-15 .paper-ink {
background: var(--d3-15-paper-ink);
}
.d3-15 .spread-label {
font-family: 'EB Garamond', serif;
font-size: 9px;
letter-spacing: 4px;
text-transform: uppercase;
opacity: 0.72;
margin-bottom: 16px;
}
.d3-15 .spread-title {
font-family: 'Playfair Display', serif;
font-weight: 900;
line-height: 1.1;
margin-bottom: 14px;
}
.d3-15 .spread-title.sm {
font-size: 26px;
}
.d3-15 .spread-title.xs {
font-size: 22px;
}
.d3-15 .spread-title.rose {
color: var(--d3-15-title-rose);
}
.d3-15 .spread-body {
font-family: 'EB Garamond', serif;
font-size: 13px;
line-height: 1.78;
opacity: 0.78;
}
.d3-15 .spread-body.mt {
margin-top: 10px;
}
.d3-15 .spread-body.alt {
color: var(--d3-15-body-cool);
}
.d3-15 .spread-body.rose-body {
color: var(--d3-15-body-rose);
}
.d3-15 .spread-body.sage-body {
color: var(--d3-15-body-sage);
}
.d3-15 .spread-pullquote {
font-family: 'Playfair Display', serif;
font-style: italic;
font-size: 16px;
line-height: 1.5;
border-left: 2px solid currentColor;
padding-left: 14px;
margin: 16px 0;
opacity: 0.85;
}
.d3-15 .spread-pullquote.warm {
border-color: var(--d3-15-quote-warm-line);
color: var(--d3-15-quote-warm-ink);
}
.d3-15 .spread-pullquote.sage {
border-color: var(--d3-15-quote-sage-line);
color: var(--d3-15-quote-sage-ink);
}
.d3-15 .spread-rule {
width: 40px;
height: 1px;
background: currentColor;
opacity: 0.3;
margin: 14px 0;
}
.d3-15 .spread-rule.alt {
background: var(--d3-15-rule-cool);
}
.d3-15 .spread-rule.rose {
background: var(--d3-15-rule-rose);
}
.d3-15 .spread-img {
width: 100%;
height: 100px;
border-radius: 4px;
margin: 14px 0;
overflow: hidden;
flex-shrink: 0;
}
.d3-15 .sage-img {
height: 72px;
background: linear-gradient(135deg, var(--d3-15-sage-img-1), var(--d3-15-sage-img-2));
display: flex;
align-items: center;
justify-content: center;
}
.d3-15 .circle-outer {
width: 52px;
height: 52px;
border: 2px solid var(--d3-15-circle-line);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.d3-15 .circle-inner {
width: 32px;
height: 32px;
background: var(--d3-15-circle-fill);
border-radius: 50%;
}
.d3-15 .spread-big-num {
font-family: 'Playfair Display', serif;
font-size: 80px;
font-weight: 900;
line-height: 0.9;
opacity: 0.07;
position: absolute;
right: 16px;
bottom: 30px;
}
.d3-15 .cover-art {
width: 100%;
height: 100%;
background: linear-gradient(148deg, var(--d3-15-cover-1), var(--d3-15-cover-2));
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 26px;
border-radius: 4px 0 0 4px;
position: relative;
overflow: hidden;
}
.d3-15 .cover-art::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse 70% 60% at 50% 40%, var(--d3-15-cover-wash) 0%, transparent 70%);
}
.d3-15 .cover-eyebrow {
font-family: 'EB Garamond', serif;
font-size: 9px;
letter-spacing: 5px;
color: var(--d3-15-gold-dim);
margin-bottom: 26px;
z-index: 1;
}
.d3-15 .cover-rule {
width: 56px;
height: 1px;
background: var(--d3-15-gold-rule);
margin: 0 0 26px;
z-index: 1;
}
.d3-15 .cover-title {
font-family: 'Playfair Display', serif;
font-size: 34px;
font-weight: 900;
color: var(--d3-15-cover-ink);
text-align: center;
line-height: 1.1;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.d3-15 .cover-vol {
font-style: italic;
font-weight: 400;
font-size: 20px;
opacity: 0.7;
margin-top: 6px;
}
.d3-15 .cover-rule:nth-of-type(2),
.d3-15 .cover-title + .cover-rule {
margin: 26px 0;
}
.d3-15 .cover-sub {
font-family: 'EB Garamond', serif;
font-style: italic;
font-size: 11px;
color: var(--d3-15-gold-dim);
letter-spacing: 1px;
z-index: 1;
}
.d3-15 .colophon-label {
font-family: 'EB Garamond', serif;
font-size: 9px;
letter-spacing: 4px;
opacity: 0.45;
margin-bottom: 18px;
}
.d3-15 .colophon-title {
font-family: 'Playfair Display', serif;
font-size: 18px;
font-weight: 700;
opacity: 0.9;
margin-bottom: 8px;
}
.d3-15 .colophon-sub {
font-family: 'Playfair Display', serif;
font-style: italic;
font-size: 12px;
opacity: 0.6;
margin-bottom: 22px;
}
.d3-15 .colophon-body {
font-family: 'EB Garamond', serif;
font-size: 11px;
line-height: 1.8;
opacity: 0.58;
}
.d3-15 .nav {
display: flex;
align-items: center;
gap: 18px;
margin-top: 26px;
position: relative;
z-index: 100;
}
.d3-15 .nav-btn {
display: flex;
align-items: center;
gap: 8px;
font-family: 'EB Garamond', serif;
font-size: 12px;
letter-spacing: 2px;
color: var(--d3-15-nav-ink);
background: none;
border: none;
cursor: pointer;
padding: 9px 18px;
border-radius: 4px;
transition: color 0.2s, background 0.2s;
}
.d3-15 .nav-btn:hover {
color: var(--d3-15-nav-ink-hot);
background: var(--d3-15-nav-hover);
}
.d3-15 .nav-btn:disabled {
opacity: 0.35;
cursor: default;
}
.d3-15 .page-counter {
font-family: 'EB Garamond', serif;
font-size: 12px;
color: var(--d3-15-counter);
letter-spacing: 2px;
min-width: 80px;
text-align: center;
}
.d3-15 .book-shadow {
position: absolute;
bottom: -18px;
left: 10%;
right: 10%;
height: 28px;
background: radial-gradient(ellipse at center, var(--d3-15-book-shadow) 0%, transparent 70%);
filter: blur(12px);
z-index: -1;
}
@media (max-width: 720px) {
.d3-15 .book {
width: 360px;
height: 280px;
}
.d3-15 .page,
.d3-15 .left-cover {
width: 180px;
height: 280px;
}
.d3-15 .page {
left: 180px;
}
.d3-15 .pg {
padding: 22px 18px;
}
.d3-15 .spread-title.sm {
font-size: 20px;
}
.d3-15 .spread-title.xs {
font-size: 18px;
}
.d3-15 .cover-title {
font-size: 26px;
}
}
@media (prefers-reduced-motion: reduce) {
.d3-15 .book-scene,
.d3-15 .page {
transition: none !important;
}
}(() => {
const root = document.querySelector('.d3-15');
if (!root) return;
const book = root.querySelector('[data-d3-15-book]');
const prevBtn = root.querySelector('[data-d3-15-prev]');
const nextBtn = root.querySelector('[data-d3-15-next]');
const counter = root.querySelector('[data-d3-15-counter]');
if (!book || !prevBtn || !nextBtn || !counter) return;
const pages = Array.from(root.querySelectorAll('.page[data-page]'));
const TOTAL_SPREADS = pages.length + 1;
let currentSpread = 0;
function update() {
pages.forEach((page, i) => {
page.classList.toggle('flipped', i < currentSpread);
page.style.zIndex = i < currentSpread ? i + 1 : pages.length - i + 4;
});
counter.textContent = 'Spread ' + (currentSpread + 1) + ' of ' + TOTAL_SPREADS;
prevBtn.disabled = currentSpread === 0;
nextBtn.disabled = currentSpread === TOTAL_SPREADS - 1;
}
prevBtn.addEventListener('click', () => { if (currentSpread > 0) { currentSpread--; update(); } });
nextBtn.addEventListener('click', () => { if (currentSpread < TOTAL_SPREADS - 1) { currentSpread++; update(); } });
book.addEventListener('click', e => {
if (e.target.closest('.nav-btn')) return;
const rect = book.getBoundingClientRect();
if (e.clientX > rect.left + rect.width / 2) {
if (currentSpread < TOTAL_SPREADS - 1) { currentSpread++; update(); }
} else {
if (currentSpread > 0) { currentSpread--; update(); }
}
});
let pointerInside = false;
root.addEventListener('mouseenter', () => { pointerInside = true; });
root.addEventListener('mouseleave', () => { pointerInside = false; });
document.addEventListener('keydown', e => {
if (!pointerInside) return;
if (e.key === 'ArrowRight') { nextBtn.click(); e.preventDefault(); }
if (e.key === 'ArrowLeft') { prevBtn.click(); e.preventDefault(); }
});
update();
})();(() => {
const root = document.querySelector('.d3-15');
if (!root) return;
const book = root.querySelector('[data-d3-15-book]');
const prevBtn = root.querySelector('[data-d3-15-prev]');
const nextBtn = root.querySelector('[data-d3-15-next]');
const counter = root.querySelector('[data-d3-15-counter]');
if (!book || !prevBtn || !nextBtn || !counter) return;
const pages = Array.from(root.querySelectorAll('.page[data-page]'));
const TOTAL_SPREADS = pages.length + 1;
let currentSpread = 0;
function update() {
pages.forEach((page, i) => {
page.classList.toggle('flipped', i < currentSpread);
page.style.zIndex = i < currentSpread ? i + 1 : pages.length - i + 4;
});
counter.textContent = 'Spread ' + (currentSpread + 1) + ' of ' + TOTAL_SPREADS;
prevBtn.disabled = currentSpread === 0;
nextBtn.disabled = currentSpread === TOTAL_SPREADS - 1;
}
prevBtn.addEventListener('click', () => { if (currentSpread > 0) { currentSpread--; update(); } });
nextBtn.addEventListener('click', () => { if (currentSpread < TOTAL_SPREADS - 1) { currentSpread++; update(); } });
book.addEventListener('click', e => {
if (e.target.closest('.nav-btn')) return;
const rect = book.getBoundingClientRect();
if (e.clientX > rect.left + rect.width / 2) {
if (currentSpread < TOTAL_SPREADS - 1) { currentSpread++; update(); }
} else {
if (currentSpread > 0) { currentSpread--; update(); }
}
});
let pointerInside = false;
root.addEventListener('mouseenter', () => { pointerInside = true; });
root.addEventListener('mouseleave', () => { pointerInside = false; });
document.addEventListener('keydown', e => {
if (!pointerInside) return;
if (e.key === 'ArrowRight') { nextBtn.click(); e.preventDefault(); }
if (e.key === 'ArrowLeft') { prevBtn.click(); e.preventDefault(); }
});
update();
})();Here's a working CSS 3D Effect from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Page Turn Flipbook with Hinged Rotation
Source: https://codefronts.com/design-styles/css-3d/page-turn-flipbook/
A four-spread journal that turns on a spine hinge: transform-origin at the gutter turns rotateY into a page turn, each leaf carries a front and a back, and the script's real job is the stacking order — z-index recomputed on every turn so turned leaves fall behind the ones still to come. Prev and next buttons, click-a-half, and arrow keys all drive the same single index.
## HTML
```html
<section class="d3-15" aria-label="Page-turn flipbook demo">
<div class="d3-15__theme">
<input class="d3-15__theme-input" type="checkbox" id="d3-15-theme">
<label class="d3-15__theme-label" for="d3-15-theme" aria-label="Toggle light and dark theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 1 0 17z" fill="currentColor"/></svg>
</label>
</div>
<div class="card">
<div class="book-scene">
<div class="book" data-d3-15-book>
<div class="book-shadow" aria-hidden="true"></div>
<div class="left-cover" data-d3-15-cover>
<div class="cover-art">
<div class="cover-eyebrow">FORMA STUDIO</div>
<div class="cover-rule"></div>
<div class="cover-title">FORMA<span class="cover-vol">Vol. VI</span></div>
<div class="cover-rule"></div>
<div class="cover-sub">Design · Architecture · Thought</div>
</div>
</div>
<div class="page" data-page="0">
<div class="page-front paper-warm">
<div class="pg pg-warm">
<div class="spread-label">Chapter One</div>
<div class="spread-title sm">The Weight<br />of Silence</div>
<div class="spread-rule"></div>
<p class="spread-body">There is a moment before speech when the world holds its breath. Architecture inhabits this pause — not the buildings themselves, but the spaces they carve from air.</p>
<p class="spread-body mt">The corridor narrows, draws the eye forward. Light slants from an unseen source. To walk through is to be composed by the building.</p>
<span class="pg-num right">1</span>
<span class="spread-big-num">I</span>
</div>
</div>
<div class="page-back paper-cool">
<div class="pg pg-cool">
<div class="spread-label">Essay II</div>
<div class="spread-title xs">Form<br />Without<br />Apology</div>
<div class="spread-rule alt"></div>
<p class="spread-body alt">To design is to make a claim about the world as it should be — not as it is. The blueprint precedes the building, and in that gap lives everything called imagination.</p>
<span class="pg-num left">2</span>
</div>
</div>
</div>
<div class="page" data-page="1">
<div class="page-front paper-sage">
<div class="pg pg-sage">
<div class="spread-label">Visual Field</div>
<div class="spread-img sage-img">
<div class="circle-outer">
<div class="circle-inner"></div>
</div>
</div>
<div class="spread-pullquote sage">"Growth is not linear. It is fractal — repeating at every scale."</div>
<p class="spread-body sage-body">The forest does not plan its canopy. Each tree simply grows toward light.</p>
<span class="pg-num right sage">3</span>
</div>
</div>
<div class="page-back paper-rose">
<div class="pg pg-rose">
<div class="spread-label">Interlude</div>
<div class="spread-title sm rose">Red<br />Notation</div>
<div class="spread-rule rose"></div>
<p class="spread-body rose-body">Color arrives before language. Red is felt in the chest before named by the tongue. The ancient painters knew this — their ochre hands pressed flat on cave walls were not decoration. They were declaration.</p>
<span class="pg-num left rose">4</span>
</div>
</div>
</div>
<div class="page" data-page="2">
<div class="page-front paper-warm">
<div class="pg pg-warm">
<div class="spread-label">Final Study</div>
<div class="spread-title sm">Endings<br />as Form</div>
<div class="spread-rule"></div>
<p class="spread-body">Every designed thing must eventually end — the page turn, the last note, the exit from the building. The best designs make their endings feel inevitable, not arbitrary.</p>
<div class="spread-pullquote warm">"The final line is the first thing written."</div>
<span class="pg-num right">5</span>
</div>
</div>
<div class="page-back paper-ink">
<div class="pg pg-ink">
<div class="colophon-label">COLOPHON</div>
<div class="colophon-title">FORMA</div>
<div class="colophon-sub">A Journal of Design Thought</div>
<p class="colophon-body">Issue VI · Printed on FSC-certified stock · Typography set in Playfair Display and EB Garamond · All rights reserved ©</p>
<span class="pg-num left ink">6</span>
</div>
</div>
</div>
</div>
<div class="nav">
<button class="nav-btn" type="button" data-d3-15-prev disabled>← Prev</button>
<span class="page-counter" data-d3-15-counter>Spread 1 of 4</span>
<button class="nav-btn" type="button" data-d3-15-next>Next →</button>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;0,900;1,400;1,700&family=EB+Garamond:ital,wght@0,400;0,500;1,400;1,500&display=swap');
.d3-15 {
--d3-15-desk: #1c1410;
--d3-15-desk-wash: rgba(180,120,40,0.06);
--d3-15-spine-deep: rgba(0,0,0,0.40);
--d3-15-spine-soft: rgba(0,0,0,0.10);
--d3-15-page-shade: rgba(0,0,0,0.14);
--d3-15-cover-shade: rgba(0,0,0,0.20);
--d3-15-book-shadow: rgba(0,0,0,0.50);
--d3-15-paper-warm: #f5ece0;
--d3-15-paper-cool: #ede8f0;
--d3-15-paper-rose: #f5e8e8;
--d3-15-paper-sage: #e8f0e8;
--d3-15-paper-ink: #1a1510;
--d3-15-ink-warm: #2a1f14;
--d3-15-ink-cool: #2a2432;
--d3-15-ink-rose: #2a1418;
--d3-15-ink-sage: #1a2a1e;
--d3-15-ink-page: #e8d8c0;
--d3-15-folio-warm: #8a6a4a;
--d3-15-folio-cool: #7a6898;
--d3-15-folio-rose: #8a4050;
--d3-15-folio-sage: #4a7a50;
--d3-15-folio-ink: rgba(200,180,140,0.40);
--d3-15-rule-cool: #4a3868;
--d3-15-rule-rose: #8a3040;
--d3-15-quote-warm-line: #8a6a4a;
--d3-15-quote-warm-ink: #4a3424;
--d3-15-quote-sage-line: #3a6a40;
--d3-15-quote-sage-ink: #2a4a2e;
--d3-15-title-rose: #5a1828;
--d3-15-body-cool: #3a2a50;
--d3-15-body-rose: #3a1820;
--d3-15-body-sage: #2a3a26;
--d3-15-sage-img-1: #2a4a2e;
--d3-15-sage-img-2: #1a3020;
--d3-15-circle-line: rgba(120,200,120,0.40);
--d3-15-circle-fill: rgba(120,200,120,0.20);
--d3-15-cover-1: #1a0f08;
--d3-15-cover-2: #0e0a06;
--d3-15-cover-wash: rgba(180,120,40,0.12);
--d3-15-gold-dim: rgba(200,160,80,0.55);
--d3-15-gold-rule: rgba(200,160,80,0.35);
--d3-15-cover-ink: #f0e4cc;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: rgba(220,190,140,1);
--d3-15-nav-hover: rgba(200,170,120,0.08);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.05);
--d3-15-ui-line: rgba(200,170,120,0.28);
--d3-15-ui-ink: rgba(220,190,140,0.92);
width: 100%;
min-height: max(100vh, 600px);
display: block;
position: relative;
background: var(--d3-15-desk);
font-family: 'EB Garamond', Georgia, serif;
overflow: hidden;
perspective: 2400px;
box-sizing: border-box;
}
.d3-15 *,
.d3-15 *::before,
.d3-15 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.d3-15:has(.d3-15__theme-input:checked) {
--d3-15-desk: #ded0bb;
--d3-15-desk-wash: rgba(120,80,20,0.10);
--d3-15-spine-deep: rgba(58,38,16,0.34);
--d3-15-spine-soft: rgba(58,38,16,0.08);
--d3-15-page-shade: rgba(78,54,24,0.13);
--d3-15-cover-shade: rgba(70,46,20,0.18);
--d3-15-book-shadow: rgba(72,50,22,0.30);
--d3-15-paper-warm: #fdf7ec;
--d3-15-paper-cool: #f4f0fa;
--d3-15-paper-rose: #fdefef;
--d3-15-paper-sage: #eef8ef;
--d3-15-paper-ink: #241b13;
--d3-15-ink-warm: #241a10;
--d3-15-ink-cool: #241d2e;
--d3-15-ink-rose: #2a1014;
--d3-15-ink-sage: #14261a;
--d3-15-ink-page: #f2e5ce;
--d3-15-folio-warm: #6d4f2f;
--d3-15-folio-cool: #5d4a80;
--d3-15-folio-rose: #77303f;
--d3-15-folio-sage: #35603f;
--d3-15-folio-ink: rgba(216,198,160,0.55);
--d3-15-rule-cool: #3d2b5c;
--d3-15-rule-rose: #77242f;
--d3-15-quote-warm-line: #7a5836;
--d3-15-quote-warm-ink: #3b2a17;
--d3-15-quote-sage-line: #2b5733;
--d3-15-quote-sage-ink: #1e3d22;
--d3-15-title-rose: #58101f;
--d3-15-body-cool: #2f2044;
--d3-15-body-rose: #34121a;
--d3-15-body-sage: #22321d;
--d3-15-sage-img-1: #23482a;
--d3-15-sage-img-2: #12301b;
--d3-15-circle-line: rgba(184,255,196,0.45);
--d3-15-circle-fill: rgba(184,255,196,0.22);
--d3-15-cover-1: #241408;
--d3-15-cover-2: #150c05;
--d3-15-cover-wash: rgba(220,160,60,0.16);
--d3-15-gold-dim: rgba(228,188,106,0.72);
--d3-15-gold-rule: rgba(228,188,106,0.45);
--d3-15-cover-ink: #f8eed8;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: #2a1e08;
--d3-15-nav-hover: rgba(90,62,20,0.10);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.60);
--d3-15-ui-line: rgba(70,48,22,0.22);
--d3-15-ui-ink: #3a2a12;
}
@media (prefers-color-scheme: light) {
.d3-15:has(.d3-15__theme-input:checked) {
--d3-15-desk: #ded0bb;
--d3-15-desk-wash: rgba(120,80,20,0.10);
--d3-15-spine-deep: rgba(58,38,16,0.34);
--d3-15-spine-soft: rgba(58,38,16,0.08);
--d3-15-page-shade: rgba(78,54,24,0.13);
--d3-15-cover-shade: rgba(70,46,20,0.18);
--d3-15-book-shadow: rgba(72,50,22,0.30);
--d3-15-paper-warm: #fdf7ec;
--d3-15-paper-cool: #f4f0fa;
--d3-15-paper-rose: #fdefef;
--d3-15-paper-sage: #eef8ef;
--d3-15-paper-ink: #241b13;
--d3-15-ink-warm: #241a10;
--d3-15-ink-cool: #241d2e;
--d3-15-ink-rose: #2a1014;
--d3-15-ink-sage: #14261a;
--d3-15-ink-page: #f2e5ce;
--d3-15-folio-warm: #6d4f2f;
--d3-15-folio-cool: #5d4a80;
--d3-15-folio-rose: #77303f;
--d3-15-folio-sage: #35603f;
--d3-15-folio-ink: rgba(216,198,160,0.55);
--d3-15-rule-cool: #3d2b5c;
--d3-15-rule-rose: #77242f;
--d3-15-quote-warm-line: #7a5836;
--d3-15-quote-warm-ink: #3b2a17;
--d3-15-quote-sage-line: #2b5733;
--d3-15-quote-sage-ink: #1e3d22;
--d3-15-title-rose: #58101f;
--d3-15-body-cool: #2f2044;
--d3-15-body-rose: #34121a;
--d3-15-body-sage: #22321d;
--d3-15-sage-img-1: #23482a;
--d3-15-sage-img-2: #12301b;
--d3-15-circle-line: rgba(184,255,196,0.45);
--d3-15-circle-fill: rgba(184,255,196,0.22);
--d3-15-cover-1: #241408;
--d3-15-cover-2: #150c05;
--d3-15-cover-wash: rgba(220,160,60,0.16);
--d3-15-gold-dim: rgba(228,188,106,0.72);
--d3-15-gold-rule: rgba(228,188,106,0.45);
--d3-15-cover-ink: #f8eed8;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: #2a1e08;
--d3-15-nav-hover: rgba(90,62,20,0.10);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.60);
--d3-15-ui-line: rgba(70,48,22,0.22);
--d3-15-ui-ink: #3a2a12;
}
}
@media (prefers-color-scheme: dark) {
.d3-15:has(.d3-15__theme-input:checked) {
--d3-15-desk: #ded0bb;
--d3-15-desk-wash: rgba(120,80,20,0.10);
--d3-15-spine-deep: rgba(58,38,16,0.34);
--d3-15-spine-soft: rgba(58,38,16,0.08);
--d3-15-page-shade: rgba(78,54,24,0.13);
--d3-15-cover-shade: rgba(70,46,20,0.18);
--d3-15-book-shadow: rgba(72,50,22,0.30);
--d3-15-paper-warm: #fdf7ec;
--d3-15-paper-cool: #f4f0fa;
--d3-15-paper-rose: #fdefef;
--d3-15-paper-sage: #eef8ef;
--d3-15-paper-ink: #241b13;
--d3-15-ink-warm: #241a10;
--d3-15-ink-cool: #241d2e;
--d3-15-ink-rose: #2a1014;
--d3-15-ink-sage: #14261a;
--d3-15-ink-page: #f2e5ce;
--d3-15-folio-warm: #6d4f2f;
--d3-15-folio-cool: #5d4a80;
--d3-15-folio-rose: #77303f;
--d3-15-folio-sage: #35603f;
--d3-15-folio-ink: rgba(216,198,160,0.55);
--d3-15-rule-cool: #3d2b5c;
--d3-15-rule-rose: #77242f;
--d3-15-quote-warm-line: #7a5836;
--d3-15-quote-warm-ink: #3b2a17;
--d3-15-quote-sage-line: #2b5733;
--d3-15-quote-sage-ink: #1e3d22;
--d3-15-title-rose: #58101f;
--d3-15-body-cool: #2f2044;
--d3-15-body-rose: #34121a;
--d3-15-body-sage: #22321d;
--d3-15-sage-img-1: #23482a;
--d3-15-sage-img-2: #12301b;
--d3-15-circle-line: rgba(184,255,196,0.45);
--d3-15-circle-fill: rgba(184,255,196,0.22);
--d3-15-cover-1: #241408;
--d3-15-cover-2: #150c05;
--d3-15-cover-wash: rgba(220,160,60,0.16);
--d3-15-gold-dim: rgba(228,188,106,0.72);
--d3-15-gold-rule: rgba(228,188,106,0.45);
--d3-15-cover-ink: #f8eed8;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: #2a1e08;
--d3-15-nav-hover: rgba(90,62,20,0.10);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.60);
--d3-15-ui-line: rgba(70,48,22,0.22);
--d3-15-ui-ink: #3a2a12;
}
}
.d3-15 .d3-15__theme {
position: absolute;
inset-block-start: clamp(0.75rem, 3vw, 1.5rem);
inset-inline-end: clamp(0.75rem, 3vw, 1.5rem);
z-index: 30;
}
.d3-15 .d3-15__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
z-index: 1;
}
.d3-15 .d3-15__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--d3-15-ui-line);
border-radius: 12px;
background: var(--d3-15-ui-face);
color: var(--d3-15-ui-ink);
pointer-events: none;
}
.d3-15 .d3-15__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.d3-15 .d3-15__theme-input:focus-visible + .d3-15__theme-label {
outline: 2px solid currentColor;
outline-offset: 3px;
}
.d3-15 .card {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 28px 16px;
}
.d3-15 .card::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse 60% 50% at 50% 40%, var(--d3-15-desk-wash) 0%, transparent 70%);
pointer-events: none;
}
.d3-15 .book-scene {
transform-style: preserve-3d;
transform: rotateX(5deg) rotateY(-4deg);
transition: transform 0.6s ease;
position: relative;
z-index: 1;
}
.d3-15 .book-scene:hover {
transform: rotateX(4deg) rotateY(-2deg);
}
.d3-15 .book {
width: 560px;
height: 360px;
position: relative;
transform-style: preserve-3d;
}
.d3-15 .book::before {
content: '';
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 12px;
transform: translateX(-50%) translateZ(1px);
background: linear-gradient(to right, var(--d3-15-spine-deep) 0%, var(--d3-15-spine-soft) 30%, var(--d3-15-spine-soft) 70%, var(--d3-15-spine-deep) 100%);
z-index: 20;
pointer-events: none;
}
.d3-15 .page {
position: absolute;
width: 280px;
height: 360px;
top: 0;
left: 280px;
transform-origin: left center;
transform-style: preserve-3d;
transition: transform 1.1s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.d3-15 .page.flipped {
transform: rotateY(-180deg);
}
.d3-15 .page-front,
.d3-15 .page-back {
position: absolute;
inset: 0;
backface-visibility: hidden;
overflow: hidden;
}
.d3-15 .page-back {
transform: rotateY(180deg);
}
.d3-15 .left-cover {
position: absolute;
left: 0;
top: 0;
width: 280px;
height: 360px;
z-index: 0;
border-radius: 4px 0 0 4px;
overflow: hidden;
}
.d3-15 .pg {
width: 100%;
height: 100%;
padding: 30px 26px;
display: flex;
flex-direction: column;
position: relative;
}
.d3-15 .pg-warm {
color: var(--d3-15-ink-warm);
}
.d3-15 .pg-cool {
color: var(--d3-15-ink-cool);
}
.d3-15 .pg-rose {
color: var(--d3-15-ink-rose);
}
.d3-15 .pg-sage {
color: var(--d3-15-ink-sage);
}
.d3-15 .pg-ink {
color: var(--d3-15-ink-page);
}
.d3-15 .pg-num {
font-family: 'EB Garamond', serif;
font-size: 10px;
letter-spacing: 2px;
position: absolute;
bottom: 16px;
}
.d3-15 .pg-num.right {
right: 22px;
color: var(--d3-15-folio-warm);
}
.d3-15 .pg-num.left {
left: 22px;
color: var(--d3-15-folio-cool);
}
.d3-15 .pg-num.rose {
color: var(--d3-15-folio-rose);
}
.d3-15 .pg-num.sage {
color: var(--d3-15-folio-sage);
}
.d3-15 .pg-num.ink {
color: var(--d3-15-folio-ink);
}
.d3-15 .page-front::after,
.d3-15 .page-back::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
width: 40px;
pointer-events: none;
}
.d3-15 .page-front::after {
right: 0;
background: linear-gradient(to right, transparent, var(--d3-15-page-shade));
}
.d3-15 .page-back::after {
left: 0;
background: linear-gradient(to left, transparent, var(--d3-15-page-shade));
}
.d3-15 .left-cover::after {
content: '';
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 30px;
background: linear-gradient(to right, transparent, var(--d3-15-cover-shade));
}
.d3-15 .paper-warm {
background: var(--d3-15-paper-warm);
}
.d3-15 .paper-cool {
background: var(--d3-15-paper-cool);
}
.d3-15 .paper-rose {
background: var(--d3-15-paper-rose);
}
.d3-15 .paper-sage {
background: var(--d3-15-paper-sage);
}
.d3-15 .paper-ink {
background: var(--d3-15-paper-ink);
}
.d3-15 .spread-label {
font-family: 'EB Garamond', serif;
font-size: 9px;
letter-spacing: 4px;
text-transform: uppercase;
opacity: 0.72;
margin-bottom: 16px;
}
.d3-15 .spread-title {
font-family: 'Playfair Display', serif;
font-weight: 900;
line-height: 1.1;
margin-bottom: 14px;
}
.d3-15 .spread-title.sm {
font-size: 26px;
}
.d3-15 .spread-title.xs {
font-size: 22px;
}
.d3-15 .spread-title.rose {
color: var(--d3-15-title-rose);
}
.d3-15 .spread-body {
font-family: 'EB Garamond', serif;
font-size: 13px;
line-height: 1.78;
opacity: 0.78;
}
.d3-15 .spread-body.mt {
margin-top: 10px;
}
.d3-15 .spread-body.alt {
color: var(--d3-15-body-cool);
}
.d3-15 .spread-body.rose-body {
color: var(--d3-15-body-rose);
}
.d3-15 .spread-body.sage-body {
color: var(--d3-15-body-sage);
}
.d3-15 .spread-pullquote {
font-family: 'Playfair Display', serif;
font-style: italic;
font-size: 16px;
line-height: 1.5;
border-left: 2px solid currentColor;
padding-left: 14px;
margin: 16px 0;
opacity: 0.85;
}
.d3-15 .spread-pullquote.warm {
border-color: var(--d3-15-quote-warm-line);
color: var(--d3-15-quote-warm-ink);
}
.d3-15 .spread-pullquote.sage {
border-color: var(--d3-15-quote-sage-line);
color: var(--d3-15-quote-sage-ink);
}
.d3-15 .spread-rule {
width: 40px;
height: 1px;
background: currentColor;
opacity: 0.3;
margin: 14px 0;
}
.d3-15 .spread-rule.alt {
background: var(--d3-15-rule-cool);
}
.d3-15 .spread-rule.rose {
background: var(--d3-15-rule-rose);
}
.d3-15 .spread-img {
width: 100%;
height: 100px;
border-radius: 4px;
margin: 14px 0;
overflow: hidden;
flex-shrink: 0;
}
.d3-15 .sage-img {
height: 72px;
background: linear-gradient(135deg, var(--d3-15-sage-img-1), var(--d3-15-sage-img-2));
display: flex;
align-items: center;
justify-content: center;
}
.d3-15 .circle-outer {
width: 52px;
height: 52px;
border: 2px solid var(--d3-15-circle-line);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.d3-15 .circle-inner {
width: 32px;
height: 32px;
background: var(--d3-15-circle-fill);
border-radius: 50%;
}
.d3-15 .spread-big-num {
font-family: 'Playfair Display', serif;
font-size: 80px;
font-weight: 900;
line-height: 0.9;
opacity: 0.07;
position: absolute;
right: 16px;
bottom: 30px;
}
.d3-15 .cover-art {
width: 100%;
height: 100%;
background: linear-gradient(148deg, var(--d3-15-cover-1), var(--d3-15-cover-2));
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 26px;
border-radius: 4px 0 0 4px;
position: relative;
overflow: hidden;
}
.d3-15 .cover-art::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse 70% 60% at 50% 40%, var(--d3-15-cover-wash) 0%, transparent 70%);
}
.d3-15 .cover-eyebrow {
font-family: 'EB Garamond', serif;
font-size: 9px;
letter-spacing: 5px;
color: var(--d3-15-gold-dim);
margin-bottom: 26px;
z-index: 1;
}
.d3-15 .cover-rule {
width: 56px;
height: 1px;
background: var(--d3-15-gold-rule);
margin: 0 0 26px;
z-index: 1;
}
.d3-15 .cover-title {
font-family: 'Playfair Display', serif;
font-size: 34px;
font-weight: 900;
color: var(--d3-15-cover-ink);
text-align: center;
line-height: 1.1;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.d3-15 .cover-vol {
font-style: italic;
font-weight: 400;
font-size: 20px;
opacity: 0.7;
margin-top: 6px;
}
.d3-15 .cover-rule:nth-of-type(2),
.d3-15 .cover-title + .cover-rule {
margin: 26px 0;
}
.d3-15 .cover-sub {
font-family: 'EB Garamond', serif;
font-style: italic;
font-size: 11px;
color: var(--d3-15-gold-dim);
letter-spacing: 1px;
z-index: 1;
}
.d3-15 .colophon-label {
font-family: 'EB Garamond', serif;
font-size: 9px;
letter-spacing: 4px;
opacity: 0.45;
margin-bottom: 18px;
}
.d3-15 .colophon-title {
font-family: 'Playfair Display', serif;
font-size: 18px;
font-weight: 700;
opacity: 0.9;
margin-bottom: 8px;
}
.d3-15 .colophon-sub {
font-family: 'Playfair Display', serif;
font-style: italic;
font-size: 12px;
opacity: 0.6;
margin-bottom: 22px;
}
.d3-15 .colophon-body {
font-family: 'EB Garamond', serif;
font-size: 11px;
line-height: 1.8;
opacity: 0.58;
}
.d3-15 .nav {
display: flex;
align-items: center;
gap: 18px;
margin-top: 26px;
position: relative;
z-index: 100;
}
.d3-15 .nav-btn {
display: flex;
align-items: center;
gap: 8px;
font-family: 'EB Garamond', serif;
font-size: 12px;
letter-spacing: 2px;
color: var(--d3-15-nav-ink);
background: none;
border: none;
cursor: pointer;
padding: 9px 18px;
border-radius: 4px;
transition: color 0.2s, background 0.2s;
}
.d3-15 .nav-btn:hover {
color: var(--d3-15-nav-ink-hot);
background: var(--d3-15-nav-hover);
}
.d3-15 .nav-btn:disabled {
opacity: 0.35;
cursor: default;
}
.d3-15 .page-counter {
font-family: 'EB Garamond', serif;
font-size: 12px;
color: var(--d3-15-counter);
letter-spacing: 2px;
min-width: 80px;
text-align: center;
}
.d3-15 .book-shadow {
position: absolute;
bottom: -18px;
left: 10%;
right: 10%;
height: 28px;
background: radial-gradient(ellipse at center, var(--d3-15-book-shadow) 0%, transparent 70%);
filter: blur(12px);
z-index: -1;
}
@media (max-width: 720px) {
.d3-15 .book {
width: 360px;
height: 280px;
}
.d3-15 .page,
.d3-15 .left-cover {
width: 180px;
height: 280px;
}
.d3-15 .page {
left: 180px;
}
.d3-15 .pg {
padding: 22px 18px;
}
.d3-15 .spread-title.sm {
font-size: 20px;
}
.d3-15 .spread-title.xs {
font-size: 18px;
}
.d3-15 .cover-title {
font-size: 26px;
}
}
@media (prefers-reduced-motion: reduce) {
.d3-15 .book-scene,
.d3-15 .page {
transition: none !important;
}
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.d3-15');
if (!root) return;
const book = root.querySelector('[data-d3-15-book]');
const prevBtn = root.querySelector('[data-d3-15-prev]');
const nextBtn = root.querySelector('[data-d3-15-next]');
const counter = root.querySelector('[data-d3-15-counter]');
if (!book || !prevBtn || !nextBtn || !counter) return;
const pages = Array.from(root.querySelectorAll('.page[data-page]'));
const TOTAL_SPREADS = pages.length + 1;
let currentSpread = 0;
function update() {
pages.forEach((page, i) => {
page.classList.toggle('flipped', i < currentSpread);
page.style.zIndex = i < currentSpread ? i + 1 : pages.length - i + 4;
});
counter.textContent = 'Spread ' + (currentSpread + 1) + ' of ' + TOTAL_SPREADS;
prevBtn.disabled = currentSpread === 0;
nextBtn.disabled = currentSpread === TOTAL_SPREADS - 1;
}
prevBtn.addEventListener('click', () => { if (currentSpread > 0) { currentSpread--; update(); } });
nextBtn.addEventListener('click', () => { if (currentSpread < TOTAL_SPREADS - 1) { currentSpread++; update(); } });
book.addEventListener('click', e => {
if (e.target.closest('.nav-btn')) return;
const rect = book.getBoundingClientRect();
if (e.clientX > rect.left + rect.width / 2) {
if (currentSpread < TOTAL_SPREADS - 1) { currentSpread++; update(); }
} else {
if (currentSpread > 0) { currentSpread--; update(); }
}
});
let pointerInside = false;
root.addEventListener('mouseenter', () => { pointerInside = true; });
root.addEventListener('mouseleave', () => { pointerInside = false; });
document.addEventListener('keydown', e => {
if (!pointerInside) return;
if (e.key === 'ArrowRight') { nextBtn.click(); e.preventDefault(); }
if (e.key === 'ArrowLeft') { prevBtn.click(); e.preventDefault(); }
});
update();
})();
```Here's a working CSS 3D Effect from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Page Turn Flipbook with Hinged Rotation
Source: https://codefronts.com/design-styles/css-3d/page-turn-flipbook/
A four-spread journal that turns on a spine hinge: transform-origin at the gutter turns rotateY into a page turn, each leaf carries a front and a back, and the script's real job is the stacking order — z-index recomputed on every turn so turned leaves fall behind the ones still to come. Prev and next buttons, click-a-half, and arrow keys all drive the same single index.
## HTML
```html
<section class="d3-15" aria-label="Page-turn flipbook demo">
<div class="d3-15__theme">
<input class="d3-15__theme-input" type="checkbox" id="d3-15-theme">
<label class="d3-15__theme-label" for="d3-15-theme" aria-label="Toggle light and dark theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 3.5a8.5 8.5 0 0 1 0 17z" fill="currentColor"/></svg>
</label>
</div>
<div class="card">
<div class="book-scene">
<div class="book" data-d3-15-book>
<div class="book-shadow" aria-hidden="true"></div>
<div class="left-cover" data-d3-15-cover>
<div class="cover-art">
<div class="cover-eyebrow">FORMA STUDIO</div>
<div class="cover-rule"></div>
<div class="cover-title">FORMA<span class="cover-vol">Vol. VI</span></div>
<div class="cover-rule"></div>
<div class="cover-sub">Design · Architecture · Thought</div>
</div>
</div>
<div class="page" data-page="0">
<div class="page-front paper-warm">
<div class="pg pg-warm">
<div class="spread-label">Chapter One</div>
<div class="spread-title sm">The Weight<br />of Silence</div>
<div class="spread-rule"></div>
<p class="spread-body">There is a moment before speech when the world holds its breath. Architecture inhabits this pause — not the buildings themselves, but the spaces they carve from air.</p>
<p class="spread-body mt">The corridor narrows, draws the eye forward. Light slants from an unseen source. To walk through is to be composed by the building.</p>
<span class="pg-num right">1</span>
<span class="spread-big-num">I</span>
</div>
</div>
<div class="page-back paper-cool">
<div class="pg pg-cool">
<div class="spread-label">Essay II</div>
<div class="spread-title xs">Form<br />Without<br />Apology</div>
<div class="spread-rule alt"></div>
<p class="spread-body alt">To design is to make a claim about the world as it should be — not as it is. The blueprint precedes the building, and in that gap lives everything called imagination.</p>
<span class="pg-num left">2</span>
</div>
</div>
</div>
<div class="page" data-page="1">
<div class="page-front paper-sage">
<div class="pg pg-sage">
<div class="spread-label">Visual Field</div>
<div class="spread-img sage-img">
<div class="circle-outer">
<div class="circle-inner"></div>
</div>
</div>
<div class="spread-pullquote sage">"Growth is not linear. It is fractal — repeating at every scale."</div>
<p class="spread-body sage-body">The forest does not plan its canopy. Each tree simply grows toward light.</p>
<span class="pg-num right sage">3</span>
</div>
</div>
<div class="page-back paper-rose">
<div class="pg pg-rose">
<div class="spread-label">Interlude</div>
<div class="spread-title sm rose">Red<br />Notation</div>
<div class="spread-rule rose"></div>
<p class="spread-body rose-body">Color arrives before language. Red is felt in the chest before named by the tongue. The ancient painters knew this — their ochre hands pressed flat on cave walls were not decoration. They were declaration.</p>
<span class="pg-num left rose">4</span>
</div>
</div>
</div>
<div class="page" data-page="2">
<div class="page-front paper-warm">
<div class="pg pg-warm">
<div class="spread-label">Final Study</div>
<div class="spread-title sm">Endings<br />as Form</div>
<div class="spread-rule"></div>
<p class="spread-body">Every designed thing must eventually end — the page turn, the last note, the exit from the building. The best designs make their endings feel inevitable, not arbitrary.</p>
<div class="spread-pullquote warm">"The final line is the first thing written."</div>
<span class="pg-num right">5</span>
</div>
</div>
<div class="page-back paper-ink">
<div class="pg pg-ink">
<div class="colophon-label">COLOPHON</div>
<div class="colophon-title">FORMA</div>
<div class="colophon-sub">A Journal of Design Thought</div>
<p class="colophon-body">Issue VI · Printed on FSC-certified stock · Typography set in Playfair Display and EB Garamond · All rights reserved ©</p>
<span class="pg-num left ink">6</span>
</div>
</div>
</div>
</div>
<div class="nav">
<button class="nav-btn" type="button" data-d3-15-prev disabled>← Prev</button>
<span class="page-counter" data-d3-15-counter>Spread 1 of 4</span>
<button class="nav-btn" type="button" data-d3-15-next>Next →</button>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;0,900;1,400;1,700&family=EB+Garamond:ital,wght@0,400;0,500;1,400;1,500&display=swap');
.d3-15 {
--d3-15-desk: #1c1410;
--d3-15-desk-wash: rgba(180,120,40,0.06);
--d3-15-spine-deep: rgba(0,0,0,0.40);
--d3-15-spine-soft: rgba(0,0,0,0.10);
--d3-15-page-shade: rgba(0,0,0,0.14);
--d3-15-cover-shade: rgba(0,0,0,0.20);
--d3-15-book-shadow: rgba(0,0,0,0.50);
--d3-15-paper-warm: #f5ece0;
--d3-15-paper-cool: #ede8f0;
--d3-15-paper-rose: #f5e8e8;
--d3-15-paper-sage: #e8f0e8;
--d3-15-paper-ink: #1a1510;
--d3-15-ink-warm: #2a1f14;
--d3-15-ink-cool: #2a2432;
--d3-15-ink-rose: #2a1418;
--d3-15-ink-sage: #1a2a1e;
--d3-15-ink-page: #e8d8c0;
--d3-15-folio-warm: #8a6a4a;
--d3-15-folio-cool: #7a6898;
--d3-15-folio-rose: #8a4050;
--d3-15-folio-sage: #4a7a50;
--d3-15-folio-ink: rgba(200,180,140,0.40);
--d3-15-rule-cool: #4a3868;
--d3-15-rule-rose: #8a3040;
--d3-15-quote-warm-line: #8a6a4a;
--d3-15-quote-warm-ink: #4a3424;
--d3-15-quote-sage-line: #3a6a40;
--d3-15-quote-sage-ink: #2a4a2e;
--d3-15-title-rose: #5a1828;
--d3-15-body-cool: #3a2a50;
--d3-15-body-rose: #3a1820;
--d3-15-body-sage: #2a3a26;
--d3-15-sage-img-1: #2a4a2e;
--d3-15-sage-img-2: #1a3020;
--d3-15-circle-line: rgba(120,200,120,0.40);
--d3-15-circle-fill: rgba(120,200,120,0.20);
--d3-15-cover-1: #1a0f08;
--d3-15-cover-2: #0e0a06;
--d3-15-cover-wash: rgba(180,120,40,0.12);
--d3-15-gold-dim: rgba(200,160,80,0.55);
--d3-15-gold-rule: rgba(200,160,80,0.35);
--d3-15-cover-ink: #f0e4cc;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: rgba(220,190,140,1);
--d3-15-nav-hover: rgba(200,170,120,0.08);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.05);
--d3-15-ui-line: rgba(200,170,120,0.28);
--d3-15-ui-ink: rgba(220,190,140,0.92);
width: 100%;
min-height: max(100vh, 600px);
display: block;
position: relative;
background: var(--d3-15-desk);
font-family: 'EB Garamond', Georgia, serif;
overflow: hidden;
perspective: 2400px;
box-sizing: border-box;
}
.d3-15 *,
.d3-15 *::before,
.d3-15 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.d3-15:has(.d3-15__theme-input:checked) {
--d3-15-desk: #ded0bb;
--d3-15-desk-wash: rgba(120,80,20,0.10);
--d3-15-spine-deep: rgba(58,38,16,0.34);
--d3-15-spine-soft: rgba(58,38,16,0.08);
--d3-15-page-shade: rgba(78,54,24,0.13);
--d3-15-cover-shade: rgba(70,46,20,0.18);
--d3-15-book-shadow: rgba(72,50,22,0.30);
--d3-15-paper-warm: #fdf7ec;
--d3-15-paper-cool: #f4f0fa;
--d3-15-paper-rose: #fdefef;
--d3-15-paper-sage: #eef8ef;
--d3-15-paper-ink: #241b13;
--d3-15-ink-warm: #241a10;
--d3-15-ink-cool: #241d2e;
--d3-15-ink-rose: #2a1014;
--d3-15-ink-sage: #14261a;
--d3-15-ink-page: #f2e5ce;
--d3-15-folio-warm: #6d4f2f;
--d3-15-folio-cool: #5d4a80;
--d3-15-folio-rose: #77303f;
--d3-15-folio-sage: #35603f;
--d3-15-folio-ink: rgba(216,198,160,0.55);
--d3-15-rule-cool: #3d2b5c;
--d3-15-rule-rose: #77242f;
--d3-15-quote-warm-line: #7a5836;
--d3-15-quote-warm-ink: #3b2a17;
--d3-15-quote-sage-line: #2b5733;
--d3-15-quote-sage-ink: #1e3d22;
--d3-15-title-rose: #58101f;
--d3-15-body-cool: #2f2044;
--d3-15-body-rose: #34121a;
--d3-15-body-sage: #22321d;
--d3-15-sage-img-1: #23482a;
--d3-15-sage-img-2: #12301b;
--d3-15-circle-line: rgba(184,255,196,0.45);
--d3-15-circle-fill: rgba(184,255,196,0.22);
--d3-15-cover-1: #241408;
--d3-15-cover-2: #150c05;
--d3-15-cover-wash: rgba(220,160,60,0.16);
--d3-15-gold-dim: rgba(228,188,106,0.72);
--d3-15-gold-rule: rgba(228,188,106,0.45);
--d3-15-cover-ink: #f8eed8;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: #2a1e08;
--d3-15-nav-hover: rgba(90,62,20,0.10);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.60);
--d3-15-ui-line: rgba(70,48,22,0.22);
--d3-15-ui-ink: #3a2a12;
}
@media (prefers-color-scheme: light) {
.d3-15:has(.d3-15__theme-input:checked) {
--d3-15-desk: #ded0bb;
--d3-15-desk-wash: rgba(120,80,20,0.10);
--d3-15-spine-deep: rgba(58,38,16,0.34);
--d3-15-spine-soft: rgba(58,38,16,0.08);
--d3-15-page-shade: rgba(78,54,24,0.13);
--d3-15-cover-shade: rgba(70,46,20,0.18);
--d3-15-book-shadow: rgba(72,50,22,0.30);
--d3-15-paper-warm: #fdf7ec;
--d3-15-paper-cool: #f4f0fa;
--d3-15-paper-rose: #fdefef;
--d3-15-paper-sage: #eef8ef;
--d3-15-paper-ink: #241b13;
--d3-15-ink-warm: #241a10;
--d3-15-ink-cool: #241d2e;
--d3-15-ink-rose: #2a1014;
--d3-15-ink-sage: #14261a;
--d3-15-ink-page: #f2e5ce;
--d3-15-folio-warm: #6d4f2f;
--d3-15-folio-cool: #5d4a80;
--d3-15-folio-rose: #77303f;
--d3-15-folio-sage: #35603f;
--d3-15-folio-ink: rgba(216,198,160,0.55);
--d3-15-rule-cool: #3d2b5c;
--d3-15-rule-rose: #77242f;
--d3-15-quote-warm-line: #7a5836;
--d3-15-quote-warm-ink: #3b2a17;
--d3-15-quote-sage-line: #2b5733;
--d3-15-quote-sage-ink: #1e3d22;
--d3-15-title-rose: #58101f;
--d3-15-body-cool: #2f2044;
--d3-15-body-rose: #34121a;
--d3-15-body-sage: #22321d;
--d3-15-sage-img-1: #23482a;
--d3-15-sage-img-2: #12301b;
--d3-15-circle-line: rgba(184,255,196,0.45);
--d3-15-circle-fill: rgba(184,255,196,0.22);
--d3-15-cover-1: #241408;
--d3-15-cover-2: #150c05;
--d3-15-cover-wash: rgba(220,160,60,0.16);
--d3-15-gold-dim: rgba(228,188,106,0.72);
--d3-15-gold-rule: rgba(228,188,106,0.45);
--d3-15-cover-ink: #f8eed8;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: #2a1e08;
--d3-15-nav-hover: rgba(90,62,20,0.10);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.60);
--d3-15-ui-line: rgba(70,48,22,0.22);
--d3-15-ui-ink: #3a2a12;
}
}
@media (prefers-color-scheme: dark) {
.d3-15:has(.d3-15__theme-input:checked) {
--d3-15-desk: #ded0bb;
--d3-15-desk-wash: rgba(120,80,20,0.10);
--d3-15-spine-deep: rgba(58,38,16,0.34);
--d3-15-spine-soft: rgba(58,38,16,0.08);
--d3-15-page-shade: rgba(78,54,24,0.13);
--d3-15-cover-shade: rgba(70,46,20,0.18);
--d3-15-book-shadow: rgba(72,50,22,0.30);
--d3-15-paper-warm: #fdf7ec;
--d3-15-paper-cool: #f4f0fa;
--d3-15-paper-rose: #fdefef;
--d3-15-paper-sage: #eef8ef;
--d3-15-paper-ink: #241b13;
--d3-15-ink-warm: #241a10;
--d3-15-ink-cool: #241d2e;
--d3-15-ink-rose: #2a1014;
--d3-15-ink-sage: #14261a;
--d3-15-ink-page: #f2e5ce;
--d3-15-folio-warm: #6d4f2f;
--d3-15-folio-cool: #5d4a80;
--d3-15-folio-rose: #77303f;
--d3-15-folio-sage: #35603f;
--d3-15-folio-ink: rgba(216,198,160,0.55);
--d3-15-rule-cool: #3d2b5c;
--d3-15-rule-rose: #77242f;
--d3-15-quote-warm-line: #7a5836;
--d3-15-quote-warm-ink: #3b2a17;
--d3-15-quote-sage-line: #2b5733;
--d3-15-quote-sage-ink: #1e3d22;
--d3-15-title-rose: #58101f;
--d3-15-body-cool: #2f2044;
--d3-15-body-rose: #34121a;
--d3-15-body-sage: #22321d;
--d3-15-sage-img-1: #23482a;
--d3-15-sage-img-2: #12301b;
--d3-15-circle-line: rgba(184,255,196,0.45);
--d3-15-circle-fill: rgba(184,255,196,0.22);
--d3-15-cover-1: #241408;
--d3-15-cover-2: #150c05;
--d3-15-cover-wash: rgba(220,160,60,0.16);
--d3-15-gold-dim: rgba(228,188,106,0.72);
--d3-15-gold-rule: rgba(228,188,106,0.45);
--d3-15-cover-ink: #f8eed8;
--d3-15-nav-ink: rgba(228,203,163,0.96);
--d3-15-nav-ink-hot: #2a1e08;
--d3-15-nav-hover: rgba(90,62,20,0.10);
--d3-15-counter: rgba(214,186,142,0.9);
--d3-15-ui-face: rgba(255,255,255,0.60);
--d3-15-ui-line: rgba(70,48,22,0.22);
--d3-15-ui-ink: #3a2a12;
}
}
.d3-15 .d3-15__theme {
position: absolute;
inset-block-start: clamp(0.75rem, 3vw, 1.5rem);
inset-inline-end: clamp(0.75rem, 3vw, 1.5rem);
z-index: 30;
}
.d3-15 .d3-15__theme-input {
position: absolute;
inset: 0;
inline-size: 100%;
block-size: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
z-index: 1;
}
.d3-15 .d3-15__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--d3-15-ui-line);
border-radius: 12px;
background: var(--d3-15-ui-face);
color: var(--d3-15-ui-ink);
pointer-events: none;
}
.d3-15 .d3-15__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.d3-15 .d3-15__theme-input:focus-visible + .d3-15__theme-label {
outline: 2px solid currentColor;
outline-offset: 3px;
}
.d3-15 .card {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 28px 16px;
}
.d3-15 .card::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse 60% 50% at 50% 40%, var(--d3-15-desk-wash) 0%, transparent 70%);
pointer-events: none;
}
.d3-15 .book-scene {
transform-style: preserve-3d;
transform: rotateX(5deg) rotateY(-4deg);
transition: transform 0.6s ease;
position: relative;
z-index: 1;
}
.d3-15 .book-scene:hover {
transform: rotateX(4deg) rotateY(-2deg);
}
.d3-15 .book {
width: 560px;
height: 360px;
position: relative;
transform-style: preserve-3d;
}
.d3-15 .book::before {
content: '';
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 12px;
transform: translateX(-50%) translateZ(1px);
background: linear-gradient(to right, var(--d3-15-spine-deep) 0%, var(--d3-15-spine-soft) 30%, var(--d3-15-spine-soft) 70%, var(--d3-15-spine-deep) 100%);
z-index: 20;
pointer-events: none;
}
.d3-15 .page {
position: absolute;
width: 280px;
height: 360px;
top: 0;
left: 280px;
transform-origin: left center;
transform-style: preserve-3d;
transition: transform 1.1s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.d3-15 .page.flipped {
transform: rotateY(-180deg);
}
.d3-15 .page-front,
.d3-15 .page-back {
position: absolute;
inset: 0;
backface-visibility: hidden;
overflow: hidden;
}
.d3-15 .page-back {
transform: rotateY(180deg);
}
.d3-15 .left-cover {
position: absolute;
left: 0;
top: 0;
width: 280px;
height: 360px;
z-index: 0;
border-radius: 4px 0 0 4px;
overflow: hidden;
}
.d3-15 .pg {
width: 100%;
height: 100%;
padding: 30px 26px;
display: flex;
flex-direction: column;
position: relative;
}
.d3-15 .pg-warm {
color: var(--d3-15-ink-warm);
}
.d3-15 .pg-cool {
color: var(--d3-15-ink-cool);
}
.d3-15 .pg-rose {
color: var(--d3-15-ink-rose);
}
.d3-15 .pg-sage {
color: var(--d3-15-ink-sage);
}
.d3-15 .pg-ink {
color: var(--d3-15-ink-page);
}
.d3-15 .pg-num {
font-family: 'EB Garamond', serif;
font-size: 10px;
letter-spacing: 2px;
position: absolute;
bottom: 16px;
}
.d3-15 .pg-num.right {
right: 22px;
color: var(--d3-15-folio-warm);
}
.d3-15 .pg-num.left {
left: 22px;
color: var(--d3-15-folio-cool);
}
.d3-15 .pg-num.rose {
color: var(--d3-15-folio-rose);
}
.d3-15 .pg-num.sage {
color: var(--d3-15-folio-sage);
}
.d3-15 .pg-num.ink {
color: var(--d3-15-folio-ink);
}
.d3-15 .page-front::after,
.d3-15 .page-back::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
width: 40px;
pointer-events: none;
}
.d3-15 .page-front::after {
right: 0;
background: linear-gradient(to right, transparent, var(--d3-15-page-shade));
}
.d3-15 .page-back::after {
left: 0;
background: linear-gradient(to left, transparent, var(--d3-15-page-shade));
}
.d3-15 .left-cover::after {
content: '';
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 30px;
background: linear-gradient(to right, transparent, var(--d3-15-cover-shade));
}
.d3-15 .paper-warm {
background: var(--d3-15-paper-warm);
}
.d3-15 .paper-cool {
background: var(--d3-15-paper-cool);
}
.d3-15 .paper-rose {
background: var(--d3-15-paper-rose);
}
.d3-15 .paper-sage {
background: var(--d3-15-paper-sage);
}
.d3-15 .paper-ink {
background: var(--d3-15-paper-ink);
}
.d3-15 .spread-label {
font-family: 'EB Garamond', serif;
font-size: 9px;
letter-spacing: 4px;
text-transform: uppercase;
opacity: 0.72;
margin-bottom: 16px;
}
.d3-15 .spread-title {
font-family: 'Playfair Display', serif;
font-weight: 900;
line-height: 1.1;
margin-bottom: 14px;
}
.d3-15 .spread-title.sm {
font-size: 26px;
}
.d3-15 .spread-title.xs {
font-size: 22px;
}
.d3-15 .spread-title.rose {
color: var(--d3-15-title-rose);
}
.d3-15 .spread-body {
font-family: 'EB Garamond', serif;
font-size: 13px;
line-height: 1.78;
opacity: 0.78;
}
.d3-15 .spread-body.mt {
margin-top: 10px;
}
.d3-15 .spread-body.alt {
color: var(--d3-15-body-cool);
}
.d3-15 .spread-body.rose-body {
color: var(--d3-15-body-rose);
}
.d3-15 .spread-body.sage-body {
color: var(--d3-15-body-sage);
}
.d3-15 .spread-pullquote {
font-family: 'Playfair Display', serif;
font-style: italic;
font-size: 16px;
line-height: 1.5;
border-left: 2px solid currentColor;
padding-left: 14px;
margin: 16px 0;
opacity: 0.85;
}
.d3-15 .spread-pullquote.warm {
border-color: var(--d3-15-quote-warm-line);
color: var(--d3-15-quote-warm-ink);
}
.d3-15 .spread-pullquote.sage {
border-color: var(--d3-15-quote-sage-line);
color: var(--d3-15-quote-sage-ink);
}
.d3-15 .spread-rule {
width: 40px;
height: 1px;
background: currentColor;
opacity: 0.3;
margin: 14px 0;
}
.d3-15 .spread-rule.alt {
background: var(--d3-15-rule-cool);
}
.d3-15 .spread-rule.rose {
background: var(--d3-15-rule-rose);
}
.d3-15 .spread-img {
width: 100%;
height: 100px;
border-radius: 4px;
margin: 14px 0;
overflow: hidden;
flex-shrink: 0;
}
.d3-15 .sage-img {
height: 72px;
background: linear-gradient(135deg, var(--d3-15-sage-img-1), var(--d3-15-sage-img-2));
display: flex;
align-items: center;
justify-content: center;
}
.d3-15 .circle-outer {
width: 52px;
height: 52px;
border: 2px solid var(--d3-15-circle-line);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.d3-15 .circle-inner {
width: 32px;
height: 32px;
background: var(--d3-15-circle-fill);
border-radius: 50%;
}
.d3-15 .spread-big-num {
font-family: 'Playfair Display', serif;
font-size: 80px;
font-weight: 900;
line-height: 0.9;
opacity: 0.07;
position: absolute;
right: 16px;
bottom: 30px;
}
.d3-15 .cover-art {
width: 100%;
height: 100%;
background: linear-gradient(148deg, var(--d3-15-cover-1), var(--d3-15-cover-2));
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 26px;
border-radius: 4px 0 0 4px;
position: relative;
overflow: hidden;
}
.d3-15 .cover-art::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse 70% 60% at 50% 40%, var(--d3-15-cover-wash) 0%, transparent 70%);
}
.d3-15 .cover-eyebrow {
font-family: 'EB Garamond', serif;
font-size: 9px;
letter-spacing: 5px;
color: var(--d3-15-gold-dim);
margin-bottom: 26px;
z-index: 1;
}
.d3-15 .cover-rule {
width: 56px;
height: 1px;
background: var(--d3-15-gold-rule);
margin: 0 0 26px;
z-index: 1;
}
.d3-15 .cover-title {
font-family: 'Playfair Display', serif;
font-size: 34px;
font-weight: 900;
color: var(--d3-15-cover-ink);
text-align: center;
line-height: 1.1;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.d3-15 .cover-vol {
font-style: italic;
font-weight: 400;
font-size: 20px;
opacity: 0.7;
margin-top: 6px;
}
.d3-15 .cover-rule:nth-of-type(2),
.d3-15 .cover-title + .cover-rule {
margin: 26px 0;
}
.d3-15 .cover-sub {
font-family: 'EB Garamond', serif;
font-style: italic;
font-size: 11px;
color: var(--d3-15-gold-dim);
letter-spacing: 1px;
z-index: 1;
}
.d3-15 .colophon-label {
font-family: 'EB Garamond', serif;
font-size: 9px;
letter-spacing: 4px;
opacity: 0.45;
margin-bottom: 18px;
}
.d3-15 .colophon-title {
font-family: 'Playfair Display', serif;
font-size: 18px;
font-weight: 700;
opacity: 0.9;
margin-bottom: 8px;
}
.d3-15 .colophon-sub {
font-family: 'Playfair Display', serif;
font-style: italic;
font-size: 12px;
opacity: 0.6;
margin-bottom: 22px;
}
.d3-15 .colophon-body {
font-family: 'EB Garamond', serif;
font-size: 11px;
line-height: 1.8;
opacity: 0.58;
}
.d3-15 .nav {
display: flex;
align-items: center;
gap: 18px;
margin-top: 26px;
position: relative;
z-index: 100;
}
.d3-15 .nav-btn {
display: flex;
align-items: center;
gap: 8px;
font-family: 'EB Garamond', serif;
font-size: 12px;
letter-spacing: 2px;
color: var(--d3-15-nav-ink);
background: none;
border: none;
cursor: pointer;
padding: 9px 18px;
border-radius: 4px;
transition: color 0.2s, background 0.2s;
}
.d3-15 .nav-btn:hover {
color: var(--d3-15-nav-ink-hot);
background: var(--d3-15-nav-hover);
}
.d3-15 .nav-btn:disabled {
opacity: 0.35;
cursor: default;
}
.d3-15 .page-counter {
font-family: 'EB Garamond', serif;
font-size: 12px;
color: var(--d3-15-counter);
letter-spacing: 2px;
min-width: 80px;
text-align: center;
}
.d3-15 .book-shadow {
position: absolute;
bottom: -18px;
left: 10%;
right: 10%;
height: 28px;
background: radial-gradient(ellipse at center, var(--d3-15-book-shadow) 0%, transparent 70%);
filter: blur(12px);
z-index: -1;
}
@media (max-width: 720px) {
.d3-15 .book {
width: 360px;
height: 280px;
}
.d3-15 .page,
.d3-15 .left-cover {
width: 180px;
height: 280px;
}
.d3-15 .page {
left: 180px;
}
.d3-15 .pg {
padding: 22px 18px;
}
.d3-15 .spread-title.sm {
font-size: 20px;
}
.d3-15 .spread-title.xs {
font-size: 18px;
}
.d3-15 .cover-title {
font-size: 26px;
}
}
@media (prefers-reduced-motion: reduce) {
.d3-15 .book-scene,
.d3-15 .page {
transition: none !important;
}
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.d3-15');
if (!root) return;
const book = root.querySelector('[data-d3-15-book]');
const prevBtn = root.querySelector('[data-d3-15-prev]');
const nextBtn = root.querySelector('[data-d3-15-next]');
const counter = root.querySelector('[data-d3-15-counter]');
if (!book || !prevBtn || !nextBtn || !counter) return;
const pages = Array.from(root.querySelectorAll('.page[data-page]'));
const TOTAL_SPREADS = pages.length + 1;
let currentSpread = 0;
function update() {
pages.forEach((page, i) => {
page.classList.toggle('flipped', i < currentSpread);
page.style.zIndex = i < currentSpread ? i + 1 : pages.length - i + 4;
});
counter.textContent = 'Spread ' + (currentSpread + 1) + ' of ' + TOTAL_SPREADS;
prevBtn.disabled = currentSpread === 0;
nextBtn.disabled = currentSpread === TOTAL_SPREADS - 1;
}
prevBtn.addEventListener('click', () => { if (currentSpread > 0) { currentSpread--; update(); } });
nextBtn.addEventListener('click', () => { if (currentSpread < TOTAL_SPREADS - 1) { currentSpread++; update(); } });
book.addEventListener('click', e => {
if (e.target.closest('.nav-btn')) return;
const rect = book.getBoundingClientRect();
if (e.clientX > rect.left + rect.width / 2) {
if (currentSpread < TOTAL_SPREADS - 1) { currentSpread++; update(); }
} else {
if (currentSpread > 0) { currentSpread--; update(); }
}
});
let pointerInside = false;
root.addEventListener('mouseenter', () => { pointerInside = true; });
root.addEventListener('mouseleave', () => { pointerInside = false; });
document.addEventListener('keydown', e => {
if (!pointerInside) return;
if (e.key === 'ArrowRight') { nextBtn.click(); e.preventDefault(); }
if (e.key === 'ArrowLeft') { prevBtn.click(); e.preventDefault(); }
});
update();
})();
```How this works
A hinge is a rotation whose pivot is not in the middle. With transform-origin at the gutter, rotateY sweeps the leaf's free edge across the spread instead of turning it in place. That one declaration is the whole mechanism behind a page turn, a door and a folding panel — and perspective on the wrapper is what makes it read as a turn rather than a horizontal squash, because the near edge has to grow as the far edge shrinks.
The stacking order is the actual hard part. Turning a leaf is easy; keeping the pile legible is not. On every update each leaf is reassigned a z-index — turned leaves get a low ascending value so the most recently turned sits on top of the left pile, while unturned leaves get a descending value so the next one to turn is always frontmost on the right. Set z-index once in CSS and the book looks correct forward and wrong the moment you go back.
Each leaf has two independent pages. Front and back are separate elements in the same preserve-3d leaf with hidden backfaces, so exactly one is visible at any angle — the pattern from demo 05. That is also why the book can carry six differently-papered pages on three leaves: warm, cool, sage, rose and an ink colophon are just five different front and back surfaces.
Three input paths, one piece of state. The buttons step the index and disable themselves at the ends; a click on the right or left half of the book does the same thing, which is how people actually expect a book to work; and the arrow keys are attached at document level but gated on the pointer being inside the demo, so a page with several demos on it does not fight over the same keys. Every path calls the same update function, so the counter, the disabled states and the stack can never disagree.
Make it yours
- Add a spread by adding another
.page[data-page]leaf — the counter and the stacking maths both read the leaf count. - Change the page size tokens; the leaves, cover and clipping wrapper all derive from them.
- Retune the turn duration and easing on
.page; a slight overshoot at the end sells the weight of paper. - Restyle a spread by swapping its paper class — warm, cool, sage, rose and ink are independent surface token sets.
- Swap the arrow-key gate from pointer-inside to focus-within if the audience is primarily keyboard users.
- Retheme from the
--d3-15-*block; every paper, ink, rule and pullquote colour has a light and a dark value.
Gotchas — read before shipping
- Z-index has to be recomputed on every turn, not declared once. A static stacking order looks right going forward and shows the wrong page through the pile going back.
- Reusing one element for both sides of a leaf shows mirrored text past 90 degrees. Two faces, back pre-rotated, both with hidden backfaces.
- The document-level arrow-key listener is gated on the pointer being inside the section. That is the pragmatic gate, not the correct one — a keyboard-only user never triggers it, so use
:focus-withinor attach to the book itself if keyboard parity matters. - Pages are a fixed size with
overflow: hidden, so spread copy has to be written to fit; a paragraph two lines too long is clipped silently rather than scrolling. - A turning leaf's rendered bounding box is far wider than its layout box mid-sweep, which is a classic source of horizontal overflow on narrow screens. Clip on a wrapper outside the 3D context, never on the leaf's own parent.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 105+ | 15.4+ | 121+ | 105+ |
The :has() theme toggle alone sets the floor here; the token block is sRGB hex and rgba() throughout, so there is no oklch() requirement. transform-origin, perspective and backface-visibility have been Baseline since 2015. Fonts load through an @import of Google Fonts, with serif fallbacks declared in every family stack. Without JavaScript the book renders closed on its cover with the nav visible but inert, which is a correct static state, and under prefers-reduced-motion the turn resolves instantly instead of sweeping.