forked from HappyHackingSpace/landing-frontend-app
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add stickers page - fix HappyHackingSpace#53
- Loading branch information
Showing
6 changed files
with
255 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
"use client" | ||
|
||
import Floating, { FloatingElement } from "../../components/fancy/parallax-floating" | ||
import LandingLayoutView from "@hhs/layouts/landing-layout" | ||
import { motion } from "framer-motion" | ||
import { useRef } from "react" | ||
|
||
const bgColors = [ | ||
"bg-red-500", | ||
"bg-blue-500", | ||
"bg-green-500", | ||
"bg-yellow-500", | ||
"bg-purple-500", | ||
"bg-pink-500", | ||
"bg-indigo-500", | ||
"bg-orange-500", | ||
] | ||
|
||
const positions = [ | ||
"top-1/4 left-[10%]", | ||
"top-1/2 left-[15%]", | ||
"top-1/3 left-[40%]", | ||
"top-2/3 left-[60%]", | ||
"top-1/4 right-[20%]", | ||
"bottom-1/3 left-[20%]", | ||
"bottom-1/2 right-[30%]", | ||
"bottom-1/4 left-[45%]", | ||
] | ||
|
||
const stickers = Array.from({ length: 8 }, (_, i) => ({ | ||
depth: Math.random() * 2 + 0.5, | ||
className: `${positions[i]} ${bgColors[i]} w-32 h-32 rounded-xl cursor-pointer`, | ||
})) | ||
|
||
export default function StickersPage() { | ||
const scope = useRef(null) | ||
|
||
return ( | ||
<LandingLayoutView> | ||
<div className="w-full h-screen overflow-hidden" ref={scope}> | ||
<Floating className="" sensitivity={2}> | ||
{stickers.map((sticker, index) => ( | ||
<FloatingElement | ||
key={index} | ||
className={sticker.className} | ||
depth={sticker.depth} | ||
> | ||
<motion.div | ||
className="w-full h-full" | ||
initial={{ opacity: 0, scale: 0.8 }} | ||
animate={{ opacity: 1, scale: 1 }} | ||
transition={{ delay: index * 0.1 }} | ||
whileHover={{ scale: 1.1 }} | ||
whileTap={{ scale: 0.95 }} | ||
/> | ||
</FloatingElement> | ||
))} | ||
</Floating> | ||
<div className="absolute inset-0 flex items-center justify-center"> | ||
<motion.div | ||
className="z-50 text-center space-y-4 items-center flex flex-col" | ||
initial={{ opacity: 0, y: 10 }} | ||
animate={{ opacity: 1, y: 0 }} | ||
transition={{ duration: 0.88, delay: 0.5 }} | ||
> | ||
<p className="text-5xl md:text-7xl z-50 text-white font-calendas italic"> | ||
Stickers | ||
</p> | ||
</motion.div> | ||
</div> | ||
</div> | ||
</LandingLayoutView> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
"use client" | ||
|
||
import { | ||
createContext, | ||
ReactNode, | ||
useCallback, | ||
useContext, | ||
useEffect, | ||
useRef, | ||
} from "react" | ||
import { useAnimationFrame } from "motion/react" | ||
|
||
import { cn } from "@hhs/utils/cn" | ||
import { useMousePositionRef } from "@hhs/hooks/use-mouse-position-ref" | ||
|
||
interface FloatingContextType { | ||
registerElement: (id: string, element: HTMLDivElement, depth: number) => void | ||
unregisterElement: (id: string) => void | ||
} | ||
|
||
const FloatingContext = createContext<FloatingContextType | null>(null) | ||
|
||
interface FloatingProps { | ||
children: ReactNode | ||
className?: string | ||
sensitivity?: number | ||
easingFactor?: number | ||
} | ||
|
||
const Floating = ({ | ||
children, | ||
className, | ||
sensitivity = 1, | ||
easingFactor = 0.05, | ||
...props | ||
}: FloatingProps) => { | ||
const containerRef = useRef<HTMLDivElement>(null) | ||
const elementsMap = useRef( | ||
new Map< | ||
string, | ||
{ | ||
element: HTMLDivElement | ||
depth: number | ||
currentPosition: { x: number; y: number } | ||
} | ||
>() | ||
) | ||
const mousePositionRef = useMousePositionRef(containerRef) | ||
|
||
const registerElement = useCallback( | ||
(id: string, element: HTMLDivElement, depth: number) => { | ||
elementsMap.current.set(id, { | ||
element, | ||
depth, | ||
currentPosition: { x: 0, y: 0 }, | ||
}) | ||
}, | ||
[] | ||
) | ||
|
||
const unregisterElement = useCallback((id: string) => { | ||
elementsMap.current.delete(id) | ||
}, []) | ||
|
||
useAnimationFrame(() => { | ||
if (!containerRef.current) return | ||
|
||
elementsMap.current.forEach((data) => { | ||
const strength = (data.depth * sensitivity) / 20 | ||
|
||
// Calculate new target position | ||
const newTargetX = mousePositionRef.current.x * strength | ||
const newTargetY = mousePositionRef.current.y * strength | ||
|
||
// Check if we need to update | ||
const dx = newTargetX - data.currentPosition.x | ||
const dy = newTargetY - data.currentPosition.y | ||
|
||
// Update position only if we're still moving | ||
data.currentPosition.x += dx * easingFactor | ||
data.currentPosition.y += dy * easingFactor | ||
|
||
data.element.style.transform = `translate3d(${data.currentPosition.x}px, ${data.currentPosition.y}px, 0)` | ||
}) | ||
}) | ||
|
||
return ( | ||
<FloatingContext.Provider value={{ registerElement, unregisterElement }}> | ||
<div | ||
ref={containerRef} | ||
className={cn("absolute top-0 left-0 w-full h-full", className)} | ||
{...props} | ||
> | ||
{children} | ||
</div> | ||
</FloatingContext.Provider> | ||
) | ||
} | ||
|
||
export default Floating | ||
|
||
interface FloatingElementProps { | ||
children: ReactNode | ||
className?: string | ||
depth?: number | ||
} | ||
|
||
export const FloatingElement = ({ | ||
children, | ||
className, | ||
depth = 1, | ||
}: FloatingElementProps) => { | ||
const elementRef = useRef<HTMLDivElement>(null) | ||
const idRef = useRef(Math.random().toString(36).substring(7)) | ||
const context = useContext(FloatingContext) | ||
|
||
useEffect(() => { | ||
if (!elementRef.current || !context) return | ||
|
||
const nonNullDepth = depth ?? 0.01 | ||
|
||
context.registerElement(idRef.current, elementRef.current, nonNullDepth) | ||
return () => context.unregisterElement(idRef.current) | ||
}, [depth]) | ||
|
||
return ( | ||
<div | ||
ref={elementRef} | ||
className={cn("absolute will-change-transform", className)} | ||
> | ||
{children} | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { RefObject, useEffect, useRef } from "react" | ||
|
||
export const useMousePositionRef = ( | ||
containerRef?: RefObject<HTMLElement | SVGElement> | ||
) => { | ||
const positionRef = useRef({ x: 0, y: 0 }) | ||
|
||
useEffect(() => { | ||
const updatePosition = (x: number, y: number) => { | ||
if (containerRef && containerRef.current) { | ||
const rect = containerRef.current.getBoundingClientRect() | ||
const relativeX = x - rect.left | ||
const relativeY = y - rect.top | ||
|
||
// Calculate relative position even when outside the container | ||
positionRef.current = { x: relativeX, y: relativeY } | ||
} else { | ||
positionRef.current = { x, y } | ||
} | ||
} | ||
|
||
const handleMouseMove = (ev: MouseEvent) => { | ||
updatePosition(ev.clientX, ev.clientY) | ||
} | ||
|
||
const handleTouchMove = (ev: TouchEvent) => { | ||
const touch = ev.touches[0] | ||
updatePosition(touch.clientX, touch.clientY) | ||
} | ||
|
||
// Listen for both mouse and touch events | ||
window.addEventListener("mousemove", handleMouseMove) | ||
window.addEventListener("touchmove", handleTouchMove) | ||
|
||
return () => { | ||
window.removeEventListener("mousemove", handleMouseMove) | ||
window.removeEventListener("touchmove", handleTouchMove) | ||
} | ||
}, [containerRef]) | ||
|
||
return positionRef | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters