GRV-Summit-Site/components/home/LastYearWinnersScroll.tsx
kirukib cb404ec079
Some checks failed
Deploy to Cloudflare Workers (OpenNext) / deploy (push) Has been cancelled
Align site colors with GRV brand book palette.
Centralize primary, secondary, tertiary, and neutral tokens and apply them across theme variables and UI components.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 14:45:22 +03:00

134 lines
3.8 KiB
TypeScript

"use client";
import { useCallback, useEffect, useState } from "react";
import { lastYearWinners, lastYearWinnersCopy } from "@/content/last-year-winners";
import { LastYearWinnerMark } from "@/components/home/LastYearWinnerMark";
import { cn } from "@/lib/utils";
type Props = {
variant?: "on-green" | "on-light";
className?: string;
};
export function LastYearWinnersScroll({ variant = "on-green", className }: Props) {
const items = [...lastYearWinners, ...lastYearWinners];
const onLight = variant === "on-light";
const [hoverKey, setHoverKey] = useState<string | null>(null);
const [pinnedKey, setPinnedKey] = useState<string | null>(null);
const isPaused = pinnedKey !== null;
const pinTip = useCallback((key: string) => {
setPinnedKey(key);
setHoverKey(null);
}, []);
const unpinTip = useCallback(() => {
setPinnedKey(null);
}, []);
const setHover = useCallback(
(key: string | null) => {
if (pinnedKey) return;
setHoverKey(key);
},
[pinnedKey]
);
useEffect(() => {
if (!pinnedKey) return;
const dismiss = () => setPinnedKey(null);
const onPointerDown = (e: PointerEvent) => {
const target = e.target as HTMLElement;
if (target.closest("[data-winner-interactive]")) return;
dismiss();
};
const onScroll = () => dismiss();
document.addEventListener("pointerdown", onPointerDown, true);
window.addEventListener("scroll", onScroll, true);
return () => {
document.removeEventListener("pointerdown", onPointerDown, true);
window.removeEventListener("scroll", onScroll, true);
};
}, [pinnedKey]);
return (
<div
className={cn(
"pt-8",
onLight ? "mt-8 border-t border-[#37a47a]/12" : "mt-10 border-t border-white/15",
className
)}
>
<p
className={cn(
"text-center text-[10px] font-semibold uppercase tracking-[0.2em] text-[#37a47a]"
)}
>
{lastYearWinnersCopy.eyebrow}
</p>
<p
className={cn(
"mt-2 text-center text-lg font-bold tracking-tight md:text-xl",
onLight ? "text-[#30614c]" : "text-white"
)}
>
{lastYearWinnersCopy.headline}
</p>
<p className="mt-2 text-center">
<span className="inline-block rounded-full bg-white px-3 py-1 text-[11px] font-medium text-[#37a47a] shadow-sm">
{lastYearWinnersCopy.hoverHint}
</span>
</p>
<div
className="relative mt-5 overflow-hidden"
aria-label="Companies supported at last year's summit"
>
<div
className={cn(
"pointer-events-none absolute inset-y-0 left-0 z-10 w-10 bg-gradient-to-r to-transparent",
onLight ? "from-white" : "from-[#37a47a]"
)}
/>
<div
className={cn(
"pointer-events-none absolute inset-y-0 right-0 z-10 w-10 bg-gradient-to-l to-transparent",
onLight ? "from-white" : "from-[#37a47a]"
)}
/>
<div
className={cn(
"marquee-winners pointer-events-auto flex w-max shrink-0 items-center gap-3 py-1",
isPaused && "marquee-winners-paused"
)}
>
{items.map((company, i) => {
const tipKey = `${company.id}-${i}`;
const isPinned = pinnedKey === tipKey;
const isOpen = isPinned || (!pinnedKey && hoverKey === tipKey);
return (
<LastYearWinnerMark
key={tipKey}
tipKey={tipKey}
company={company}
variant={variant}
isOpen={isOpen}
isPinned={isPinned}
onPin={() => pinTip(tipKey)}
onUnpin={unpinTip}
onHover={setHover}
/>
);
})}
</div>
</div>
</div>
);
}