← back to Dw Marketing Reels
public/collections/graduate-flipbook/page-flip.css
142 lines
/*!
* page-flip.css — visual layer for page-flip.js.
*
* The JS drives WHEN things move; this file makes them look like paper. The
* shadow work Meng To flagged lives here in three cooperating layers:
* 1. .pf-curl — per-leaf ambient-occlusion gradient (opacity driven by JS)
* 2. .pf-gutter — the fixed spine shadow down the binding edge
* 3. .pf-face — subtle paper grain + a page-edge highlight
*
* Tunable via CSS custom properties (all overridable per instance):
* --pf-persp 3D perspective depth (set by JS from opts.perspective)
* --pf-gutter spine-shadow peak opacity (set by JS from opts.gutterShadow)
* --pf-paper page background
* --pf-ink page text color
* --pf-radius corner radius of the book
*/
.pageflip {
--pf-paper: #fbf9f4; /* warm sketchbook stock */
--pf-paper-2: #f3efe6; /* backside, slightly darker */
--pf-ink: #2a2622;
--pf-radius: 10px;
--pf-edge: rgba(0, 0, 0, 0.10); /* page-edge line */
position: relative;
width: 100%;
aspect-ratio: 3 / 4; /* portrait sketchbook; override as needed */
margin: 0 auto;
perspective: var(--pf-persp, 2200px);
perspective-origin: 0% 50%; /* eye toward the spine so the turn arcs correctly */
touch-action: pan-y; /* let vertical scroll through; we own horizontal */
user-select: none;
-webkit-user-select: none;
outline: none;
}
.pageflip.pf-grabbing { cursor: grabbing; }
.pageflip:not(.pf-grabbing) { cursor: grab; }
/* Each leaf pivots on its spine edge (left). */
.pf-page {
position: absolute;
inset: 0;
transform-origin: left center;
transform-style: preserve-3d;
will-change: transform;
border-radius: 0 var(--pf-radius) var(--pf-radius) 0;
/* No CSS transition — the JS rAF loop owns every frame so the shadow and the
transform stay locked together. A CSS transition here would desync them. */
}
/* Front + back faces. backface-visibility hides whichever side faces away, so
the two faces never bleed through each other mid-turn. */
.pf-face {
position: absolute;
inset: 0;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
border-radius: inherit;
overflow: hidden;
box-sizing: border-box;
padding: clamp(16px, 5%, 40px);
color: var(--pf-ink);
background: var(--pf-paper);
/* faint paper grain + a bright page-edge highlight on the free (right) edge */
background-image:
linear-gradient(90deg, rgba(0,0,0,0.05) 0, rgba(0,0,0,0) 3%), /* spine-side shading */
linear-gradient(270deg, rgba(255,255,255,0.6) 0, rgba(255,255,255,0) 2%);/* free-edge highlight */
box-shadow: inset 0 0 0 1px var(--pf-edge);
}
.pf-front { transform: rotateY(0deg); }
.pf-back {
transform: rotateY(180deg);
background: var(--pf-paper-2);
/* the back of a sheet: mirror the shading (highlight now on the left) */
background-image:
linear-gradient(270deg, rgba(0,0,0,0.06) 0, rgba(0,0,0,0) 4%),
linear-gradient(90deg, rgba(255,255,255,0.4) 0, rgba(255,255,255,0) 3%);
}
/*
* THE CURL SHADOW — the layer that sells the turn.
* Its opacity is set every frame by JS (peaks as the page stands perpendicular).
* --pf-curl-dir flips the gradient once we pass 90° so the darkness always
* gathers where the sheet is bending away from the light, not on the flat face.
*/
.pf-curl {
position: absolute;
inset: 0;
pointer-events: none;
border-radius: inherit;
opacity: 0;
/* dir 0 (front showing): dark gathers toward the free/right edge */
background: linear-gradient(
100deg,
rgba(0, 0, 0, 0) 30%,
rgba(0, 0, 0, 0.35) 78%,
rgba(0, 0, 0, 0.55) 100%
);
}
/* dir 1 (back showing, past 90°): mirror so dark gathers toward the spine/left */
.pf-curl[style*="--pf-curl-dir: 1"] {
background: linear-gradient(
260deg,
rgba(0, 0, 0, 0) 30%,
rgba(0, 0, 0, 0.35) 78%,
rgba(0, 0, 0, 0.55) 100%
);
}
/* The turning leaf gets a real drop shadow so it lifts off the stack. */
.pf-page.pf-turning {
box-shadow: 0 18px 40px rgba(0, 0, 0, 0.28);
}
/*
* THE GUTTER / SPINE SHADOW — fixed, always on, so the book reads as bound.
* A soft dark band hugging the binding edge (left). Peak opacity from --pf-gutter.
*/
.pf-gutter {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 12%;
pointer-events: none;
z-index: 9999;
border-radius: var(--pf-radius) 0 0 var(--pf-radius);
background: linear-gradient(
90deg,
rgba(0, 0, 0, calc(var(--pf-gutter, 0.28) * 1)) 0,
rgba(0, 0, 0, calc(var(--pf-gutter, 0.28) * 0.35)) 40%,
rgba(0, 0, 0, 0) 100%
);
mix-blend-mode: multiply;
}
/* Respect users who ask for less motion: snap instantly (JS still works, the
rAF just completes in one frame's worth of visual change to them via CSS is
n/a — but we drop the lift shadow that draws the eye). */
@media (prefers-reduced-motion: reduce) {
.pf-page.pf-turning { box-shadow: none; }
}