The old flex layout with justify-content: space-between pushed its
two children to the far edges on wide screens, leaving a large dead
void in the middle. Replaced with a centered, max-width three-column
grid (brand, contact, navigate) with a script wordmark, gold section
labels, a divider separating it from the banner above, and a
copyright line.
Sourced from Pexels (free for commercial use, no attribution
required) to complete the image wiring added in the visual overhaul.
Fills the 18 image slots that were previously deferred (products,
signature styles, About Mira portrait, testimonial avatars).
Kept in French to match how Mira actually messages clients. Updated
RuleIcon's icon set to match the real policy categories (deposit,
hair prep, extensions, lateness, guests, payment) in place of the
placeholder categories.
- Lazy-load Home in App.tsx so three/@react-three/fiber (~238 kB gzip) only ships to the
Home route, not to Shop/Book/Rules.
- Add max-width: 260px to ProductCard/ServiceCard so real images won't collapse Home/Book
grid layouts.
- Restore AnimatePresence mode="wait" in Book.tsx to eliminate the exit/enter double-stack
during step transitions, updating the three Book.test.tsx assertions that raced the
transition to findBy* queries.
- Hero3DScene: drop the meaningless "static" className string in favor of a dedicated
data-testid, add aria-hidden to the reduced-motion fallback branch, and remove the
unused @react-three/drei dependency (adding @types/three directly since it was only
present transitively via drei).
Adds StepProgress component showing 4 labeled booking steps (Service,
Date & Time, Contact, Confirmed) and wires it into Book.tsx alongside
framer-motion AnimatePresence/motion.div step transitions.
Deviates from literal brief in two ways, both to avoid known issues:
- Guards the transition with useReducedMotion() (duration: 0 when
prefers-reduced-motion is set), matching the fix pattern already
applied to Home.tsx and Shop.tsx after prior review round-trips.
- Drops AnimatePresence's mode="wait" in favor of the default (sync)
mode: with mode="wait", the incoming step's DOM doesn't mount until
the outgoing step's exit animation finishes, which broke existing
synchronous Testing Library queries in Book.test.tsx (real 250ms
exit animations in jsdom, no fake timers). Sync mode mounts the new
step immediately while both fade, preserving test synchronicity.
Also updates Book.test.tsx's confirmation assertion since the old
"You're booked!" heading is gone (page now always shows the shared
"Book an Appointment" heading and StepProgress; only the body swaps).
The About section's portrait image now checks useReducedMotion() and
disables parallax scroll motion when the user has set OS-level reduced
motion preferences.
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
Adds Hero3DScene, a procedurally-built rotating braid mesh rendered via
@react-three/fiber, replacing the flat CSS-circle Hero3D component. Falls
back to a static gradient circle under prefers-reduced-motion, matching the
old component's fallback behavior.
Also adds a ResizeObserver mock to the test setup, required by
@react-three/fiber's Canvas (via react-use-measure) in jsdom, which doesn't
implement it natively.
Replace 'global.IntersectionObserver' with 'globalThis.IntersectionObserver' to fix
TS2304 "Cannot find name 'global'" error. globalThis is the standard environment-agnostic
reference that type-checks cleanly in browser-targeted tsconfig. Runtime behavior
identical to global in Node/jsdom contexts.
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
Implements a modal component for collecting product inquiries with name, email, and notes fields. Validates name (required) and email (must contain @) client-side, displays inline error messages for invalid input, and shows a confirmation message on valid submit.
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
Implement ProductCard component with:
- Renders product details (name, price, description)
- "Ask about this" button disabled when out of stock
- Shows "Currently unavailable" badge when out of stock
- Calls onInquire callback when clicked
Implement ServiceCard component with:
- Renders service details (name, formatted duration, price)
- Clickable button card that calls onSelect with service id
- formatDuration helper for human-readable time format
Both components include CSS modules with hover effects and proper styling.
All 4 tests pass (2 for each component).
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
framer-motion's useReducedMotion() hook caches its result at the module level
and never re-reads window.matchMedia on subsequent calls. This caused the second
test to fail because the cache was locked to the first test's matchMedia mock.
Fixed by using vi.doMock() with vi.resetModules() to mock useReducedMotion
directly with the desired value for each test, ensuring each test runs with a
fresh module context where the hook returns the expected value.
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
- Implements Hero3D component with CSS 3D transforms
- Uses framer-motion's useScroll/useTransform for scroll-linked rotation
- Respects prefers-reduced-motion preference for static rendering
- Component structure: wrapper with perspective, inner motif with 3D rotation
- Test setup: added window.matchMedia mock for framer-motion compatibility
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH
- Create Layout component that renders Nav > Outlet > Footer
- Wire Layout as parent route in App.tsx to wrap all page routes
- Update App.test.tsx to verify Nav/Footer render on every route
Claude-Session: https://claude.ai/code
Implement ScrollReveal component that fades and rises sections into view
on scroll with framer-motion. Respects prefers-reduced-motion by
rendering children unanimated when that setting is enabled.
- Add ScrollReveal component with fade+rise animation on scroll
- Add test coverage for both animated and reduced-motion paths
- Add IntersectionObserver mock to test setup
Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH