fix: add lan and switchable supporter and visitor tickets (#133)

* update sponsor social media link

* add ingame to sponsors

* Feature - Changed supporter ticket to LAN area ticket.

* feat: switchable ticket card (#132)

* update sponsor social media link (#131)

* update sponsor social media link

* add ingame to sponsors

* feat: add switchable-ticket-card, supporter ticket

---------

Co-authored-by: Katri <96204214+SwagMuffin88@users.noreply.github.com>

* fix: translations got reverted

* Fix styling for ticket switcher

---------

Co-authored-by: Katri Lotamõis <katrilotamois@outlook.com>
Co-authored-by: Rene Arumetsa <rene.arumetsa@gmail.com>
Co-authored-by: Katri <96204214+SwagMuffin88@users.noreply.github.com>
This commit is contained in:
2026-08-03 21:01:38 +03:00
committed by GitHub
parent 79d14aa8fd
commit f42b893dcb
5 changed files with 377 additions and 18 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

View File

@@ -2,6 +2,7 @@ import { vipnagorgialla } from "@/components/Vipnagorgialla";
import Link from "next/link"; import Link from "next/link";
import Image from "next/image"; import Image from "next/image";
import { getTranslations, setRequestLocale } from "next-intl/server"; import { getTranslations, setRequestLocale } from "next-intl/server";
import SwitchableTicketCard from "@/components/SwitchableTicketCard";
interface TicketCardProps { interface TicketCardProps {
title: string; title: string;
@@ -12,6 +13,7 @@ interface TicketCardProps {
buttonHref: string; buttonHref: string;
backgroundImage?: string; backgroundImage?: string;
backgroundOpacity?: number; backgroundOpacity?: number;
blueTinge?: boolean;
className?: string; className?: string;
} }
@@ -24,6 +26,7 @@ function TicketCard({
buttonHref, buttonHref,
backgroundImage, backgroundImage,
backgroundOpacity = 40, backgroundOpacity = 40,
blueTinge = false,
className = "", className = "",
}: TicketCardProps) { }: TicketCardProps) {
return ( return (
@@ -39,6 +42,9 @@ function TicketCard({
style={{ opacity: backgroundOpacity / 100 }} style={{ opacity: backgroundOpacity / 100 }}
/> />
)} )}
{blueTinge && (
<div className="absolute inset-0 bg-[#005A8C]/30" />
)}
<div className="relative z-10 flex flex-col h-full"> <div className="relative z-10 flex flex-col h-full">
<h2 <h2
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2rem,1.5rem+2vw,3rem)] leading-none text-[#00A3E0] uppercase`} className={`${vipnagorgialla.className} font-bold italic text-[clamp(2rem,1.5rem+2vw,3rem)] leading-none text-[#00A3E0] uppercase`}
@@ -72,12 +78,12 @@ function TicketCard({
key={index} key={index}
className="flex items-start gap-2 text-[#EEE5E5] text-base" className="flex items-start gap-2 text-[#EEE5E5] text-base"
> >
<span className="w-1 h-full min-h-5 bg-[#00A3E0] shrink-0" /> <span className="w-1 self-stretch min-h-5 bg-[#00A3E0] shrink-0" />
<span>{feature}</span> <span>{feature}</span>
</li> </li>
))} ))}
</ul> </ul>
<Link href={buttonHref} target="_blank"> <Link href={buttonHref} target="_blank" className="w-fit">
<button <button
className={`px-4 py-2 border-4 border-transparent bg-[#00A3E0] hover:bg-[#E5E5EE] text-[#0A121F] cursor-pointer ${vipnagorgialla.className} font-bold italic leading-none uppercase transition`} className={`px-4 py-2 border-4 border-transparent bg-[#00A3E0] hover:bg-[#E5E5EE] text-[#0A121F] cursor-pointer ${vipnagorgialla.className} font-bold italic leading-none uppercase transition`}
> >
@@ -101,30 +107,41 @@ export default async function Tickets({
return ( return (
<div className="bg-[#0E0F19] min-h-0 flex flex-col flex-1"> <div className="bg-[#0E0F19] min-h-0 flex flex-col flex-1">
{/* 2x2 Grid */} {/* 2x2 Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 md:grid-rows-2 auto-rows-fr pt-14 flex-1 min-h-0"> <div className="grid grid-cols-1 lg:grid-cols-2 lg:grid-rows-2 auto-rows-fr pt-14 flex-1 min-h-0">
{/* KÜLASTAJA PILET */}
<TicketCard {/* KÜLASTAJA / TOETAJA — switchable */}
title={t("tickets.visitor.name")} <SwitchableTicketCard
subtitle={t("tickets.subtitle")} optionA={{
price={t("tickets.visitor.price")} title: t("tickets.visitor.name"),
features={t.raw("tickets.visitor.features")} subtitle: t("tickets.subtitle"),
price: t("tickets.visitor.price"),
features: t.raw("tickets.visitor.features"),
}}
optionB={{
title: t("tickets.supporter.name"),
subtitle: t("tickets.subtitle"),
price: t("tickets.supporter.price"),
features: t.raw("tickets.supporter.features"),
}}
buttonText={t("tickets.buyButton")} buttonText={t("tickets.buyButton")}
buttonHref="https://fienta.com/et/tipilan" buttonHref="https://fienta.com/et/tipilan"
backgroundImage="/images/landing/visitor_tournament.jpg" backgroundImage="/images/landing/visitor_tournament.jpg"
backgroundOpacity={30} backgroundImageB="/images/landing/main_supporter.jpg"
className="border-b-[3px] md:border-b-[3px] md:border-r-[3px]" backgroundOpacityA={30}
backgroundOpacityB={40}
className="border-b-[3px] lg:border-r-[3px]"
/> />
{/* TOETAJA PILET */} {/* LAN PILET */}
<TicketCard <TicketCard
title={t("tickets.supporter.name")} title={t("tickets.lan.name")}
subtitle={t("tickets.subtitle")} subtitle={t("tickets.lan.subtitle")}
price={t("tickets.supporter.price")} price={t("tickets.lan.price")}
features={t.raw("tickets.supporter.features")} features={t.raw("tickets.lan.features")}
buttonText={t("tickets.buyButton")} buttonText={t("tickets.buyButton")}
buttonHref="https://fienta.com/et/tipilan" buttonHref="https://fienta.com/et/tipilan"
backgroundImage="/images/landing/explore_teaser.png" backgroundImage="/images/landing/explore_teaser.png"
backgroundOpacity={80} backgroundOpacity={100}
className="border-b-[3px]" className="border-b-[3px]"
/> />
@@ -137,7 +154,7 @@ export default async function Tickets({
buttonText={t("tickets.buyButton")} buttonText={t("tickets.buyButton")}
buttonHref="https://fienta.com/et/tipilan" buttonHref="https://fienta.com/et/tipilan"
backgroundImage="/images/landing/league_ticket.jpg" backgroundImage="/images/landing/league_ticket.jpg"
className="border-b-[3px] md:border-b-0 md:border-r-[3px]" className="border-b-[3px] lg:border-b-0 lg:border-r-[3px]"
/> />
{/* CS2 TURNIIRI PILET */} {/* CS2 TURNIIRI PILET */}

View File

@@ -0,0 +1,322 @@
"use client";
import { useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { vipnagorgialla } from "@/components/Vipnagorgialla";
interface TicketData {
title: string;
subtitle: string;
price: string;
features: string[];
}
interface SwitchableTicketCardProps {
optionA: TicketData;
optionB: TicketData;
buttonText: string;
buttonHref: string;
backgroundImage?: string;
backgroundImageB?: string;
backgroundOpacity?: number;
backgroundOpacityA?: number;
backgroundOpacityB?: number;
className?: string;
}
export default function SwitchableTicketCard({
optionA,
optionB,
buttonText,
buttonHref,
backgroundImage,
backgroundImageB,
backgroundOpacity = 40,
backgroundOpacityA,
backgroundOpacityB,
className = "",
}: SwitchableTicketCardProps) {
const [isA, setIsA] = useState(true);
const titleOnRight = !isA;
function renderPrice(price: string) {
return Array.from(price).map((char, index) => (
<span
key={`${char}-${index}`}
className={
char === "€" || char === "+"
? "text-[#00A3E0]"
: "text-[#EEE5E5]"
}
>
{char}
</span>
));
}
function renderFeatures(features: string[]) {
return (
<ul className="flex flex-col gap-2 grow">
{features.map((feature, index) => (
<li
key={index}
className={`flex items-start gap-2 text-[#EEE5E5] text-base ${titleOnRight ? "flex-row-reverse" : ""}`}
>
<span className="w-1 self-stretch min-h-5 bg-[#00A3E0] shrink-0" />
<span className={titleOnRight ? "text-end" : ""}>{feature}</span>
</li>
))}
</ul>
);
}
function renderMobileFeatures(features: string[]) {
return (
<ul className="flex flex-col gap-2 grow mb-6">
{features.map((feature, index) => (
<li
key={index}
className="flex items-start gap-2 text-[#EEE5E5] text-base"
>
<span className="w-1 self-stretch min-h-5 bg-[#00A3E0] shrink-0" />
<span>{feature}</span>
</li>
))}
</ul>
);
}
return (
<div
className={`relative bg-[#0E0F19] border-[#1F5673] px-12 sm:py-16 py-0 flex flex-col min-h-87.5 h-full overflow-hidden ${className}`}
>
{backgroundImage && (
<Image
src={backgroundImage}
alt=""
fill
className="object-cover object-center transition-opacity duration-700 ease-out"
style={{ opacity: isA ? (backgroundOpacityA ?? backgroundOpacity) / 100 : 0 }}
/>
)}
{backgroundImageB && (
<Image
src={backgroundImageB}
alt=""
fill
className="object-cover object-center transition-opacity duration-700 ease-out"
style={{ opacity: isA ? 0 : (backgroundOpacityB ?? backgroundOpacity) / 100 }}
/>
)}
<div className="relative z-10 flex flex-col h-full">
{/* Mobile: Simple slide transition at sm: breakpoint (640px) */}
<div className="sm:hidden relative w-full flex flex-col h-full">
<div className="relative flex-grow overflow-hidden">
<div
className={`absolute inset-0 flex flex-col transition-transform duration-700 ease-out`}
style={{ transform: isA ? "translateX(0)" : "translateX(-100%)" }}
>
<div className="pt-16 pb-8">
<h2
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2rem,1.5rem+2vw,3rem)] leading-none text-[#00A3E0] uppercase`}
>
{optionA.title}
</h2>
<h3
className={`${vipnagorgialla.className} font-bold italic text-2xl text-[#EEE5E5] uppercase mb-4`}
>
{optionA.subtitle}
</h3>
<p
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2.5rem,2rem+2vw,4rem)] leading-none mb-4`}
>
{renderPrice(optionA.price)}
</p>
{renderMobileFeatures(optionA.features)}
</div>
</div>
<div
className={`absolute inset-0 flex flex-col transition-transform duration-700 ease-out`}
style={{ transform: !isA ? "translateX(0)" : "translateX(100%)" }}
>
<div className="pt-16 pb-8">
<h2
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2rem,1.5rem+2vw,3rem)] leading-none text-[#00A3E0] uppercase`}
>
{optionB.title}
</h2>
<h3
className={`${vipnagorgialla.className} font-bold italic text-2xl text-[#EEE5E5] uppercase mb-4`}
>
{optionB.subtitle}
</h3>
<p
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2.5rem,2rem+2vw,4rem)] leading-none mb-4`}
>
{renderPrice(optionB.price)}
</p>
{renderMobileFeatures(optionB.features)}
</div>
</div>
</div>
<div className="pb-16 flex-shrink-0">
<Link href={buttonHref} target="_blank" className="w-fit">
<button
className={`px-4 py-2 border-4 border-transparent bg-[#00A3E0] hover:bg-[#E5E5EE] text-[#0A121F] cursor-pointer ${vipnagorgialla.className} font-bold italic leading-none uppercase transition`}
>
{buttonText}
</button>
</Link>
</div>
</div>
{/* Desktop: Original transition behavior */}
<div className="hidden sm:flex flex-col h-full">
{/* Title + subtitle + price with position shift */}
<div className="flex items-start mb-2 ">
<div
className="transition-[flex-grow] duration-700 ease-out "
style={{ flexGrow: titleOnRight ? 1 : 0 }}
/>
<div className={`grid ${titleOnRight ? "text-end" : ""}`}>
<div
className="col-start-1 row-start-1 transition-opacity duration-500 ease-out"
style={{ opacity: isA ? 1 : 0 }}
>
<h2
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2rem,1.5rem+2vw,3rem)] leading-none text-[#00A3E0] uppercase`}
>
{optionA.title}
</h2>
<h3
className={`${vipnagorgialla.className} font-bold italic text-2xl text-[#EEE5E5] uppercase`}
>
{optionA.subtitle}
</h3>
<p
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2.5rem,2rem+2vw,4rem)] leading-none mt-2`}
>
{renderPrice(optionA.price)}
</p>
</div>
<div
className="col-start-1 row-start-1 transition-opacity duration-500 ease-out pointer-events-none"
style={{ opacity: isA ? 0 : 1 }}
>
<h2
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2rem,1.5rem+2vw,3rem)] leading-none text-[#00A3E0] uppercase`}
>
{optionB.title}
</h2>
<h3
className={`${vipnagorgialla.className} font-bold italic text-2xl text-[#EEE5E5] uppercase`}
>
{optionB.subtitle}
</h3>
<p
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2.5rem,2rem+2vw,4rem)] leading-none mt-2`}
>
{renderPrice(optionB.price)}
</p>
</div>
</div>
<div
className="transition-[flex-grow] duration-700 ease-out "
style={{ flexGrow: titleOnRight ? 0 : 1 }}
/>
</div>
<div className="relative grow">
<div
className={`absolute inset-0 flex flex-col transition-all duration-700 ease-out ${
isA
? "opacity-100 translate-x-0"
: "opacity-0 -translate-x-full"
}`}
>
<div className="flex ">
<div
className="transition-[flex-grow] duration-700 ease-out "
style={{ flexGrow: titleOnRight ? 1 : 0 }}
/>
<div>
{renderFeatures(optionA.features)}
</div>
<div
className="transition-[flex-grow] duration-700 ease-out "
style={{ flexGrow: titleOnRight ? 0 : 1 }}
/>
</div>
</div>
<div
className={`absolute inset-0 flex flex-col transition-all duration-700 ease-out ${
!isA
? "opacity-100 translate-x-0"
: "opacity-0 translate-x-full"
}`}
>
<div className="flex ">
<div
className="transition-[flex-grow] duration-700 ease-out "
style={{ flexGrow: titleOnRight ? 1 : 0 }}
/>
<div>
{renderFeatures(optionB.features)}
</div>
<div
className="transition-[flex-grow] duration-700 ease-out "
style={{ flexGrow: titleOnRight ? 0 : 1 }}
/>
</div>
</div>
</div>
<Link href={buttonHref} target="_blank" className="w-fit">
<button
className={`px-4 py-2 border-4 border-transparent bg-[#00A3E0] hover:bg-[#E5E5EE] text-[#0A121F] cursor-pointer ${vipnagorgialla.className} font-bold italic leading-none uppercase transition`}
>
{buttonText}
</button>
</Link>
</div>
</div>
{/* Chevron buttons on left/right sides */}
<button
onClick={() => setIsA(false)}
className={`${isA ? "flex" : "hidden"} absolute right-0 top-1/2 -translate-y-1/2 w-10 h-10 items-center justify-center bg-[#0E0F19]/50 hover:bg-[#007CAB] text-[#EEE5E5] transition-all duration-300 z-20`}
aria-label={optionB.title}
>
<span className="material-symbols-outlined">chevron_right</span>
</button>
<button
onClick={() => setIsA(true)}
className={`${!isA ? "flex" : "hidden"} absolute left-0 top-1/2 -translate-y-1/2 w-10 h-10 items-center justify-center bg-[#0E0F19]/50 hover:bg-[#007CAB] text-[#EEE5E5] transition-all duration-300 z-20`}
aria-label={optionA.title}
>
<span className="material-symbols-outlined">chevron_left</span>
</button>
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 flex justify-center gap-2 pb-2 z-20">
<button
onClick={() => setIsA(true)}
className={`w-3 h-3 rounded-full transition ${
isA ? "bg-[#00A3E0]" : "bg-[#1F5673] hover:bg-[#007CAB]/60"
}`}
aria-label={optionA.title}
/>
<button
onClick={() => setIsA(false)}
className={`w-3 h-3 rounded-full transition ${
!isA ? "bg-[#00A3E0]" : "bg-[#1F5673] hover:bg-[#007CAB]/60"
}`}
aria-label={optionB.title}
/>
</div>
</div>
);
}

