diff --git a/src/sections/IntroSection.css b/src/sections/IntroSection.css index 9eaabf9..939256d 100644 --- a/src/sections/IntroSection.css +++ b/src/sections/IntroSection.css @@ -264,14 +264,21 @@ position: relative; transform: translateX(100%); transition: transform 0.4s ease-in, opacity 0.4s ease-in; - opacity: 0; + opacity: 1; pointer-events: none; } /* active state set upon mouse hover */ .tracks_label.active { transform: translateX(0); - transition: transform 0.4s ease-out, opacity 0.4s ease-out; + opacity: 1; + pointer-events: auto; +} + +/* peek state - partially slides out when no tracks are active */ +.tracks_label.peek { + transform: translateX(60%); + transition: transform 0.4s ease-out; opacity: 1; pointer-events: auto; } diff --git a/src/sections/IntroSection.jsx b/src/sections/IntroSection.jsx index ee7b69d..38999c3 100644 --- a/src/sections/IntroSection.jsx +++ b/src/sections/IntroSection.jsx @@ -4,9 +4,15 @@ import { useEffect, useRef, useState } from 'react'; export default function IntroSection() { const [canFall, setCanFall] = useState(false); const [activeTracks, setActiveTracks] = useState(new Set()); - const [animatingTracks, setAnimatingTracks] = useState(new Set()); + const [peekingTracks, setPeekingTracks] = useState(new Set()); + const [isIdle, setIsIdle] = useState(true); const canRef = useRef(null); const timeoutRef = useRef({}); + let TRACK_SLIDE_DELAY = 1000; //change this to change how fast track slides back in + + useEffect(() => { + setIsIdle(activeTracks.size === 0); + }, [activeTracks]); useEffect(() => { const handleScroll = () => { @@ -28,45 +34,18 @@ export default function IntroSection() { return () => window.removeEventListener('scroll', handleScroll); }, [canFall]); - useEffect(() => { - if (canFall) { - // Slide out tracks one at a time during the Can Falling animation - const slideOutTimeouts = []; - for (let i = 0; i < 4; i++) { - slideOutTimeouts.push( - setTimeout(() => { - setAnimatingTracks(prev => new Set(prev).add(i)); - }, i * 200) // 200ms delay between each track - ); - } - - // Slide them back in after the fall completes (1.25s) + a delay - const slideBackDelay = 1250 + 200; // After fall animation + buffer - const slideInTimeouts = []; - for (let i = 0; i < 4; i++) { - slideInTimeouts.push( - setTimeout(() => { - setAnimatingTracks(prev => { - const newSet = new Set(prev); - newSet.delete(i); - return newSet; - }); - }, slideBackDelay + i * 200) - ); - } - - return () => { - [...slideOutTimeouts, ...slideInTimeouts].forEach(timeout => clearTimeout(timeout)); - }; - } - }, [canFall]); - + // hovering over a track label causes it to be active (slide into view) const handleTrackMouseEnter = (trackIndex) => { - // Hovering over a track label causes it to be active (slide into view) - if (timeoutRef.current[trackIndex]) clearTimeout(timeoutRef.current[trackIndex]); - if (!activeTracks.has(trackIndex)){ - setActiveTracks(prev => new Set(prev).add(trackIndex)); - } + setPeekingTracks(new Set()); + setIsIdle(false); + + if (timeoutRef.current[trackIndex]) { + clearTimeout(timeoutRef.current[trackIndex]); + } + + if (!activeTracks.has(trackIndex)){ + setActiveTracks(prev => new Set(prev).add(trackIndex)); + } }; const handleTrackMouseLeave = (trackIndex) => { @@ -77,9 +56,65 @@ export default function IntroSection() { newSet.delete(trackIndex); return newSet; }); - }, 2000); + }, TRACK_SLIDE_DELAY); }; + // function that makes the tracks peek out a little one by one + const runPeekSequence = () => { + const timeouts = []; + const PEEK_DURATION = 600; + const STAGGER = 90; + + setPeekingTracks(new Set()); + + for (let i = 0; i < 4; i++) { + const start = i * STAGGER; + + timeouts.push( + setTimeout(() => { + setPeekingTracks(prev => new Set(prev).add(i)); + }, start) + ); + + timeouts.push( + setTimeout(() => { + setPeekingTracks(prev => { + const ns = new Set(prev); + ns.delete(i); + return ns; + }); + }, start + PEEK_DURATION) + ); + } + + return timeouts; +}; + + + //if user not hovering over any tracks, play peeking animation to draw their eye to it + useEffect(() => { + if (!isIdle) return; + + let peekTimeouts = []; + let intervalId; + + const startPeekLoop = () => { + peekTimeouts = runPeekSequence(); + }; + + // Run immediately + startPeekLoop(); + + // Then repeat every 3s + intervalId = setInterval(startPeekLoop, 3000); + + return () => { + clearInterval(intervalId); + peekTimeouts.forEach(clearTimeout); + setPeekingTracks(new Set()); + }; + }, [isIdle]); + return (