Skip to content

Commit

Permalink
add stickers page - fix HappyHackingSpace#53
Browse files Browse the repository at this point in the history
  • Loading branch information
umutbasal committed Jan 15, 2025
1 parent 42e42b0 commit db5bda4
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 0 deletions.
74 changes: 74 additions & 0 deletions app/stickers/page.tsx
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>
)
}
Binary file modified bun.lockb
Binary file not shown.
134 changes: 134 additions & 0 deletions components/fancy/parallax-floating.tsx
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>
)
}
4 changes: 4 additions & 0 deletions constants/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export const NAV_ITEMS = [
label: "Live",
href: "/live",
},
{
label: "Stickers",
href: "/stickers",
},
{
label: "HHS",
href: "#",
Expand Down
42 changes: 42 additions & 0 deletions hooks/use-mouse-position-ref.ts
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
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"ical-generator": "^8.0.1",
"lodash.debounce": "^4.0.8",
"lucide-react": "^0.447.0",
"motion": "^11.18.0",
"next": "14.2.21",
"next-themes": "^0.3.0",
"react": "^18",
Expand Down

0 comments on commit db5bda4

Please sign in to comment.