diff --git a/package-lock.json b/package-lock.json index 6b91dcd..c988e40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-icons": "^5.5.0", "react-router-dom": "^7.5.1", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" @@ -14973,6 +14974,15 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/package.json b/package.json index 8a4f72b..8f82050 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-icons": "^5.5.0", "react-router-dom": "^7.5.1", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" diff --git a/src/assets/GalleryNail1.jpg b/src/assets/GalleryNail1.jpg new file mode 100644 index 0000000..00a0c36 Binary files /dev/null and b/src/assets/GalleryNail1.jpg differ diff --git a/src/assets/GalleryNail2.jpg b/src/assets/GalleryNail2.jpg new file mode 100644 index 0000000..b109f8f Binary files /dev/null and b/src/assets/GalleryNail2.jpg differ diff --git a/src/assets/GalleryNail3.jpg b/src/assets/GalleryNail3.jpg new file mode 100644 index 0000000..752f526 Binary files /dev/null and b/src/assets/GalleryNail3.jpg differ diff --git a/src/assets/GalleryNail4.jpg b/src/assets/GalleryNail4.jpg new file mode 100644 index 0000000..6dd82b1 Binary files /dev/null and b/src/assets/GalleryNail4.jpg differ diff --git a/src/assets/HeroBanner.png b/src/assets/HeroBanner.png index 30de386..2808f42 100644 Binary files a/src/assets/HeroBanner.png and b/src/assets/HeroBanner.png differ diff --git a/src/components/Gallery.js b/src/components/Gallery.js new file mode 100644 index 0000000..26961a9 --- /dev/null +++ b/src/components/Gallery.js @@ -0,0 +1,209 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { FaPlay } from 'react-icons/fa'; +import GalleryNail1 from '../assets/GalleryNail1.jpg'; +import GalleryNail2 from '../assets/GalleryNail2.jpg'; +import GalleryNail3 from '../assets/GalleryNail3.jpg'; +import GalleryNail4 from '../assets/GalleryNail4.jpg'; + +const IMAGES = [GalleryNail1, GalleryNail2, GalleryNail3, GalleryNail4]; +const IMAGE_WIDTH = 170; // Width of each image + margin +const CONTAINER_WIDTH = 30 * 16; // 30em to px (1em = 16px) + +const Gallery = () => { + // Store position instead of rearranging images + const [position, setPosition] = useState(0); + const [isAnimating, setIsAnimating] = useState(false); + const sliderRef = useRef(null); + + // Preload images + useEffect(() => { + IMAGES.forEach(src => { + const img = new Image(); + img.src = src; + }); + + if (sliderRef.current?.parentElement) { + sliderRef.current.parentElement.style.width = `${CONTAINER_WIDTH}px`; + } + }, []); + + // Create "circular" array with doubled images + const displayImages = [...IMAGES, ...IMAGES]; + + const rotateImagesRight = () => { + if (isAnimating) return; + setIsAnimating(true); + + const slider = sliderRef.current; + if (!slider) return; + + // Get current position and calculate next position + const nextPosition = (position + 1) % IMAGES.length; + + // Set up smooth transition + slider.style.transition = 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)'; + slider.style.transform = `translateX(-${(position + 1) * IMAGE_WIDTH}px)`; + + // When transition completes + const handleTransitionEnd = () => { + // Check if we need to loop back + if (nextPosition === 0) { + // Immediately jump back to the first set without transition + slider.style.transition = 'none'; + slider.style.transform = 'translateX(0)'; + setPosition(0); + } else { + // Just update position + setPosition(nextPosition); + } + + // Allow more animations after a brief delay + setTimeout(() => { + setIsAnimating(false); + }, 50); + }; + + slider.addEventListener('transitionend', handleTransitionEnd, { once: true }); + }; + + const rotateImagesLeft = () => { + if (isAnimating) return; + setIsAnimating(true); + + const slider = sliderRef.current; + if (!slider) return; + + let newPosition = position - 1; + + if (newPosition < 0) { + // Jump instantly to duplicate set (at end) + newPosition = IMAGES.length - 1; + slider.style.transition = 'none'; + slider.style.transform = `translateX(-${IMAGES.length * IMAGE_WIDTH}px)`; + + // Wait one animation frame, then animate back one + requestAnimationFrame(() => { + slider.style.transition = 'transform 0.2s cubic-bezier(0.4, 0, 0.2, 1)'; + slider.style.transform = `translateX(-${newPosition * IMAGE_WIDTH}px)`; + }); + + // After the real transition ends, update position + const handleTransitionEnd = () => { + setPosition(newPosition); + setTimeout(() => setIsAnimating(false), 50); + }; + slider.addEventListener('transitionend', handleTransitionEnd, { once: true }); + } else { + // Normal left movement + slider.style.transition = 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)'; + slider.style.transform = `translateX(-${newPosition * IMAGE_WIDTH}px)`; + + const handleTransitionEnd = () => { + setPosition(newPosition); + setTimeout(() => setIsAnimating(false), 50); + }; + slider.addEventListener('transitionend', handleTransitionEnd, { once: true }); + } + }; + + // Common styles + const styles = { + container: { + backgroundColor: '#FEE8ED', + padding: '2rem', + minHeight: '14em' + }, + sliderContainer: { + overflow: 'hidden', + position: 'relative', + isolation: 'isolate' + }, + button: { + position: 'absolute', + top: '50%', + right: '0.5em', + transform: 'translateY(-50%)', + backgroundColor: '#7d1260', + border: 'none', + color: '#FEE8ED', + width: '2.5em', + height: '2.5em', + borderRadius: '50%', + fontSize: '1rem', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + padding: 0, + zIndex: 10 + }, + slider: { + display: 'flex', + transform: `translateX(-${position * IMAGE_WIDTH}px)`, + willChange: 'transform', + width: `${displayImages.length * IMAGE_WIDTH}px`, + backfaceVisibility: 'hidden', + perspective: '1000px' + }, + imageContainer: { + width: `${IMAGE_WIDTH}px`, + flexShrink: 0, + contain: 'content', + transform: 'translateZ(0)' + }, + image: { + height: '10em', + width: '10em', + borderRadius: '15px', + objectFit: 'cover', + boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)', + backfaceVisibility: 'hidden', + transform: 'translateZ(0)', + loading: 'eager' + } + }; + + return ( +
+
+
+ + + + +
+ {displayImages.map((img, idx) => ( +
+ {`Nail +
+ ))} +
+
+
+
+ ); +}; + +export default Gallery; \ No newline at end of file diff --git a/src/components/HeroBanner.js b/src/components/HeroBanner.js index 10fcd6f..2501a88 100644 --- a/src/components/HeroBanner.js +++ b/src/components/HeroBanner.js @@ -8,7 +8,7 @@ function HeroBanner() { backgroundImage: `url(${bannerImage})`, backgroundSize: 'cover', backgroundPosition: 'center', - height: '100vh', + height: '75vh', display: 'flex', flexDirection: 'column', alignItems: 'center', diff --git a/src/pages/TestPage.js b/src/pages/TestPage.js index c264484..39492fa 100644 --- a/src/pages/TestPage.js +++ b/src/pages/TestPage.js @@ -1,11 +1,11 @@ - +import Gallery from "../components/Gallery" // add your components to the test page to see what they look like function TestPage() { return ( <> -

remove this if you want

+ ); }