GRV-Summit-Site/components/home/LastYearWinnerMark.tsx
kirukib 03d439e97b
Some checks failed
Deploy to Cloudflare Workers (OpenNext) / deploy (push) Has been cancelled
Update hero alumni strip, 2027 dates, and Antebas font loading.
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>
2026-06-10 00:25:43 +03:00

200 lines
6.1 KiB
TypeScript

"use client";
import { useCallback, useRef, useState } from "react";
import Image from "next/image";
import type { LastYearWinner } from "@/content/last-year-winners";
import { LastYearWinnerTip } from "@/components/home/LastYearWinnerTip";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { cn } from "@/lib/utils";
const GRV_LOGO = "/branding/logo-icon.png";
type Props = {
company: LastYearWinner;
tipKey: string;
isOpen: boolean;
isPinned: boolean;
onPin: () => void;
onUnpin: () => void;
onHover: (key: string | null) => void;
variant?: "on-green" | "on-light";
size?: "default" | "large";
className?: string;
};
export function LastYearWinnerMark({
company,
tipKey,
isOpen,
isPinned,
onPin,
onUnpin,
onHover,
variant = "on-light",
size = "default",
className,
}: Props) {
const leaveTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const [failed, setFailed] = useState(false);
const src = company.logoSrc ?? GRV_LOGO;
const showImage = !failed && src;
const logoOnly = !company.name;
const onLight = variant === "on-light";
const isLarge = size === "large";
const logoPx = isLarge ? 48 : 28;
const clearLeaveTimer = useCallback(() => {
if (leaveTimer.current) {
clearTimeout(leaveTimer.current);
leaveTimer.current = null;
}
}, []);
const handleEnter = useCallback(() => {
clearLeaveTimer();
onHover(tipKey);
}, [clearLeaveTimer, onHover, tipKey]);
const handleLeave = useCallback(() => {
if (isPinned) return;
clearLeaveTimer();
leaveTimer.current = setTimeout(() => onHover(null), 120);
}, [clearLeaveTimer, isPinned, onHover]);
const handleClick = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation();
onPin();
},
[onPin]
);
const handleOpenChange = useCallback(
(open: boolean) => {
if (!open && !isPinned) onHover(null);
},
[isPinned, onHover]
);
return (
<Popover open={isOpen} onOpenChange={handleOpenChange}>
<PopoverTrigger asChild>
<button
type="button"
data-winner-interactive
className={cn(
"shrink-0 rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-[#37a47a]/40",
isOpen && "relative z-30"
)}
aria-label={
company.name
? `${company.name} — tap to read impact details`
: "GRV alumni — tap to read impact details"
}
aria-expanded={isOpen}
onClick={handleClick}
onMouseEnter={handleEnter}
onMouseLeave={handleLeave}
onFocus={handleEnter}
onBlur={handleLeave}
>
<div
className={cn(
"flex shrink-0 items-center gap-2.5 rounded-xl border shadow-sm transition-colors",
isLarge
? "h-14 min-w-[10.5rem] gap-2.5 px-3 sm:h-16 sm:min-w-[12.75rem] sm:gap-3 sm:px-3.5 md:h-[4.5rem] md:min-w-[13.25rem] md:px-4"
: "h-10 gap-2 rounded-lg px-2.5",
onLight
? "border-[#37a47a]/15 bg-white/95 hover:border-[#37a47a]/30 hover:bg-white"
: "border-white/25 bg-white/90 hover:border-white/50 hover:bg-white",
logoOnly && (isLarge ? "px-3" : "px-2"),
isOpen &&
(onLight
? "border-[#37a47a]/40 ring-2 ring-[#37a47a]/15"
: "border-white/60 ring-2 ring-white/25"),
isPinned && "border-[#37a47a]/70 ring-2 ring-[#37a47a]/25",
className
)}
>
<div
className={cn(
"relative flex shrink-0 items-center justify-center overflow-hidden rounded-lg bg-white",
isLarge ? "size-11 md:size-12" : "size-7 rounded-md"
)}
>
{showImage ? (
<Image
src={src}
alt=""
width={logoPx}
height={logoPx}
className={cn("object-contain p-0.5", isLarge ? "size-11 md:size-12" : "size-7")}
onError={() => setFailed(true)}
/>
) : company.initials ? (
<span
className={cn(
"font-bold leading-none text-[#37a47a]",
isLarge ? "text-sm md:text-base" : "text-[10px]"
)}
>
{company.initials}
</span>
) : (
<Image
src={GRV_LOGO}
alt=""
width={logoPx}
height={logoPx}
className={cn("object-contain p-0.5", isLarge ? "size-11 md:size-12" : "size-7")}
/>
)}
</div>
{company.name ? (
<span
className={cn(
"truncate font-medium text-[#37a47a]",
isLarge
? "max-w-[8.5rem] text-sm md:max-w-[9.5rem] md:text-base"
: "max-w-[7.5rem] text-[11px]"
)}
>
{company.name}
</span>
) : null}
</div>
</button>
</PopoverTrigger>
{isOpen ? (
<PopoverContent
side="top"
align="center"
sideOffset={8}
collisionPadding={12}
data-winner-interactive
className={cn(
"z-50 max-w-[calc(100vw-2rem)] shrink-0 overflow-hidden border-[#37a47a]/15 bg-white p-0 shadow-lg",
isLarge ? "w-[22rem] md:w-96" : "w-72"
)}
onMouseEnter={handleEnter}
onMouseLeave={handleLeave}
onOpenAutoFocus={(e) => e.preventDefault()}
onPointerDownOutside={(e) => {
if (isPinned) e.preventDefault();
}}
onInteractOutside={(e) => {
if (isPinned) e.preventDefault();
}}
>
<LastYearWinnerTip
company={company}
onClose={onUnpin}
showClose={isPinned}
size={size}
/>
</PopoverContent>
) : null}
</Popover>
);
}