Some checks are pending
Deploy to Cloudflare Workers (OpenNext) / deploy (push) Waiting to run
Replace hero pan animations with gentle opacity breathing, add bottom-right consent modal with accept/decline, condense privacy policy with contact for full details, and ensure ticket card descriptions read green on white cards in the tickets section. Co-authored-by: Cursor <cursoragent@cursor.com>
133 lines
4.7 KiB
TypeScript
133 lines
4.7 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { ArrowRight, Ticket } from "lucide-react";
|
|
import type { TicketTier } from "@/content/tickets";
|
|
import { site } from "@/content/site";
|
|
import { TicketInclusionsPopover } from "@/components/tickets/TicketInclusionsPopover";
|
|
import { ScrollReveal } from "@/components/motion/ScrollReveal";
|
|
import { Button } from "@/components/ui/button";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type Props = {
|
|
tier: TicketTier;
|
|
index: number;
|
|
featured?: boolean;
|
|
/** Lighter layout for /payment tier picker */
|
|
compact?: boolean;
|
|
};
|
|
|
|
/** One-line summary for the card face (not the popover) */
|
|
function ticketTagline(tier: TicketTier): string {
|
|
return tier.features[0] ?? tier.description;
|
|
}
|
|
|
|
export function TicketCard({ tier, index, featured, compact }: Props) {
|
|
const price =
|
|
tier.priceLabel ?? (tier.priceUsd === 0 ? "Free" : `$${tier.priceUsd}`);
|
|
const serial = `GRV-${tier.id.slice(0, 3).toUpperCase()}-${1000 + index}`;
|
|
const schedule = tier.scheduleLabel ?? site.dates.label;
|
|
|
|
return (
|
|
<ScrollReveal
|
|
variant="card"
|
|
delay={index * 100}
|
|
as="article"
|
|
className={cn(
|
|
"flex w-full",
|
|
featured && !compact && "md:-mt-3 md:mb-1 md:scale-[1.02]"
|
|
)}
|
|
>
|
|
<div
|
|
className={cn(
|
|
"topo-card-surface ticket-admission relative w-full overflow-hidden bg-white text-[#0f0404]",
|
|
"shadow-[0_12px_40px_rgba(0,0,0,0.18)]",
|
|
featured && "ring-2 ring-[#ffb300]/50"
|
|
)}
|
|
data-ticket-notch={compact ? "light" : "inverse"}
|
|
>
|
|
{featured && (
|
|
<span className="absolute right-3 top-3 z-10 rounded-full bg-[#ffb300] px-2.5 py-0.5 text-[10px] font-bold uppercase tracking-wider text-[#0f0404]">
|
|
Popular
|
|
</span>
|
|
)}
|
|
|
|
<div
|
|
className={cn(
|
|
"flex flex-col md:flex-row",
|
|
featured && "bg-gradient-to-br from-white via-white to-[#fff9eb]"
|
|
)}
|
|
>
|
|
{/* Stub — price & date only */}
|
|
<div
|
|
className={cn(
|
|
"relative flex shrink-0 md:w-[30%]",
|
|
"border-b border-dashed border-[#1a5c38]/20 md:border-b-0 md:border-r"
|
|
)}
|
|
>
|
|
<div className="flex w-full flex-row items-center justify-between gap-3 p-4 md:flex-col md:items-stretch md:justify-between md:py-5 md:pl-5 md:pr-4">
|
|
<div className="flex items-center gap-2 text-[#1a5c38] md:flex-col md:items-start">
|
|
<Ticket className="size-4 shrink-0" strokeWidth={2} aria-hidden />
|
|
<span className="text-[10px] font-bold uppercase tracking-[0.18em]">
|
|
Admit one
|
|
</span>
|
|
</div>
|
|
<div className="text-right md:text-left">
|
|
<p
|
|
className={cn(
|
|
"font-bold tabular-nums leading-none text-[#1f3d7e]",
|
|
compact ? "text-2xl" : "text-3xl"
|
|
)}
|
|
>
|
|
{price}
|
|
</p>
|
|
<p className="mt-1 text-[10px] font-medium uppercase tracking-wide text-[#3d5248]">
|
|
{schedule}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main — name, one line, details popover, CTA */}
|
|
<div className="flex flex-1 flex-col justify-between gap-4 p-4 md:p-5">
|
|
<div className="min-w-0 pr-8 md:pr-0">
|
|
<h3 className="text-lg font-bold leading-tight text-[#0d3d26] md:text-xl">
|
|
{tier.name}
|
|
</h3>
|
|
<p className="mt-1.5 line-clamp-2 text-sm text-[#3d5248]">
|
|
{ticketTagline(tier)}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-3">
|
|
<TicketInclusionsPopover tier={tier} serial={serial} />
|
|
<Button
|
|
className={cn(
|
|
"w-full rounded-full bg-[#ffb300] text-[#0f0404] hover:bg-[#ffb300]/90",
|
|
featured && "ticket-cta-pulse"
|
|
)}
|
|
disabled={tier.soldOut}
|
|
asChild={!tier.soldOut}
|
|
>
|
|
{tier.soldOut ? (
|
|
<span>Sold out</span>
|
|
) : (
|
|
<Link href={compact ? `/payment?ticket=${tier.id}` : "/payment"}>
|
|
{compact ? "Select" : "Get tickets"}
|
|
<ArrowRight className="size-4" />
|
|
</Link>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
className="h-1 w-full bg-gradient-to-r from-[#1a5c38] via-[#ffb300] to-[#1f3d7e]"
|
|
aria-hidden
|
|
/>
|
|
</div>
|
|
</ScrollReveal>
|
|
);
|
|
}
|