← 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; }
}