// @creds: https://codepen.io/jh3y/pen/BabOBzv
import React from 'react'
import { applyPropsToChildrenOfType } from '@/utils/manipulateReactComponents'
type MarqueeProps = {
children: React.ReactNode
blur?: boolean
speed?: number
blurIntensity?: number
blurs?: number
state: 'running' | 'paused'
}
type MarqueeBlurProps = {
count: number
}
type MarqueeItemProps = {
children: React.ReactNode
index: number
}
const styles = {
blur: 'absolute top-0 bottom-0 w-[--mask-fade-distance] max-w-[70px] md:max-w-[240px] z-[2] pointer-events-none [.marquee[data-blurring="true"]_&]:opacity-100 [&>div]:backdrop-blur-[calc((var(--index,0)*var(--blur,0))*2px)] [&>div]:[mask-image:linear-gradient(90deg,transparent_calc(var(--index)*calc((100/var(--blurs))*1%)),black_calc((var(--index)+1)*calc((100/var(--blurs))*1%)),black_calc((var(--index)+2)*calc((100/var(--blurs))*1%)),transparent_calc((var(--index)+3)*calc((100/var(--blurs))*1%)))]',
mask: '[--mask-fade-distance:160px] [mask-image:_linear-gradient(to_right,transparent_0,_black_var(--mask-fade-distance),_black_calc(100%-var(--mask-fade-distance)),transparent_100%)]',
vars: '[--mask-fade-distance:160px]'
}
const MarqueeBlur: React.FC<MarqueeBlurProps & { position?: 'left' | 'right' }> = ({ count, position = 'left' }) => {
const positionStyle = position === 'left' ? 'left-0 !rotate-180' : 'right-0'
return (
<div className={`${styles.blur} ${positionStyle}`}>
{[...Array(count)].map((_, index) => (
<div key={index} className="absolute inset-0 z-[--index]" style={{ '--index': index } as React.CSSProperties} />
))}
</div>
)
}
const Marquee: React.FC<MarqueeProps> = ({ children, blur = true, speed = 20, blurIntensity = 1, blurs = 8, state = 'running' }) => {
children = applyPropsToChildrenOfType(children, {}, MarqueeItem, { includeIndex: true })
return (
<>
<style>{`
@keyframes marquee {
100% {
translate: var(--destination-x) var(--destination-y);
}
}
`}</style>
<div
className={`marquee group relative min-h-[100px] w-full min-w-[300px] ${styles.vars}`}
data-direction="horizontal"
data-blurring={blur}
data-play-state={state}
style={
{
'--speed': speed,
'--count': React.Children.count(children),
'--blur': blurIntensity,
'--blurs': blurs
} as React.CSSProperties
}>
<div className={`grid min-h-[100px] w-fit min-w-[300px] ${styles.mask}`}>
{blur ? <MarqueeBlur position="left" count={React.Children.count(children)} /> : null}
<ul className="pointer-events-none flex h-full w-fit items-center p-0 group-data-[play-state='paused']:![animation-play-state:paused] group-data-[play-state='running']:![animation-play-state:running]">
{children}
</ul>
{blur ? <MarqueeBlur position="right" count={React.Children.count(children)} /> : null}
</div>
</div>
</>
)
}
Marquee.displayName = 'Marquee'
const MarqueeItem: React.FC<MarqueeItemProps> = ({ children, index }) => (
<li
key={`index--${index}`}
style={{ '--index': index } as React.CSSProperties}
className="grid aspect-video h-[80%] animate-[marquee_var(--duration)_var(--delay)_infinite_linear_paused] place-items-center text-[clamp(2rem,4vw+1rem,8rem)] [--delay:calc((var(--duration)/var(--count))*(var(--index,0)*-1))] [--destination-x:calc(calc((var(--index)+1+var(--outset,0))*-100%))] [--destination-y:0] [--duration:calc(var(--speed)*1s)] [--origin-x:calc(((var(--count)-var(--index))+var(--inset,0))*100%)] [--origin-y:0] [translate:var(--origin-x)_var(--origin-y)] group-data-[play-state='paused']:![animation-play-state:paused] group-data-[play-state='running']:![animation-play-state:running]">
{children}
</li>
)
MarqueeItem.displayName = 'MarqueeItem'
export { Marquee, MarqueeItem }