View File

@@ -94,6 +94,16 @@
"Access to all TipiLAN activities, side events, good vibes and engaging atmosphere." "Access to all TipiLAN activities, side events, good vibes and engaging atmosphere."
] ]
}, },
"lan": {
"name": "LAN",
"subtitle": "TICKET",
"price": "10€",
"features": [
"A spot in the TipiLAN free-play area",
"Personal desk, power and internet connection",
"Participation in LAN-area activities and mini-tournaments"
]
},
"supporter": { "supporter": {
"name": "SUPPORTER", "name": "SUPPORTER",
"price": "25+€", "price": "25+€",

View File

@@ -94,6 +94,16 @@
"Juurdepääs kogu TipiLAN melule, tegevustele, kõrvalsündmustele, headele vibe'dele ja kaasahaaravale atmosfäärile." "Juurdepääs kogu TipiLAN melule, tegevustele, kõrvalsündmustele, headele vibe'dele ja kaasahaaravale atmosfäärile."
] ]
}, },
"lan": {
"name": "LAN",
"subtitle": "PILET",
"price": "10€",
"features": [
"Koht TipiLANi vabamängualal",
"Isiklik laud, voolu- ja internetiühendus",
"Osalemine LAN-ala tegevustes ja miniturniiridel"
]
},
"supporter": { "supporter": {
"name": "TOETAJA", "name": "TOETAJA",
"price": "25+€", "price": "25+€",