Some checks failed
Deploy to Cloudflare Workers (OpenNext) / deploy (push) Has been cancelled
Add a bottom cut-out panel with larger winner cards (four visible), move summit dates to Feb 21–22 2027, and limit demo Antebas to letters so symbols render via DM Sans. Co-authored-by: Cursor <cursoragent@cursor.com>
186 lines
5.2 KiB
TypeScript
186 lines
5.2 KiB
TypeScript
"use client";
|
|
|
|
import { useCallback, useEffect, useState, type CSSProperties } 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";
|
|
size?: "default" | "large";
|
|
/** How many company marks fit in the visible strip (marquee viewport). */
|
|
visibleCount?: number;
|
|
className?: string;
|
|
};
|
|
|
|
export function LastYearWinnersScroll({
|
|
variant = "on-green",
|
|
size = "default",
|
|
visibleCount = 4,
|
|
className,
|
|
}: Props) {
|
|
const items = [...lastYearWinners, ...lastYearWinners];
|
|
const onLight = variant === "on-light";
|
|
const isLarge = size === "large";
|
|
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]);
|
|
|
|
const content = (
|
|
<>
|
|
<p
|
|
className={cn(
|
|
"text-center font-semibold uppercase tracking-[0.2em] text-[#37a47a]",
|
|
isLarge ? "text-xs md:text-sm" : "text-[10px]"
|
|
)}
|
|
>
|
|
{lastYearWinnersCopy.eyebrow}
|
|
</p>
|
|
<p
|
|
className={cn(
|
|
"mt-2 text-center font-bold tracking-tight",
|
|
isLarge ? "text-xl md:text-2xl" : "text-lg md:text-xl",
|
|
onLight ? "text-[#30614c]" : "text-white"
|
|
)}
|
|
>
|
|
{lastYearWinnersCopy.headline}
|
|
</p>
|
|
<p className="mt-2 text-center">
|
|
<span
|
|
className={cn(
|
|
"inline-block rounded-full bg-white font-medium text-[#37a47a] shadow-sm",
|
|
isLarge ? "px-4 py-1.5 text-xs md:text-sm" : "px-3 py-1 text-[11px]"
|
|
)}
|
|
>
|
|
{lastYearWinnersCopy.hoverHint}
|
|
</span>
|
|
</p>
|
|
<div
|
|
className={cn(
|
|
"relative mt-5 overflow-hidden",
|
|
isLarge && "hero-winners-viewport mx-auto"
|
|
)}
|
|
style={
|
|
isLarge
|
|
? ({
|
|
"--winners-visible": visibleCount,
|
|
} as CSSProperties)
|
|
: undefined
|
|
}
|
|
aria-label="Companies supported at last year's summit"
|
|
>
|
|
<div
|
|
className={cn(
|
|
"pointer-events-none absolute inset-y-0 left-0 z-10 w-12 bg-gradient-to-r to-transparent md:w-16",
|
|
onLight ? "from-[#e8f2ec]" : "from-[#37a47a]"
|
|
)}
|
|
/>
|
|
<div
|
|
className={cn(
|
|
"pointer-events-none absolute inset-y-0 right-0 z-10 w-12 bg-gradient-to-l to-transparent md:w-16",
|
|
onLight ? "from-[#e8f2ec]" : "from-[#37a47a]"
|
|
)}
|
|
/>
|
|
<div
|
|
className={cn(
|
|
"marquee-winners pointer-events-auto flex w-max shrink-0 items-center py-1",
|
|
isLarge ? "gap-3 md:gap-4" : "gap-3",
|
|
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}
|
|
size={size}
|
|
isOpen={isOpen}
|
|
isPinned={isPinned}
|
|
onPin={() => pinTip(tipKey)}
|
|
onUnpin={unpinTip}
|
|
onHover={setHover}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
|
|
if (onLight && isLarge) {
|
|
return (
|
|
<div className={cn("relative mt-auto w-full", className)}>
|
|
<div className="hero-winners-cutout relative mx-auto w-full max-w-6xl">
|
|
<div
|
|
className={cn(
|
|
"rounded-t-[1.75rem] border border-[#37a47a]/12 border-b-0",
|
|
"bg-[#e8f2ec]/95 px-4 pt-7 pb-6 shadow-[0_-10px_40px_rgba(48,97,76,0.08)]",
|
|
"md:rounded-t-[2.25rem] md:px-8 md:pt-8 md:pb-7"
|
|
)}
|
|
>
|
|
{content}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"pt-8",
|
|
onLight ? "mt-8 border-t border-[#37a47a]/12" : "mt-10 border-t border-white/15",
|
|
className
|
|
)}
|
|
>
|
|
{content}
|
|
</div>
|
|
);
|
|
}
|