mirror of
https://github.com/Lapikud/tipilan.git
synced 2026-05-08 10:00:46 +00:00
Tons of fixes to every page
This commit is contained in:
40
package.json
40
package.json
@@ -16,45 +16,45 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@libsql/client": "^0.15.15",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@paralleldrive/cuid2": "^2.3.1",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@types/three": "^0.178.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"drizzle-orm": "^0.44.5",
|
||||
"drizzle-orm": "^0.44.7",
|
||||
"lucide-react": "^0.522.0",
|
||||
"material-symbols": "^0.31.9",
|
||||
"next": "15.3.0",
|
||||
"next-intl": "^4.3.9",
|
||||
"next-intl": "^4.11.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"react-icons": "^5.6.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"three": "^0.178.0",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@tailwindcss/postcss": "^4.1.13",
|
||||
"@types/bun": "^1.2.22",
|
||||
"@types/node": "^20.19.17",
|
||||
"@types/react": "^19.1.13",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"@eslint/eslintrc": "^3.3.5",
|
||||
"@tailwindcss/postcss": "^4.2.4",
|
||||
"@types/bun": "^1.3.13",
|
||||
"@types/node": "^20.19.39",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"autoprefixer": "^10.5.0",
|
||||
"dotenv": "^16.6.1",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"eslint": "^9.36.0",
|
||||
"drizzle-kit": "^0.31.10",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-config-next": "15.3.0",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^4.1.13",
|
||||
"postcss": "^8.5.13",
|
||||
"tailwindcss": "^4.2.4",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.2"
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,11 @@ export default async function LocaleLayout({
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<SidebarParent />
|
||||
{children}
|
||||
<Footer />
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<SidebarParent />
|
||||
<main className="flex-1 flex flex-col">{children}</main>
|
||||
<Footer />
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</NextIntlClientProvider>
|
||||
</div>
|
||||
|
||||
@@ -17,12 +17,12 @@ export default async function Home({
|
||||
return (
|
||||
<div>
|
||||
{/* Hero */}
|
||||
<div className="mt-18">
|
||||
<div className="mt-16">
|
||||
<HeroSection />
|
||||
</div>
|
||||
|
||||
{/* Nav cards: Piletid + Turniirid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 md:h-[260px] border-b-3 border-[#1F5673]">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 md:h-65 border-b-3 border-[#1F5673]">
|
||||
<Link
|
||||
href="/piletid"
|
||||
className="px-8 md:px-12 py-8 flex flex-col justify-center gap-4 border-b-3 md:border-b-0 md:border-r-3 group border-[#1F5673] hover:bg-[#007CAB] dark:hover:bg-[#00A3E0] transition"
|
||||
@@ -71,7 +71,6 @@ export default async function Home({
|
||||
|
||||
{/* Sponsors */}
|
||||
<Sponsors />
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ interface TicketCardProps {
|
||||
buttonHref: string;
|
||||
backgroundImage?: string;
|
||||
backgroundOpacity?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function TicketCard({
|
||||
@@ -23,9 +24,12 @@ function TicketCard({
|
||||
buttonHref,
|
||||
backgroundImage,
|
||||
backgroundOpacity = 40,
|
||||
className = "",
|
||||
}: TicketCardProps) {
|
||||
return (
|
||||
<div className="relative bg-[#0E0F19] border-r border-[#1F5673] p-8 flex flex-col min-h-[350px]">
|
||||
<div
|
||||
className={`relative bg-[#0E0F19] border-[#1F5673] p-8 flex flex-col min-h-87.5 h-full ${className}`}
|
||||
>
|
||||
{backgroundImage && (
|
||||
<Image
|
||||
src={backgroundImage}
|
||||
@@ -51,13 +55,13 @@ function TicketCard({
|
||||
>
|
||||
{price}
|
||||
</p>
|
||||
<ul className="flex flex-col gap-1 mb-6 flex-grow">
|
||||
<ul className="flex flex-col gap-1 mb-6 grow">
|
||||
{features.map((feature, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="flex items-start gap-2 text-[#EEE5E5] text-sm"
|
||||
className="flex items-start gap-2 text-[#EEE5E5] text-base"
|
||||
>
|
||||
<span className="w-1 h-full min-h-[1.25rem] bg-[#00A3E0] flex-shrink-0" />
|
||||
<span className="w-1 h-full min-h-5 bg-[#00A3E0] shrink-0" />
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
@@ -84,53 +88,59 @@ export default async function Tickets({
|
||||
const t = await getTranslations({ locale });
|
||||
|
||||
return (
|
||||
<div className="bg-[#0E0F19]">
|
||||
<div className="bg-[#0E0F19] min-h-0 flex flex-col flex-1">
|
||||
{/* 2x2 Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 pt-16 md:pt-20">
|
||||
{/* KÜLASTAJA PILET */}
|
||||
<TicketCard
|
||||
title={t("tickets.visitor.name")}
|
||||
subtitle={t("tickets.subtitle")}
|
||||
price={t("tickets.visitor.price")}
|
||||
features={t.raw("tickets.visitor.features")}
|
||||
buttonText={t("tickets.buyButton")}
|
||||
buttonHref="https://fienta.com/et/tipilan"
|
||||
backgroundImage="/images/landing/visitor_tournament.jpg"
|
||||
/>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 md:grid-rows-2 auto-rows-fr pt-14 flex-1 min-h-0">
|
||||
{/* KÜLASTAJA PILET */}
|
||||
<TicketCard
|
||||
title={t("tickets.visitor.name")}
|
||||
subtitle={t("tickets.subtitle")}
|
||||
price={t("tickets.visitor.price")}
|
||||
features={t.raw("tickets.visitor.features")}
|
||||
buttonText={t("tickets.buyButton")}
|
||||
buttonHref="https://fienta.com/et/tipilan"
|
||||
backgroundImage="/images/landing/visitor_tournament.jpg"
|
||||
backgroundOpacity={30}
|
||||
className="border-b-[3px] md:border-b-[3px] md:border-r-[3px]"
|
||||
/>
|
||||
|
||||
{/* TOETAJA PILET */}
|
||||
<TicketCard
|
||||
title={t("tickets.supporter.name")}
|
||||
subtitle={t("tickets.subtitle")}
|
||||
price={t("tickets.supporter.price")}
|
||||
features={t.raw("tickets.supporter.features")}
|
||||
buttonText={t("tickets.buyButton")}
|
||||
buttonHref="https://fienta.com/et/tipilan"
|
||||
backgroundImage="/images/landing/explore_teaser.png"
|
||||
/>
|
||||
{/* TOETAJA PILET */}
|
||||
<TicketCard
|
||||
title={t("tickets.supporter.name")}
|
||||
subtitle={t("tickets.subtitle")}
|
||||
price={t("tickets.supporter.price")}
|
||||
features={t.raw("tickets.supporter.features")}
|
||||
buttonText={t("tickets.buyButton")}
|
||||
buttonHref="https://fienta.com/et/tipilan"
|
||||
backgroundImage="/images/landing/explore_teaser.png"
|
||||
backgroundOpacity={80}
|
||||
className="border-b-[3px]"
|
||||
/>
|
||||
|
||||
{/* LOL TURNIIRI PILET */}
|
||||
<TicketCard
|
||||
title={t("tickets.lol.name")}
|
||||
subtitle={t("tickets.subtitle")}
|
||||
price={t("tickets.lol.price")}
|
||||
features={t.raw("tickets.lol.features")}
|
||||
buttonText={t("tickets.buyButton")}
|
||||
buttonHref="https://fienta.com/et/tipilan"
|
||||
backgroundImage="/images/landing/league_ticket.jpg"
|
||||
/>
|
||||
{/* LOL TURNIIRI PILET */}
|
||||
<TicketCard
|
||||
title={t("tickets.lol.name")}
|
||||
subtitle={t("tickets.subtitle")}
|
||||
price={t("tickets.lol.price")}
|
||||
features={t.raw("tickets.lol.features")}
|
||||
buttonText={t("tickets.buyButton")}
|
||||
buttonHref="https://fienta.com/et/tipilan"
|
||||
backgroundImage="/images/landing/league_ticket.jpg"
|
||||
className="border-b-[3px] md:border-b-0 md:border-r-[3px]"
|
||||
/>
|
||||
|
||||
{/* CS2 TURNIIRI PILET */}
|
||||
<TicketCard
|
||||
title={t("tickets.cs2.name")}
|
||||
subtitle={t("tickets.subtitle")}
|
||||
price={t("tickets.cs2.price")}
|
||||
features={t.raw("tickets.cs2.features")}
|
||||
buttonText={t("tickets.buyButton")}
|
||||
buttonHref="https://fienta.com/et/tipilan"
|
||||
backgroundImage="/images/landing/compete_teaser.jpg"
|
||||
backgroundOpacity={30}
|
||||
/>
|
||||
{/* CS2 TURNIIRI PILET */}
|
||||
<TicketCard
|
||||
title={t("tickets.cs2.name")}
|
||||
subtitle={t("tickets.subtitle")}
|
||||
price={t("tickets.cs2.price")}
|
||||
features={t.raw("tickets.cs2.features")}
|
||||
buttonText={t("tickets.buyButton")}
|
||||
buttonHref="https://fienta.com/et/tipilan"
|
||||
backgroundImage="/images/landing/compete_teaser.jpg"
|
||||
backgroundOpacity={30}
|
||||
className="border-b-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { vipnagorgialla } from "@/components/Vipnagorgialla";
|
||||
import CS2Sidebar from "@/components/CS2Sidebar";
|
||||
import CS2Rules from "@/components/CS2Rules";
|
||||
import RuleNav from "@/components/RuleNav";
|
||||
import RulesContent from "@/components/RulesContent";
|
||||
import Link from "next/link";
|
||||
import { getTranslations, setRequestLocale } from "next-intl/server";
|
||||
|
||||
@@ -30,7 +30,7 @@ export default async function CS2Tournament({
|
||||
|
||||
return (
|
||||
<div className="bg-[#0E0F19] min-h-screen pt-16 md:pt-20">
|
||||
<div className="max-w-[1920px] mx-auto px-6 md:px-12 py-8 md:py-16">
|
||||
<div className="max-w-480 mx-auto px-6 md:px-12 py-8 md:py-16">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-[1fr_300px] gap-8 lg:gap-16">
|
||||
{/* Main content */}
|
||||
<div>
|
||||
@@ -60,7 +60,7 @@ export default async function CS2Tournament({
|
||||
</div>
|
||||
|
||||
{/* SISSEJUHATUS */}
|
||||
<section id="intro" className="mb-12">
|
||||
<section id="intro" className="mb-12 scroll-mt-24 md:scroll-mt-28">
|
||||
<h2
|
||||
className={`${vipnagorgialla.className} font-bold italic text-2xl md:text-3xl text-[#EEE5E5] uppercase mb-4`}
|
||||
>
|
||||
@@ -84,7 +84,7 @@ export default async function CS2Tournament({
|
||||
</section>
|
||||
|
||||
{/* ÜLDINE INFO */}
|
||||
<section id="info" className="mb-12">
|
||||
<section id="info" className="mb-12 scroll-mt-24 md:scroll-mt-28">
|
||||
<h2
|
||||
className={`${vipnagorgialla.className} font-bold italic text-2xl md:text-3xl text-[#EEE5E5] uppercase mb-4`}
|
||||
>
|
||||
@@ -96,7 +96,7 @@ export default async function CS2Tournament({
|
||||
</section>
|
||||
|
||||
{/* AUHINNAFOND */}
|
||||
<section id="prizes" className="mb-12">
|
||||
<section id="prizes" className="mb-12 scroll-mt-24 md:scroll-mt-28">
|
||||
<h2
|
||||
className={`${vipnagorgialla.className} font-bold italic text-2xl md:text-3xl text-[#EEE5E5] uppercase mb-4`}
|
||||
>
|
||||
@@ -109,9 +109,18 @@ export default async function CS2Tournament({
|
||||
{t("cs2page.prizes.mainTitle")}
|
||||
</h3>
|
||||
<ul className="text-[#EEE5E5]/80 mb-2">
|
||||
<li>1. koht - 3000€, 600€ inimese kohta, 50% ehk 1/2 auhinnafondist.</li>
|
||||
<li>2. koht - 2000€, 400€ inimese kohta, 33.3...(3)% ehk 1/3 auhinnafondist.</li>
|
||||
<li>3. koht - 1000€, 200€ inimese kohta, 16.6...(6)% ehk 1/6 auhinnafondist.</li>
|
||||
<li>
|
||||
1. koht - 3000€, 600€ inimese kohta, 50% ehk 1/2
|
||||
auhinnafondist.
|
||||
</li>
|
||||
<li>
|
||||
2. koht - 2000€, 400€ inimese kohta, 33.3...(3)% ehk 1/3
|
||||
auhinnafondist.
|
||||
</li>
|
||||
<li>
|
||||
3. koht - 1000€, 200€ inimese kohta, 16.6...(6)% ehk 1/6
|
||||
auhinnafondist.
|
||||
</li>
|
||||
</ul>
|
||||
<p className="text-[#EEE5E5]/60 text-sm mb-6">
|
||||
{t("cs2page.prizes.mainNote")}
|
||||
@@ -123,8 +132,14 @@ export default async function CS2Tournament({
|
||||
{t("cs2page.prizes.secondTitle")}
|
||||
</h3>
|
||||
<ul className="text-[#EEE5E5]/80 mb-2">
|
||||
<li>1. koht - 500€, 100€ inimese kohta, 66.6...(6)% ehk 2/3 auhinnafondist.</li>
|
||||
<li>2. koht - 250€, 50€ inimese kohta, 33.3...(3)% ehk 1/3 auhinnafondist.</li>
|
||||
<li>
|
||||
1. koht - 500€, 100€ inimese kohta, 66.6...(6)% ehk 2/3
|
||||
auhinnafondist.
|
||||
</li>
|
||||
<li>
|
||||
2. koht - 250€, 50€ inimese kohta, 33.3...(3)% ehk 1/3
|
||||
auhinnafondist.
|
||||
</li>
|
||||
</ul>
|
||||
<p className="text-[#EEE5E5]/60 text-sm">
|
||||
{t("cs2page.prizes.secondNote")}
|
||||
@@ -132,7 +147,7 @@ export default async function CS2Tournament({
|
||||
</section>
|
||||
|
||||
{/* TURNIIRI FORMAAT */}
|
||||
<section id="format" className="mb-12">
|
||||
<section id="format" className="mb-12 scroll-mt-24 md:scroll-mt-28">
|
||||
<h2
|
||||
className={`${vipnagorgialla.className} font-bold italic text-2xl md:text-3xl text-[#EEE5E5] uppercase mb-4`}
|
||||
>
|
||||
@@ -141,16 +156,12 @@ export default async function CS2Tournament({
|
||||
<p className="text-[#EEE5E5]/80 mb-4">
|
||||
{t("cs2page.format.description")}
|
||||
</p>
|
||||
<p className="text-[#EEE5E5]/80">
|
||||
{t("cs2page.format.day1")}
|
||||
</p>
|
||||
<p className="text-[#EEE5E5]/80">
|
||||
{t("cs2page.format.day23")}
|
||||
</p>
|
||||
<p className="text-[#EEE5E5]/80">{t("cs2page.format.day1")}</p>
|
||||
<p className="text-[#EEE5E5]/80">{t("cs2page.format.day23")}</p>
|
||||
</section>
|
||||
|
||||
{/* VRS INFO */}
|
||||
<section id="vrs" className="mb-12">
|
||||
<section id="vrs" className="mb-12 scroll-mt-24 md:scroll-mt-28">
|
||||
<h2
|
||||
className={`${vipnagorgialla.className} font-bold italic text-2xl md:text-3xl text-[#EEE5E5] uppercase mb-4`}
|
||||
>
|
||||
@@ -183,7 +194,7 @@ export default async function CS2Tournament({
|
||||
*/}
|
||||
|
||||
{/* REEGLID */}
|
||||
<section id="rules" className="mb-12">
|
||||
<section id="rules" className="mb-12 scroll-mt-24 md:scroll-mt-28">
|
||||
<h2
|
||||
className={`${vipnagorgialla.className} font-bold italic text-2xl md:text-3xl text-[#EEE5E5] uppercase mb-4`}
|
||||
>
|
||||
@@ -193,12 +204,18 @@ export default async function CS2Tournament({
|
||||
{t("cs2page.rules.description")}
|
||||
</p>
|
||||
|
||||
<CS2Rules sections={t.raw("cs2page.rules.sections")} />
|
||||
<RulesContent sections={t.raw("cs2page.rules.sections")} />
|
||||
|
||||
<div className="mt-8">
|
||||
<p className="text-[#EEE5E5]/80 mb-2">{t("cs2page.rules.contact")}</p>
|
||||
<p className="text-[#00A3E0] font-bold">{t("cs2page.rules.contactName")}</p>
|
||||
<p className="text-[#EEE5E5]/70">{t("cs2page.rules.contactRole")}</p>
|
||||
<p className="text-[#EEE5E5]/80 mb-2">
|
||||
{t("cs2page.rules.contact")}
|
||||
</p>
|
||||
<p className="text-[#00A3E0] font-bold">
|
||||
{t("cs2page.rules.contactName")}
|
||||
</p>
|
||||
<p className="text-[#EEE5E5]/70">
|
||||
{t("cs2page.rules.contactRole")}
|
||||
</p>
|
||||
<p className="text-[#EEE5E5]/70">
|
||||
Discord:{" "}
|
||||
<a
|
||||
@@ -214,7 +231,7 @@ export default async function CS2Tournament({
|
||||
</div>
|
||||
|
||||
{/* Sidebar navigation */}
|
||||
<CS2Sidebar sections={sections} />
|
||||
<RuleNav sections={sections} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { vipnagorgialla } from "@/components/Vipnagorgialla";
|
||||
import CS2Sidebar from "@/components/CS2Sidebar";
|
||||
import CS2Rules from "@/components/CS2Rules";
|
||||
import RuleNav from "@/components/RuleNav";
|
||||
import RulesContent from "@/components/RulesContent";
|
||||
import Link from "next/link";
|
||||
import { getTranslations, setRequestLocale } from "next-intl/server";
|
||||
|
||||
@@ -52,7 +52,7 @@ export default async function LoLTournament({
|
||||
</div>
|
||||
|
||||
{/* SISSEJUHATUS */}
|
||||
<section id="intro" className="mb-12">
|
||||
<section id="intro" className="mb-12 scroll-mt-24 md:scroll-mt-28">
|
||||
<h2
|
||||
className={`${vipnagorgialla.className} font-bold italic text-2xl md:text-3xl text-[#EEE5E5] uppercase mb-4`}
|
||||
>
|
||||
@@ -76,7 +76,7 @@ export default async function LoLTournament({
|
||||
</section>
|
||||
|
||||
{/* ÜLDINE INFO */}
|
||||
<section id="info" className="mb-12">
|
||||
<section id="info" className="mb-12 scroll-mt-24 md:scroll-mt-28">
|
||||
<h2
|
||||
className={`${vipnagorgialla.className} font-bold italic text-2xl md:text-3xl text-[#EEE5E5] uppercase mb-4`}
|
||||
>
|
||||
@@ -88,7 +88,7 @@ export default async function LoLTournament({
|
||||
</section>
|
||||
|
||||
{/* AUHINNAFOND */}
|
||||
<section id="prizes" className="mb-12">
|
||||
<section id="prizes" className="mb-12 scroll-mt-24 md:scroll-mt-28">
|
||||
<h2
|
||||
className={`${vipnagorgialla.className} font-bold italic text-2xl md:text-3xl text-[#EEE5E5] uppercase mb-4`}
|
||||
>
|
||||
@@ -111,7 +111,7 @@ export default async function LoLTournament({
|
||||
</section>
|
||||
|
||||
{/* TURNIIRI FORMAAT */}
|
||||
<section id="format" className="mb-12">
|
||||
<section id="format" className="mb-12 scroll-mt-24 md:scroll-mt-28">
|
||||
<h2
|
||||
className={`${vipnagorgialla.className} font-bold italic text-2xl md:text-3xl text-[#EEE5E5] uppercase mb-4`}
|
||||
>
|
||||
@@ -120,12 +120,8 @@ export default async function LoLTournament({
|
||||
<p className="text-[#EEE5E5]/80 mb-4">
|
||||
{t("lolpage.format.description")}
|
||||
</p>
|
||||
<p className="text-[#EEE5E5]/80">
|
||||
{t("lolpage.format.day1")}
|
||||
</p>
|
||||
<p className="text-[#EEE5E5]/80">
|
||||
{t("lolpage.format.day2")}
|
||||
</p>
|
||||
<p className="text-[#EEE5E5]/80">{t("lolpage.format.day1")}</p>
|
||||
<p className="text-[#EEE5E5]/80">{t("lolpage.format.day2")}</p>
|
||||
</section>
|
||||
|
||||
{/* FAQ - commented out until content is ready
|
||||
@@ -147,7 +143,7 @@ export default async function LoLTournament({
|
||||
*/}
|
||||
|
||||
{/* REEGLID */}
|
||||
<section id="rules" className="mb-12">
|
||||
<section id="rules" className="mb-12 scroll-mt-24 md:scroll-mt-28">
|
||||
<h2
|
||||
className={`${vipnagorgialla.className} font-bold italic text-2xl md:text-3xl text-[#EEE5E5] uppercase mb-4`}
|
||||
>
|
||||
@@ -157,12 +153,18 @@ export default async function LoLTournament({
|
||||
{t("lolpage.rules.description")}
|
||||
</p>
|
||||
|
||||
<CS2Rules sections={t.raw("lolpage.rules.sections")} />
|
||||
<RulesContent sections={t.raw("lolpage.rules.sections")} />
|
||||
|
||||
<div className="mt-8">
|
||||
<p className="text-[#EEE5E5]/80 mb-2">{t("lolpage.rules.contact")}</p>
|
||||
<p className="text-[#00A3E0] font-bold">{t("lolpage.rules.contactName")}</p>
|
||||
<p className="text-[#EEE5E5]/70">{t("lolpage.rules.contactRole")}</p>
|
||||
<p className="text-[#EEE5E5]/80 mb-2">
|
||||
{t("lolpage.rules.contact")}
|
||||
</p>
|
||||
<p className="text-[#00A3E0] font-bold">
|
||||
{t("lolpage.rules.contactName")}
|
||||
</p>
|
||||
<p className="text-[#EEE5E5]/70">
|
||||
{t("lolpage.rules.contactRole")}
|
||||
</p>
|
||||
<p className="text-[#EEE5E5]/70">
|
||||
Discord:{" "}
|
||||
<a
|
||||
@@ -178,7 +180,7 @@ export default async function LoLTournament({
|
||||
</div>
|
||||
|
||||
{/* Sidebar navigation */}
|
||||
<CS2Sidebar sections={sections} />
|
||||
<RuleNav sections={sections} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,46 +1,44 @@
|
||||
import { vipnagorgialla } from "@/components/Vipnagorgialla";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { getTranslations, setRequestLocale } from "next-intl/server";
|
||||
import { setRequestLocale } from "next-intl/server";
|
||||
|
||||
interface TournamentCardProps {
|
||||
title: string;
|
||||
buttonText: string;
|
||||
buttonHref: string;
|
||||
backgroundImage: string;
|
||||
objectPosition?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function TournamentCard({
|
||||
title,
|
||||
buttonText,
|
||||
buttonHref,
|
||||
backgroundImage,
|
||||
objectPosition = "center",
|
||||
className = "",
|
||||
}: TournamentCardProps) {
|
||||
return (
|
||||
<div className="relative bg-[#0E0F19] border-r border-[#1F5673] flex flex-col items-center justify-center h-[818px]">
|
||||
<Link
|
||||
href={buttonHref}
|
||||
aria-label={title}
|
||||
className={`group relative bg-[#0E0F19] border-[#1F5673] flex flex-col items-center justify-center h-full min-h-87.5 transition-colors duration-200 focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-[#00A3E0]/40 ${className}`}
|
||||
>
|
||||
<Image
|
||||
src={backgroundImage}
|
||||
alt=""
|
||||
fill
|
||||
className="object-cover opacity-50"
|
||||
className="object-cover opacity-50 transition-opacity duration-200 group-hover:opacity-40 group-focus-visible:opacity-40"
|
||||
style={{ objectPosition }}
|
||||
/>
|
||||
<div className="relative z-10 flex flex-col items-center text-center">
|
||||
<h2
|
||||
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2rem,1.5rem+3vw,4rem)] leading-none text-[#EEE5E5] uppercase mb-4`}
|
||||
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2rem,1.5rem+3vw,4rem)] leading-none text-[#EEE5E5] uppercase transition-colors duration-200 group-hover:text-[#00A3E0] group-focus-visible:text-[#00A3E0]`}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
<Link
|
||||
href={buttonHref}
|
||||
className={`${vipnagorgialla.className} font-bold italic text-xl text-[#00A3E0] hover:text-[#EEE5E5] uppercase transition`}
|
||||
>
|
||||
{buttonText}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,27 +49,25 @@ export default async function Tourney({
|
||||
}) {
|
||||
const { locale } = await params;
|
||||
setRequestLocale(locale);
|
||||
const t = await getTranslations({ locale });
|
||||
|
||||
return (
|
||||
<div className="bg-[#0E0F19]">
|
||||
<div className="bg-[#0E0F19] min-h-0 flex flex-col flex-1">
|
||||
{/* 1x2 Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 pt-16 md:pt-20">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 auto-rows-fr pt-14 flex-1 min-h-0">
|
||||
{/* CS2 */}
|
||||
<TournamentCard
|
||||
title="COUNTER-STRIKE 2"
|
||||
buttonText={t("tournaments.clickButton")}
|
||||
buttonHref="/turniirid/cs2"
|
||||
backgroundImage="/images/landing/cs2_tournament.jpg"
|
||||
className="border-b-[3px] md:border-b-0 md:border-r-3"
|
||||
/>
|
||||
|
||||
{/* LoL */}
|
||||
<TournamentCard
|
||||
title="LEAGUE OF LEGENDS"
|
||||
buttonText={t("tournaments.clickButton")}
|
||||
buttonHref="/turniirid/lol"
|
||||
backgroundImage="/images/landing/lol_tournament.png"
|
||||
objectPosition="bottom left"
|
||||
className="border-b-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface CS2SidebarProps {
|
||||
sections: { id: string; label: string }[];
|
||||
}
|
||||
|
||||
export default function CS2Sidebar({ sections }: CS2SidebarProps) {
|
||||
const [activeSection, setActiveSection] = useState<string>(sections[0]?.id || "");
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
setActiveSection(entry.target.id);
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
rootMargin: "-20% 0px -70% 0px",
|
||||
threshold: 0,
|
||||
}
|
||||
);
|
||||
|
||||
sections.forEach((section) => {
|
||||
const element = document.getElementById(section.id);
|
||||
if (element) {
|
||||
observer.observe(element);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
sections.forEach((section) => {
|
||||
const element = document.getElementById(section.id);
|
||||
if (element) {
|
||||
observer.unobserve(element);
|
||||
}
|
||||
});
|
||||
};
|
||||
}, [sections]);
|
||||
|
||||
return (
|
||||
<aside className="hidden lg:block">
|
||||
<nav className="sticky top-24 border-l border-[#1F5673] pl-6">
|
||||
<ul className="flex flex-col gap-2">
|
||||
{sections.map((section) => (
|
||||
<li key={section.id}>
|
||||
<a
|
||||
href={`#${section.id}`}
|
||||
className={`transition ${
|
||||
activeSection === section.id
|
||||
? "text-[#EEE5E5] font-bold"
|
||||
: "text-[#00A3E0] hover:text-[#EEE5E5]"
|
||||
}`}
|
||||
>
|
||||
{section.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
@@ -15,11 +15,9 @@ const Footer = () => {
|
||||
>
|
||||
{t("footer.contact")}
|
||||
</h2>
|
||||
<div className="flex flex-row justify-between gap-8 items-start">
|
||||
<div>
|
||||
<h3 className="text-xl font-bold">
|
||||
MTÜ Lapikud
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-8 items-start w-full">
|
||||
<div className="text-center sm:text-left sm:justify-self-start">
|
||||
<h3 className="text-xl font-bold">MTÜ Lapikud</h3>
|
||||
<div className="flex flex-col gap-2 mt-2">
|
||||
<p>
|
||||
{t("footer.registrationCode")}:{" "}
|
||||
@@ -30,12 +28,12 @@ const Footer = () => {
|
||||
<p className="">Swedbank EE842200221094704780</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
<div className="flex flex-col gap-2 items-center text-center sm:justify-self-center">
|
||||
<div className="flex flex-row gap-2">
|
||||
<span className="material-symbols-outlined !font-bold text-[#007CAB] dark:text-[#00A3E0]">
|
||||
mail
|
||||
</span>
|
||||
<a href="mailto:tipilaninfo@gmail.com" className="underline">
|
||||
<a href="mailto:tipilaninfogmail.com" className="underline">
|
||||
tipilaninfo@gmail.com
|
||||
</a>
|
||||
</div>
|
||||
@@ -49,7 +47,7 @@ const Footer = () => {
|
||||
</div>
|
||||
</div>
|
||||
{/* Social media */}
|
||||
<div className="flex flex-row gap-4">
|
||||
<div className="flex flex-row gap-4 justify-center sm:justify-self-end sm:justify-end">
|
||||
<a
|
||||
href="https://discord.gg/pPhhatZAfA"
|
||||
target="_blank"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { Link } from "@/i18n/routing";
|
||||
import { Link, usePathname } from "@/i18n/routing";
|
||||
import { vipnagorgialla } from "@/components/Vipnagorgialla";
|
||||
|
||||
// Icons
|
||||
@@ -36,15 +36,16 @@ interface HeaderProps {
|
||||
}
|
||||
|
||||
const Header = ({ navItems }: HeaderProps) => {
|
||||
const pathname = usePathname();
|
||||
|
||||
// Filter nav items for the horizontal bar (exclude kodukord)
|
||||
const mainNavItems = navItems.filter(
|
||||
(item) => item.href !== "/kodukord"
|
||||
);
|
||||
const mainNavItems = navItems.filter((item) => item.href !== "/kodukord");
|
||||
const disabledNavHrefs = new Set<NavItem["href"]>(["/messiala", "/ajakava"]);
|
||||
|
||||
return (
|
||||
<header className="px-4 py-2 md:px-8 flex items-center bg-[#0E0F19] border-b-3 border-[#1F5673] justify-between">
|
||||
{/* Logo */}
|
||||
<Link href="/" className="flex-shrink-0">
|
||||
<Link href="/" className="shrink-0">
|
||||
<Image
|
||||
src="/tipilan-icon-white.svg"
|
||||
alt="TipiLAN"
|
||||
@@ -57,16 +58,30 @@ const Header = ({ navItems }: HeaderProps) => {
|
||||
{/* Right side - Navigation + controls */}
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Desktop Navigation */}
|
||||
<nav className="hidden md:flex items-center gap-3">
|
||||
{mainNavItems.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`${vipnagorgialla.className} font-bold italic text-lg uppercase px-4 py-1.5 border-2 border-[#00A3E0] text-[#EEE5E5] hover:bg-[#00A3E0]/20 transition`}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
<nav className="hidden lg:flex items-center gap-3">
|
||||
{mainNavItems.map((item) => {
|
||||
const isActive = pathname === item.href;
|
||||
const isDisabled = disabledNavHrefs.has(item.href);
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
aria-disabled={isActive || isDisabled ? true : undefined}
|
||||
tabIndex={isActive || isDisabled ? -1 : undefined}
|
||||
className={`${vipnagorgialla.className} font-bold italic text-lg uppercase px-4 py-1.5 border-2 border-[#00A3E0] text-[#EEE5E5] transition ${
|
||||
isActive
|
||||
? "bg-[#00A3E0] text-black cursor-default pointer-events-none"
|
||||
: isDisabled
|
||||
? "opacity-50 cursor-not-allowed pointer-events-none"
|
||||
: "hover:bg-[#00A3E0]/20"
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<LanguageSwitcher />
|
||||
|
||||
@@ -76,23 +91,46 @@ const Header = ({ navItems }: HeaderProps) => {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="md:hidden size-10 cursor-pointer"
|
||||
className="lg:hidden size-10 cursor-pointer"
|
||||
>
|
||||
<MdMenu className="h-8 w-8 text-[#EEE5E5]" />
|
||||
<span className="sr-only">Menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-48 translate-y-4">
|
||||
{navItems.map((item) => (
|
||||
<DropdownMenuItem key={item.href} asChild>
|
||||
<Link
|
||||
href={item.href}
|
||||
className={`${vipnagorgialla.className} font-bold italic uppercase text-lg`}
|
||||
{navItems.map((item) => {
|
||||
const isActive = pathname === item.href;
|
||||
const isDisabled = disabledNavHrefs.has(item.href);
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={item.href}
|
||||
disabled={isActive || isDisabled}
|
||||
asChild={!isActive && !isDisabled}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
{isActive ? (
|
||||
<span
|
||||
className={`${vipnagorgialla.className} font-bold italic uppercase text-lg text-[#00A3E0] cursor-default`}
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
) : isDisabled ? (
|
||||
<span
|
||||
className={`${vipnagorgialla.className} font-bold italic uppercase text-lg opacity-50 cursor-not-allowed`}
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
) : (
|
||||
<Link
|
||||
href={item.href}
|
||||
className={`${vipnagorgialla.className} font-bold italic uppercase text-lg`}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
84
src/components/RuleNav.tsx
Normal file
84
src/components/RuleNav.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface RuleNavProps {
|
||||
sections: { id: string; label: string }[];
|
||||
}
|
||||
|
||||
export default function RuleNav({ sections }: RuleNavProps) {
|
||||
const [activeSection, setActiveSection] = useState<string>(
|
||||
sections[0]?.id || "",
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (sections.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const getScrollOffset = () => {
|
||||
const firstSection = document.getElementById(sections[0].id);
|
||||
if (!firstSection) {
|
||||
return 0;
|
||||
}
|
||||
const scrollMarginTop =
|
||||
window.getComputedStyle(firstSection).scrollMarginTop;
|
||||
const parsed = Number.parseFloat(scrollMarginTop);
|
||||
return Number.isNaN(parsed) ? 0 : parsed;
|
||||
};
|
||||
|
||||
const updateActiveSection = () => {
|
||||
const offset = getScrollOffset();
|
||||
const scrollPosition = window.scrollY + offset + 1;
|
||||
let currentId = sections[0].id;
|
||||
|
||||
for (const section of sections) {
|
||||
const element = document.getElementById(section.id);
|
||||
if (!element) {
|
||||
continue;
|
||||
}
|
||||
if (element.offsetTop <= scrollPosition) {
|
||||
currentId = section.id;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setActiveSection(currentId);
|
||||
};
|
||||
|
||||
updateActiveSection();
|
||||
window.addEventListener("scroll", updateActiveSection, { passive: true });
|
||||
window.addEventListener("resize", updateActiveSection);
|
||||
window.addEventListener("hashchange", updateActiveSection);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("scroll", updateActiveSection);
|
||||
window.removeEventListener("resize", updateActiveSection);
|
||||
window.removeEventListener("hashchange", updateActiveSection);
|
||||
};
|
||||
}, [sections]);
|
||||
|
||||
return (
|
||||
<aside className="hidden lg:block">
|
||||
<nav className="sticky top-24 border-l border-[#1F5673] pl-6">
|
||||
<ul className="flex flex-col gap-2">
|
||||
{sections.map((section) => (
|
||||
<li key={section.id}>
|
||||
<a
|
||||
href={`#${section.id}`}
|
||||
className={`transition ${
|
||||
activeSection === section.id
|
||||
? "text-[#EEE5E5] font-bold"
|
||||
: "text-[#00A3E0] hover:text-[#EEE5E5]"
|
||||
}`}
|
||||
>
|
||||
{section.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
@@ -4,14 +4,25 @@ import { vipnagorgialla } from "@/components/Vipnagorgialla";
|
||||
|
||||
interface RuleSection {
|
||||
title: string;
|
||||
rules: (string | { main: string; sub: (string | { main: string; sub: string[] })[] })[];
|
||||
rules: (
|
||||
| string
|
||||
| { main: string; sub: (string | { main: string; sub: string[] })[] }
|
||||
)[];
|
||||
}
|
||||
|
||||
interface CS2RulesProps {
|
||||
interface RulesContentProps {
|
||||
sections: RuleSection[];
|
||||
}
|
||||
|
||||
function RuleItem({ rule, index }: { rule: string | { main: string; sub: (string | { main: string; sub: string[] })[] }; index: number }) {
|
||||
function RuleItem({
|
||||
rule,
|
||||
index,
|
||||
}: {
|
||||
rule:
|
||||
| string
|
||||
| { main: string; sub: (string | { main: string; sub: string[] })[] };
|
||||
index: number;
|
||||
}) {
|
||||
if (typeof rule === "string") {
|
||||
return (
|
||||
<li className="text-[#EEE5E5]/80 mb-2">
|
||||
@@ -31,20 +42,26 @@ function RuleItem({ rule, index }: { rule: string | { main: string; sub: (string
|
||||
if (typeof subRule === "string") {
|
||||
return (
|
||||
<li key={subIndex} className="text-[#EEE5E5]/70 mb-1">
|
||||
<span className="text-[#00A3E0]/70 mr-2">{index}.{subIndex + 1}.</span>
|
||||
<span className="text-[#00A3E0]/70 mr-2">
|
||||
{index}.{subIndex + 1}.
|
||||
</span>
|
||||
{subRule}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<li key={subIndex} className="text-[#EEE5E5]/70 mb-2">
|
||||
<span className="text-[#00A3E0]/70 mr-2">{index}.{subIndex + 1}.</span>
|
||||
<span className="text-[#00A3E0]/70 mr-2">
|
||||
{index}.{subIndex + 1}.
|
||||
</span>
|
||||
{subRule.main}
|
||||
{subRule.sub && subRule.sub.length > 0 && (
|
||||
<ol className="ml-6 mt-1">
|
||||
{subRule.sub.map((subSubRule, subSubIndex) => (
|
||||
<li key={subSubIndex} className="text-[#EEE5E5]/60 mb-1">
|
||||
<span className="text-[#00A3E0]/50 mr-2">{index}.{subIndex + 1}.{subSubIndex + 1}.</span>
|
||||
<span className="text-[#00A3E0]/50 mr-2">
|
||||
{index}.{subIndex + 1}.{subSubIndex + 1}.
|
||||
</span>
|
||||
{subSubRule}
|
||||
</li>
|
||||
))}
|
||||
@@ -59,12 +76,14 @@ function RuleItem({ rule, index }: { rule: string | { main: string; sub: (string
|
||||
);
|
||||
}
|
||||
|
||||
export default function CS2Rules({ sections }: CS2RulesProps) {
|
||||
export default function RulesContent({ sections }: RulesContentProps) {
|
||||
return (
|
||||
<div>
|
||||
{sections.map((section, sectionIndex) => (
|
||||
<div key={sectionIndex} className="mb-8">
|
||||
<h3 className={`${vipnagorgialla.className} font-bold italic text-xl text-[#00A3E0] uppercase mb-4`}>
|
||||
<h3
|
||||
className={`${vipnagorgialla.className} font-bold italic text-xl text-[#00A3E0] uppercase mb-4`}
|
||||
>
|
||||
{sectionIndex + 1}) {section.title}
|
||||
</h3>
|
||||
<ol className="list-none">
|
||||
@@ -16,15 +16,71 @@ interface Sponsor {
|
||||
}
|
||||
|
||||
const sponsors: Sponsor[] = [
|
||||
{ href: "https://taltech.ee", src: "/sponsors/taltech-color.png", alt: "Taltech (Tallinna Tehnikaülikool)", width: 192, height: 192 },
|
||||
{ href: "https://www.redbull.com/ee-et/", src: "/sponsors/redbull.png", alt: "Redbull", width: 80, height: 80 },
|
||||
{ href: "https://www.simracing.ee/", src: "/sponsors/EVAL.png", alt: "EVAL", width: 200, height: 200 },
|
||||
{ href: "https://www.facebook.com/bfglOfficial", src: "/sponsors/BFGL.png", alt: "BFGL", width: 192, height: 192 },
|
||||
{ href: "https://www.tomorrow.ee/", src: "/sponsors/nt.png", alt: "Network Tomorrow", width: 300, height: 200 },
|
||||
{ href: "https://k-space.ee/", src: "/sponsors/k-space_ee-white.png", alt: "K-Space", width: 200, height: 200, className: "not-dark:invert" },
|
||||
{ href: "https://globalproductions.ee/", src: "/sponsors/Global-productions.png", alt: "Global Productions", width: 200, height: 200 },
|
||||
{ href: "https://www.linkedin.com/company/gamedev-guild/", src: "/sponsors/estonian_gamedev_guild.png", alt: "Estonian Gamedev Guild", width: 200, height: 200, className: "not-dark:invert" },
|
||||
{ href: "https://alzgamer.ee/", src: "/sponsors/alzgamer.png", alt: "AlzGamer", width: 200, height: 200 },
|
||||
{
|
||||
href: "https://taltech.ee",
|
||||
src: "/sponsors/taltech-color.png",
|
||||
alt: "Taltech (Tallinna Tehnikaülikool)",
|
||||
width: 192,
|
||||
height: 192,
|
||||
},
|
||||
{
|
||||
href: "https://www.redbull.com/ee-et/",
|
||||
src: "/sponsors/redbull.png",
|
||||
alt: "Redbull",
|
||||
width: 80,
|
||||
height: 80,
|
||||
},
|
||||
{
|
||||
href: "https://www.simracing.ee/",
|
||||
src: "/sponsors/EVAL.png",
|
||||
alt: "EVAL",
|
||||
width: 200,
|
||||
height: 200,
|
||||
},
|
||||
{
|
||||
href: "https://www.facebook.com/bfglOfficial",
|
||||
src: "/sponsors/BFGL.png",
|
||||
alt: "BFGL",
|
||||
width: 192,
|
||||
height: 192,
|
||||
},
|
||||
{
|
||||
href: "https://www.tomorrow.ee/",
|
||||
src: "/sponsors/nt.png",
|
||||
alt: "Network Tomorrow",
|
||||
width: 300,
|
||||
height: 200,
|
||||
},
|
||||
{
|
||||
href: "https://k-space.ee/",
|
||||
src: "/sponsors/k-space_ee-white.png",
|
||||
alt: "K-Space",
|
||||
width: 200,
|
||||
height: 200,
|
||||
className: "not-dark:invert",
|
||||
},
|
||||
{
|
||||
href: "https://globalproductions.ee/",
|
||||
src: "/sponsors/Global-productions.png",
|
||||
alt: "Global Productions",
|
||||
width: 200,
|
||||
height: 200,
|
||||
},
|
||||
{
|
||||
href: "https://www.linkedin.com/company/gamedev-guild/",
|
||||
src: "/sponsors/estonian_gamedev_guild.png",
|
||||
alt: "Estonian Gamedev Guild",
|
||||
width: 200,
|
||||
height: 200,
|
||||
className: "not-dark:invert",
|
||||
},
|
||||
{
|
||||
href: "https://alzgamer.ee/",
|
||||
src: "/sponsors/alzgamer.png",
|
||||
alt: "AlzGamer",
|
||||
width: 200,
|
||||
height: 200,
|
||||
},
|
||||
];
|
||||
|
||||
// Split sponsors into slides (6 per slide)
|
||||
@@ -46,7 +102,10 @@ export default function Sponsors({
|
||||
const t = useTranslations();
|
||||
const [current, setCurrent] = useState(0);
|
||||
|
||||
const next = useCallback(() => setCurrent((c) => (c + 1) % slides.length), []);
|
||||
const next = useCallback(
|
||||
() => setCurrent((c) => (c + 1) % slides.length),
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(next, 5000);
|
||||
@@ -55,7 +114,7 @@ export default function Sponsors({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col max-w-[1920px] h-[414px] mx-auto ${vipnagorgialla.className} font-bold italic border-b-3 border-[#1F5673] ${className}`}
|
||||
className={`flex flex-col max-w-[1920px] xl:h-[414px] mx-auto ${vipnagorgialla.className} font-bold italic border-[#1F5673] ${className}`}
|
||||
>
|
||||
{showTitle && (
|
||||
<h3 className="text-4xl md:text-5xl dark:text-[#EEE5E5] text-[#2A2C3F] px-12 pt-8 pb-4">
|
||||
@@ -64,7 +123,7 @@ export default function Sponsors({
|
||||
)}
|
||||
|
||||
{/* Carousel container */}
|
||||
<div className="relative flex-1 overflow-hidden">
|
||||
<div className="relative xl:flex-1 overflow-x-hidden overflow-y-visible">
|
||||
<div
|
||||
className="flex h-full transition-transform duration-500 ease-in-out"
|
||||
style={{ transform: `translateX(-${current * 100}%)` }}
|
||||
@@ -72,16 +131,21 @@ export default function Sponsors({
|
||||
{slides.map((slideSponsors, slideIndex) => (
|
||||
<div
|
||||
key={slideIndex}
|
||||
className="flex-none w-full h-full flex items-center justify-center gap-8 md:gap-12 px-12"
|
||||
className="flex-none w-full xl:h-full grid grid-cols-2 sm:grid-cols-3 place-items-center gap-6 px-6 py-6 xl:flex xl:items-center xl:justify-center xl:gap-12 xl:px-12 xl:py-0"
|
||||
>
|
||||
{slideSponsors.map((sponsor, i) => (
|
||||
<NextLink key={i} href={sponsor.href} target="_blank">
|
||||
<NextLink
|
||||
key={i}
|
||||
href={sponsor.href}
|
||||
target="_blank"
|
||||
className="flex items-center justify-center w-full"
|
||||
>
|
||||
<Image
|
||||
src={sponsor.src}
|
||||
alt={sponsor.alt}
|
||||
width={sponsor.width}
|
||||
height={sponsor.height}
|
||||
className={`object-contain max-h-[180px] ${sponsor.className || ""}`}
|
||||
className={`object-contain max-h-[80px] max-w-[120px] sm:max-h-[110px] sm:max-w-[150px] md:max-h-[130px] md:max-w-[180px] lg:max-h-[140px] lg:max-w-[200px] xl:max-h-[180px] xl:max-w-[240px] ${sponsor.className || ""}`}
|
||||
/>
|
||||
</NextLink>
|
||||
))}
|
||||
@@ -97,7 +161,9 @@ export default function Sponsors({
|
||||
key={i}
|
||||
onClick={() => setCurrent(i)}
|
||||
className={`w-3 h-3 rounded-full transition ${
|
||||
i === current ? "bg-[#007CAB]" : "bg-[#1F5673] hover:bg-[#007CAB]/60"
|
||||
i === current
|
||||
? "bg-[#00A3E0]"
|
||||
: "bg-[#1F5673] hover:bg-[#007CAB]/60"
|
||||
}`}
|
||||
aria-label={`Slide ${i + 1}`}
|
||||
/>
|
||||
|
||||
@@ -25,7 +25,8 @@ function highlightLAN(text: string) {
|
||||
const suffix = part.slice(7); // Everything after "TIPILAN"
|
||||
return (
|
||||
<span key={i}>
|
||||
TIPI<span className="text-[#00A3E0]">LAN</span>{suffix.toUpperCase()}
|
||||
TIPI<span className="text-[#00A3E0]">LAN</span>
|
||||
{suffix.toUpperCase()}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -34,16 +35,40 @@ function highlightLAN(text: string) {
|
||||
}
|
||||
|
||||
const slides: Slide[] = [
|
||||
{ key: "compete", image: "/images/landing/compete_teaser.jpg", imageAlt: "Võistle", hero: "/images/landing/compete_hero.png", href: "/turniirid" },
|
||||
{ key: "play", image: "/images/landing/play_teaser.png", imageAlt: "Mängi", hero: "/images/landing/play_hero.png", href: "/piletid", flip: true, fullBrightness: true },
|
||||
{ key: "explore", image: "/images/landing/explore_teaser.png", imageAlt: "Avasta", hero: "/images/landing/explore_hero.png", href: "/messiala", fullBrightness: true },
|
||||
{
|
||||
key: "compete",
|
||||
image: "/images/landing/compete_teaser.jpg",
|
||||
imageAlt: "Võistle",
|
||||
hero: "/images/landing/compete_hero.png",
|
||||
href: "/turniirid",
|
||||
},
|
||||
{
|
||||
key: "play",
|
||||
image: "/images/landing/play_teaser.png",
|
||||
imageAlt: "Mängi",
|
||||
hero: "/images/landing/play_hero.png",
|
||||
href: "/piletid",
|
||||
flip: true,
|
||||
fullBrightness: true,
|
||||
},
|
||||
{
|
||||
key: "explore",
|
||||
image: "/images/landing/explore_teaser.png",
|
||||
imageAlt: "Avasta",
|
||||
hero: "/images/landing/explore_hero.png",
|
||||
href: "/messiala",
|
||||
fullBrightness: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default function TeaserCarousel() {
|
||||
const t = useTranslations("home.teaser");
|
||||
const [current, setCurrent] = useState(0);
|
||||
|
||||
const next = useCallback(() => setCurrent((c) => (c + 1) % slides.length), []);
|
||||
const next = useCallback(
|
||||
() => setCurrent((c) => (c + 1) % slides.length),
|
||||
[],
|
||||
);
|
||||
const prev = () => setCurrent((c) => (c - 1 + slides.length) % slides.length);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -60,7 +85,7 @@ export default function TeaserCarousel() {
|
||||
style={{ transform: `translateX(-${current * 100}%)` }}
|
||||
>
|
||||
{slides.map((slide) => {
|
||||
const title = t(`${slide.key}.title`);
|
||||
const title = t(`${slide.key}.title`);
|
||||
const description = t(`${slide.key}.description`);
|
||||
return (
|
||||
<div key={slide.key} className="relative flex-none w-full h-full">
|
||||
@@ -72,19 +97,29 @@ export default function TeaserCarousel() {
|
||||
className="object-cover object-center"
|
||||
/>
|
||||
{/* Overlay */}
|
||||
<div className={`absolute inset-0 ${slide.fullBrightness ? "" : "bg-gradient-to-r from-[#0E0F19]/90 via-[#0E0F19]/60 to-[#0E0F19]/20"}`} />
|
||||
<div
|
||||
className={`absolute inset-0 ${slide.fullBrightness ? "" : "bg-gradient-to-r from-[#0E0F19]/90 via-[#0E0F19]/60 to-[#0E0F19]/20"}`}
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<div className={`relative grid grid-cols-1 md:grid-cols-2 h-full ${slide.flip ? "md:[direction:rtl]" : ""}`}>
|
||||
<div className={`flex flex-col justify-between px-8 py-8 md:px-12 md:py-10 ${slide.flip ? "md:[direction:ltr]" : ""}`}>
|
||||
<div
|
||||
className={`relative grid grid-cols-1 md:grid-cols-2 h-full ${slide.flip ? "md:[direction:rtl]" : ""}`}
|
||||
>
|
||||
<div
|
||||
className={`flex flex-col justify-between px-8 py-8 md:px-12 md:py-10 ${slide.flip ? "md:[direction:ltr]" : ""}`}
|
||||
>
|
||||
{/* Heading at top */}
|
||||
<h2 className={`${vipnagorgialla.className} font-bold italic text-[64px] leading-none tracking-normal uppercase text-[#EEE5E5]`}>
|
||||
<h2
|
||||
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2.5rem,1.5rem+4.5vw,4rem)] md:text-[64px] leading-none tracking-normal uppercase text-[#EEE5E5]`}
|
||||
>
|
||||
{highlightLAN(t("heading"))}
|
||||
</h2>
|
||||
{/* Title + description at bottom */}
|
||||
<div className="flex flex-col gap-3 pb-16">
|
||||
<Link href={slide.href}>
|
||||
<h3 className={`${vipnagorgialla.className} font-bold italic text-[clamp(2.5rem,2rem+2.5vw,5rem)] leading-none text-[#EEE5E5] hover:text-[#00A3E0] transition`}>
|
||||
<h3
|
||||
className={`${vipnagorgialla.className} font-bold italic text-[clamp(2.5rem,2rem+2.5vw,5rem)] leading-none text-[#EEE5E5] hover:text-[#00A3E0] transition`}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
</Link>
|
||||
@@ -94,7 +129,9 @@ export default function TeaserCarousel() {
|
||||
</div>
|
||||
</div>
|
||||
{/* Hero image */}
|
||||
<div className={`hidden md:block relative ${slide.flip ? "md:[direction:ltr]" : ""}`}>
|
||||
<div
|
||||
className={`hidden md:block relative ${slide.flip ? "md:[direction:ltr]" : ""}`}
|
||||
>
|
||||
<Image
|
||||
src={slide.hero}
|
||||
alt={slide.imageAlt}
|
||||
@@ -111,32 +148,34 @@ export default function TeaserCarousel() {
|
||||
{/* Arrow buttons */}
|
||||
<button
|
||||
onClick={prev}
|
||||
className="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 flex items-center justify-center bg-[#0E0F19]/50 hover:bg-[#007CAB] text-[#EEE5E5] transition"
|
||||
className="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 flex items-center justify-center bg-[#0E0F19]/50 hover:bg-[#007CAB] text-[#EEE5E5] transition z-20"
|
||||
aria-label="Previous slide"
|
||||
>
|
||||
<span className="material-symbols-outlined">chevron_left</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={next}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 flex items-center justify-center bg-[#0E0F19]/50 hover:bg-[#007CAB] text-[#EEE5E5] transition"
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 flex items-center justify-center bg-[#0E0F19]/50 hover:bg-[#007CAB] text-[#EEE5E5] transition z-20"
|
||||
aria-label="Next slide"
|
||||
>
|
||||
<span className="material-symbols-outlined">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Navigation dots */}
|
||||
<div className="flex justify-center gap-3 py-5">
|
||||
{slides.map((_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCurrent(i)}
|
||||
className={`w-3 h-3 rounded-full transition ${
|
||||
i === current ? "bg-[#007CAB]" : "bg-[#1F5673] hover:bg-[#007CAB]/60"
|
||||
}`}
|
||||
aria-label={`Slide ${i + 1}`}
|
||||
/>
|
||||
))}
|
||||
{/* Navigation dots */}
|
||||
<div className="absolute bottom-5 left-1/2 -translate-x-1/2 flex justify-center gap-3 z-20 px-3 py-2 bg-[#0E0F19]/50 backdrop-blur-md">
|
||||
{slides.map((_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCurrent(i)}
|
||||
className={`w-3 h-3 rounded-full transition ${
|
||||
i === current
|
||||
? "bg-[#00A3E0]"
|
||||
: "bg-[#1F5673] hover:bg-[#007CAB]/60"
|
||||
}`}
|
||||
aria-label={`Slide ${i + 1}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user