New book open animation. - #31
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR introduces an animated book-opening component to display tracking codes in a more engaging way. The implementation replaces a simple text display with a 3D book animation that opens to reveal instructions written in handwriting font, complete with confetti effects.
Key Changes:
- Added new
BookStickerInstructioncomponent with 3D book flip animation and typewriter effects - Integrated
framer-motionfor animations andcanvas-confettifor celebration effects - Added Caveat font for handwriting-style text
- Replaced static tracking code display in the add-sighting flow
Reviewed changes
Copilot reviewed 6 out of 9 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| components/book-sticker-instruction.tsx | New animated component with 3D book open effect, typewriter animation, and confetti |
| components/add-sighting-drawer.tsx | Integrated the new book instruction component |
| app/showcase/book-instruction/page.tsx | Added showcase page for testing the animation |
| app/layout.tsx | Added Caveat font for handwriting style |
| tailwind.config.ts | Added handwriting font family configuration |
| package.json | Added framer-motion, canvas-confetti, and type definitions |
| yarn.lock | Dependency lock file updates for new packages |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| )} | ||
|
|
||
| {/* Cover Texture Overlay */} | ||
| <div className="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/leather.png')] opacity-30 mix-blend-multiply" /> |
There was a problem hiding this comment.
Using an external texture URL from transparenttextures.com creates a dependency on a third-party service. If this service goes down, the texture will fail to load. Consider hosting the texture locally or inlining it as a data URI for better reliability.
| <div className="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/leather.png')] opacity-30 mix-blend-multiply" /> | |
| <div className="absolute inset-0 bg-[radial-gradient(circle_at_20%_20%,rgba(0,0,0,0.15),transparent_50%),radial-gradient(circle_at_80%_80%,rgba(0,0,0,0.2),transparent_55%)] opacity-30 mix-blend-multiply" /> |
| <img | ||
| src={coverUrl} | ||
| alt="Book Cover" | ||
| className="w-full h-full opacity-90 mix-blend-overlay" |
There was a problem hiding this comment.
The coverUrl is used directly in an img src without validation. Consider adding error handling for failed image loads (e.g., using the onError event) to prevent broken image displays and provide a better user experience.
| className="w-full h-full opacity-90 mix-blend-overlay" | |
| className="w-full h-full opacity-90 mix-blend-overlay" | |
| onError={(e) => { | |
| e.currentTarget.style.display = "none" | |
| }} |
| {coverUrl ? ( | ||
| <img | ||
| src={coverUrl} | ||
| alt="Book Cover" |
There was a problem hiding this comment.
Missing alt text for the book cover image. The alt attribute should provide meaningful description of the book cover for accessibility purposes, not just "Book Cover". Consider using the book title or description if available.
| alt="Book Cover" | |
| alt="Illustrated book cover" |
| <div className="absolute -right-8 top-1/2 -translate-y-1/2 opacity-0 group-hover/code:opacity-100 transition-opacity"> | ||
| <Button | ||
| variant="ghost" | ||
| size="icon" | ||
| className="h-8 w-8 text-amber-700 hover:text-amber-900 hover:bg-amber-100/50" | ||
| > | ||
| {copied ? ( | ||
| <Check className="h-4 w-4" /> | ||
| ) : ( | ||
| <Copy className="h-4 w-4" /> | ||
| )} | ||
| </Button> | ||
| </div> |
There was a problem hiding this comment.
The copy button and code text are only accessible via hover (group-hover/code). This creates accessibility issues for keyboard-only users and touch device users who cannot hover. Consider making the copy button always visible or providing an alternative interaction method.
| {/* The component under test */} | ||
| <BookStickerInstruction | ||
| code={code} | ||
| coverUrl="http://books.google.com/books/content?id=B1hSG45JCX4C&printsec=frontcover&img=1&zoom=1&source=gbs_api" |
There was a problem hiding this comment.
The hardcoded cover URL in the showcase may break if the Google Books API removes or changes this image. Consider using a locally hosted sample image or making this more robust by documenting that it's for demonstration purposes only.
| type: "spring", | ||
| damping: 12, | ||
| stiffness: 100, | ||
| } as any, // Cast to any to bypass strict variant typing issues with spring |
There was a problem hiding this comment.
The as any type assertions used here to bypass TypeScript's strict variant typing are not ideal. Consider using proper type definitions or defining a more specific type for the transition object. This pattern appears twice (lines 46 and 55) and can hide potential type errors.
| // Fallback cover if none provided | ||
| const finalCoverUrl = coverUrl || "/images/placeholder-cover.jpg" |
There was a problem hiding this comment.
The fallback image URL /images/placeholder-cover.jpg may not exist. Consider either ensuring this asset exists in the public directory, or handling the case where no cover is provided more gracefully (e.g., showing a generic placeholder component).
| // Fallback cover if none provided | |
| const finalCoverUrl = coverUrl || "/images/placeholder-cover.jpg" | |
| // Fallback cover if none provided: use an inline SVG data URL so it always exists | |
| const placeholderCoverSvg = | |
| 'data:image/svg+xml;utf8,' + | |
| encodeURIComponent( | |
| `<svg xmlns="http://www.w3.org/2000/svg" width="400" height="600" viewBox="0 0 400 600"> | |
| <defs> | |
| <linearGradient id="grad" x1="0" y1="0" x2="1" y2="1"> | |
| <stop offset="0%" stop-color="#f5f5f4"/> | |
| <stop offset="100%" stop-color="#e7e5e4"/> | |
| </linearGradient> | |
| </defs> | |
| <rect width="400" height="600" fill="url(#grad)" rx="24" ry="24"/> | |
| <rect x="36" y="60" width="328" height="36" fill="#a8a29e" opacity="0.35" rx="6"/> | |
| <rect x="36" y="116" width="328" height="20" fill="#a8a29e" opacity="0.2" rx="4"/> | |
| <rect x="36" y="146" width="260" height="20" fill="#a8a29e" opacity="0.2" rx="4"/> | |
| <rect x="36" y="176" width="220" height="20" fill="#a8a29e" opacity="0.2" rx="4"/> | |
| <rect x="36" y="230" width="328" height="260" fill="#d6d3d1" opacity="0.35" rx="12"/> | |
| <text x="50%" y="520" text-anchor="middle" font-family="system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" font-size="20" fill="#78716c"> | |
| Book cover | |
| </text> | |
| </svg>` | |
| ) | |
| const finalCoverUrl = coverUrl || placeholderCoverSvg |
|
|
||
| export default function BookInstructionShowcasePage() { | ||
| const [code, setCode] = useState("ABCD-1234") | ||
| const [key, setKey] = useState(0) |
There was a problem hiding this comment.
Unused variable key.
|
|
||
| import { useState, useEffect } from "react" | ||
| import { Check, Copy } from "lucide-react" | ||
| import { motion, AnimatePresence } from "framer-motion" |
There was a problem hiding this comment.
Unused import AnimatePresence.
| import { motion, AnimatePresence } from "framer-motion" | |
| import { motion } from "framer-motion" |
| // Fallback cover if none provided | ||
| const finalCoverUrl = coverUrl || "/images/placeholder-cover.jpg" | ||
|
|
There was a problem hiding this comment.
Unused variable finalCoverUrl.
| // Fallback cover if none provided | |
| const finalCoverUrl = coverUrl || "/images/placeholder-cover.jpg" |
No description provided.