since3-website/modules/landingPage/ui/components/marquee-band.tsx
2026-09-25 00:45:33 +02:00

94 lines
2.6 KiB
TypeScript

"use client"
import { useRef } from "react"
import {
motion,
useAnimationFrame,
useMotionValue,
useReducedMotion,
useScroll,
useSpring,
useTransform,
useVelocity,
} from "motion/react"
import { cn } from "@/lib/utils"
function wrap(min: number, max: number, value: number) {
const range = max - min
return ((((value - min) % range) + range) % range) + min
}
/**
* Endless text band. Scrolling speeds it up, scrolling up reverses it.
* `baseVelocity` is in percent of one copy per second.
*/
function VelocityRow({
items,
baseVelocity,
className,
}: {
items: string[]
baseVelocity: number
className?: string
}) {
const reduced = useReducedMotion()
const baseX = useMotionValue(0)
const { scrollY } = useScroll()
const velocity = useVelocity(scrollY)
const smoothVelocity = useSpring(velocity, { damping: 50, stiffness: 400 })
const factor = useTransform(smoothVelocity, [0, 1000], [0, 4], { clamp: false })
const x = useTransform(baseX, (value) => `${wrap(-50, 0, value)}%`)
const direction = useRef(1)
useAnimationFrame((_, delta) => {
if (reduced) return
const f = factor.get()
if (f < 0) direction.current = -1
else if (f > 0) direction.current = 1
let move = direction.current * baseVelocity * (delta / 1000)
move += move * Math.abs(f)
baseX.set(baseX.get() + move)
})
const content = (
<span className="flex shrink-0 items-center">
{items.map((item, index) => (
<span key={index} className="flex items-center">
<span className="px-[0.35em]">{item}</span>
<span className="text-[0.5em]">✦</span>
</span>
))}
</span>
)
return (
<div className={cn("flex overflow-clip whitespace-nowrap", className)}>
<motion.div style={{ x }} className="flex w-max">
{content}
{content}
</motion.div>
</div>
)
}
/** Two crossing bands between hero and statement. */
export function MarqueeBand({ items }: { items: string[] }) {
return (
<div aria-hidden="true" className="relative h-[clamp(9rem,20vw,19rem)] overflow-x-clip bg-black">
<div className="absolute inset-x-[-5%] top-1/2 -translate-y-1/2 rotate-3">
<VelocityRow
items={items}
baseVelocity={-2}
className="py-2 text-display type-poster text-outline [--stroke-color:var(--color-ink-600)]"
/>
</div>
<div
data-theme="light"
className="absolute inset-x-[-5%] top-1/2 -translate-y-1/2 -rotate-2 shadow-[0_30px_80px_-20px_rgb(0_0_0/0.8)]"
>
<VelocityRow items={items} baseVelocity={3} className="py-3 text-display type-poster" />
</div>
</div>
)
}