The portfolio uses two complementary animation layers that work at different points in the lifecycle of each page. CSS transitions and keyframes handle the initial entrance choreography of UI elements — things like menu items sliding in or bars sweeping across — while Framer Motion handles route-level orchestration, page wipes, and anything that needs tight keyframe timing control. Both layers rely heavily onDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/blairxu13/persona3-website/llms.txt
Use this file to discover all available pages before exploring further.
clip-path polygon shapes to create the parallelogram and arrow silhouettes that define the game’s visual style.
CSS-based animations
The mounted-state pattern
Most interactive pages start with their elements hidden and off-screen, then set amounted boolean to true inside a useEffect shortly after the component renders. CSS classes conditioned on mounted then trigger transitions so elements glide smoothly into their final positions. The small timeout ensures the browser has painted at least one frame with the initial (hidden) state before the transition fires.
transitionDelay inline style (i * 80ms) so they cascade in one after another rather than all appearing simultaneously.
P3Menu uses a 1000 ms delay before setting mounted, which is deliberately longer than the other pages (Socials and ResumePage use 60–80 ms). This gives the looping background video and the rotated name-tag watermark time to settle before the menu items draw attention by animating in.CSS keyframes catalogue
The following@keyframes rules are defined inline in each component’s <style> block:
| Name | Component | What it does |
|---|---|---|
p3-shadow-pop | P3Menu | The triangular shadow behind the active menu item punches in with a scaleX overshoot: 0 → 1.22 → 0.96 → 1 over 0.28s |
sc-reveal-bar-in | AboutMe | The detail panel slides in from the left with a slight overshoot and a rotation of −20deg, animating translateX and scaleX together |
sc-portrait-in | AboutMe | The character portrait fades in from the right with a skew and a blur-to-sharp filter transition over 0.5s |
resume-entry-reveal | ResumePage | A clip-path: circle() expands from 0 to 150vmax at the center of the screen, revealing a full-bleed video overlay |
sc-right-nav-pop | Socials / AboutMe | The LB/RB navigation widget scales from 0.55 to 1.1 and back to 1, simulating a button press pop |
sc-infobar-in | Socials | Right-panel info bars slide in from translateX(40px) with an overshoot on each mount |
sc-dim-in | AboutMe | A dark overlay fades from opacity: 0 to 1 when the reveal panel opens |
Active-item highlight animation in P3Menu
When a new menu item becomes active,animKey is incremented to force a remount of the shadow triangle element, which re-triggers the p3-shadow-pop keyframe:
cubic-bezier(0.34, 1.56, 0.64, 1) spring easing produces the characteristic overshoot that makes the pop feel physical.
Clip-path shapes
Arrow shapes in P3Menu
Every menu item uses a triangular arrowclip-path generated by a small function that computes the polygon vertices from the element’s estimated width and height:
clip-path — so all three share identical geometry and stack flush. Width and height are estimated from item.label.length * item.fontSize * 0.6 + 80 and item.fontSize * 0.94 respectively.
Parallelogram bars
The.sc-bar elements in Socials and AboutMe use a static clip-path that cuts a parallelogram with a slanted right edge:
calc(100% - 14px) bottom-right point pulls the corner 14 pixels inward from the right edge, creating the characteristic shear on the right side. The same pattern at larger offsets (88px, 120px, 18px) appears on the detail panels in AboutMe and ResumePage, maintaining a consistent skewed-geometry visual language across pages.
The character portrait inside each bar uses its own clip-path to punch out a centered parallelogram from the image:
Framer Motion usage
Keyframe arrays and the times prop
Framer Motion accepts arrays in the animate prop to define multi-step keyframe sequences on any property. The times array (values 0–1) maps each keyframe to a fraction of the total duration:
scaleX, x, y) and different timing ratios.
AnimatePresence and route transitions
AnimatePresence mode="wait" is used at two levels:
- In
App.jsx— wraps<Routes>so that the exiting route’sexitanimation completes before the new route mounts. - Inside
PageTransition— wraps the contentmotion.divwith a simpleopacity: 0exit so the page fades out cleanly ifAnimatePresencetriggers its exit.
mode="wait" setting is important at the top level — without it, the incoming and outgoing route components would both be in the DOM simultaneously, and the wipe panels from the new route’s PageTransition would render on top of a still-visible old page.
The
p3-shadow-pop CSS animation and the Framer Motion panel keyframes both use spring-flavored cubic-bezier curves ([0.34, 1.56, 0.64, 1] and [0.76, 0, 0.24, 1] respectively). These are not identical — the shadow pop uses an overshoot curve while the panel wipes use an anticipation-then-fast curve — chosen to match the weight and speed of each element type.