REFERENCE
- Gallery page: https://magpui.com/en/i/paper-fold-promo-bottom-sheet-73723d
- First frame (image): https://dnioievkvxejjbxgwvko.supabase.co/storage/v1/object/public/media/paper-fold-promo-bottom-sheet-73723d/shot.jpg
- Clip (mp4): https://dnioievkvxejjbxgwvko.supabase.co/storage/v1/object/public/media/paper-fold-promo-bottom-sheet-73723d/clip.mp4
- Original post: https://x.com/justinwiese/status/2111102276379275282 by @justinwiese
Before building, download the first frame and look at it. If ffmpeg is available, download the clip and extract frames (ffmpeg -i clip.mp4 -vf fps=2 frame-%02d.png), then study the motion frame by frame. Match what you see; the text below describes it, the frames are the truth.
BUILD:
A promotional bottom sheet for a mobile-style app that opens and closes with a realistic paper-fold. Tapping a small promo card on the home screen raises a near-fullscreen sheet: a hero photo at the top fading to white, a headline, a two-line description, a full-width button and fine print. When dismissed, the sheet does not slide away: its upper half folds down over the lower half along a horizontal crease, the folding panel darkening as it turns away from the light, then the folded sheet tucks back into the promo card. Opening plays the same fold in reverse. It should feel physical, tactile and calm, like closing a printed brochure.
DESIGN SYSTEM:
- Viewport: phone frame, 390x844. Sheet inset 8px from the sides and bottom, top at 44px, radius 40px on all corners (matches the device corner).
- Background app behind the sheet dims to rgba(0,0,0,0.35) when the sheet is open.
- Sheet surface #FFFFFF. Hero photo fills the top 45% and fades to white with linear-gradient(to bottom, transparent 55%, #FFFFFF 100%).
- Top chips: a pill "14d left" (12px medium, #111, background #FFFFFF, padding 6px 12px, radius 999px) top-left at 16px, and a 32px round close button with an X icon top-right. A 36x4px grab handle centered 8px from the top, #D4D4D4.
- Headline 24px, weight 500, #111, centered, letter-spacing -0.01em. Description 14px/20px, #6B6B6B, centered, max-width 280px.
- Primary button: full width minus 32px, height 48px, background #F2F2F2, text 15px medium #111, radius 999px.
- Fine print 11px/15px #8A8A8A centered, links in #111 weight 600.
- Shadow under the open sheet: 0 -12px 40px rgba(0,0,0,0.12).
MOTION:
- Open (tap promo card): backdrop opacity 0 -> 1 over 300ms cubic-bezier(0.32,0.72,0,1). The sheet starts folded (top half rotated rotateX(-180deg) onto the bottom half) and positioned at the card; it moves to its open position (translateY) over 420ms cubic-bezier(0.32,0.72,0,1), then the top half unfolds rotateX(-180deg -> 0deg) around its bottom edge over 650ms cubic-bezier(0.22,1,0.36,1), starting 120ms after the move begins.
- Fold lighting: on the folding half, a gradient overlay from rgba(0,0,0,0) to rgba(0,0,0,0.85) whose opacity follows sin of the fold angle (darkest near 90deg); on the static half, a soft shadow strip right under the crease, rgba(0,0,0,0.25) -> 0 over 120px, that grows as the top half closes. Add a 1px highlight line rgba(255,255,255,0.6) at the crease.
- Content blur: while the angle is between 30deg and 150deg, the folding half's content gets filter: blur(0 -> 6px -> 0) to sell motion and depth.
- Close (X, drag down > 120px, or backdrop tap): reverse: fold 0 -> -180deg over 550ms cubic-bezier(0.65,0,0.35,1), then the folded sheet translates back to the promo card and scales to its size over 380ms cubic-bezier(0.32,0.72,0,1); backdrop fades out in 250ms.
- Drag: while dragging the handle down, map drag distance 0-300px to fold angle 0 -> -120deg directly (no easing); on release, snap to open or closed with a spring (stiffness 300, damping 30).
SECTIONS:
- Trigger: a promo card on the home screen (title, subtitle, small thumbnail, dismiss x).
- Sheet parts: grab handle, "14d left" chip, close button, hero image with white fade, headline, description, primary button, fine print.
- States: closed (folded inside card), opening (move + unfold), open (idle), dragging (fold follows finger), closing (fold + tuck), reduced motion.
IMPLEMENTATION:
- Web: split the sheet into two halves rendered from the same content (top half clips the upper 50%, bottom half the lower 50%, each with overflow hidden and the content positioned so they line up). Rotate the top half with transform-origin: bottom center inside a parent with perspective: 1400px and transform-style: preserve-3d; backface-visibility: hidden, plus a back face showing the white paper reverse. Drive angle, overlays and blur from one motion value with Framer Motion (useMotionValue + useTransform), so drag and animation share the same code.
- For a true curved paper bend instead of a hinge, render the sheet into a WebGL plane (OGL or three.js) and bend vertices in a vertex shader around a moving cylinder; keep the CSS version as the default.
- Accessibility: the sheet is a dialog (role=dialog, aria-modal, focus trapped, Escape closes, focus returns to the promo card). Close button has aria-label.
- prefers-reduced-motion: no fold, just a 200ms opacity fade of sheet and backdrop.
- Performance: animate only transform, opacity and filter; avoid layout reads during drag; drop the blur on low-end devices.
ADAPT TO THE PROJECT:
Reuse the project's existing design tokens, components, fonts and dialog primitive. Map the grays and black to the closest existing color tokens. Never copy logos, brand names, photos or illustrations from the original: use a placeholder photo and placeholder copy.
SUCCESS CHECK:
- Closing the sheet visibly folds the top half down over the bottom half along a horizontal crease, with the folding half darkening and a shadow under the crease, before it shrinks into the card.
- Dragging the handle down scrubs the fold angle in real time and releasing snaps to open or closed.
- With prefers-reduced-motion enabled the sheet only fades, and keyboard users can open, close with Escape, and get focus back on the card.