Add navbar

This commit is contained in:
Rene Arumetsa
2026-05-01 20:06:56 +03:00
parent f8a64f078e
commit b3f740ca46
6 changed files with 85 additions and 245 deletions

View File

@@ -0,0 +1,7 @@
<svg width="245" height="200" viewBox="0 0 245 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M69.8858 146.667L66.3566 163.333L62.122 183.333C61.9998 183.91 61.9081 184.48 61.846 185.042C61.7838 185.603 61.75 186.157 61.7458 186.7C61.7407 187.244 61.7659 187.777 61.816 188.299C61.8672 188.822 61.9455 189.334 62.0503 189.833C62.1551 190.332 62.2848 190.818 62.4408 191.29C62.5968 191.763 62.7786 192.221 62.9837 192.664C63.1887 193.107 63.4177 193.535 63.6697 193.946C63.9217 194.358 64.1969 194.752 64.4937 195.129C64.7904 195.505 65.1086 195.864 65.4479 196.203C65.7873 196.542 66.1473 196.862 66.5271 197.161C66.9068 197.46 67.3054 197.739 67.7234 197.995C68.1415 198.251 68.5788 198.484 69.033 198.694C69.4872 198.904 69.9599 199.091 70.448 199.252C70.9362 199.414 71.4395 199.552 71.9594 199.663C72.4793 199.773 73.015 199.857 73.5645 199.914C74.114 199.97 74.6773 200 75.2542 200H95.2494C95.8264 200 96.4021 199.97 96.9756 199.914C97.5491 199.857 98.1206 199.773 98.6874 199.663C99.2541 199.552 99.8179 199.414 100.375 199.252C100.931 199.091 101.482 198.904 102.025 198.694C102.568 198.483 103.102 198.251 103.629 197.995C104.155 197.739 104.673 197.46 105.179 197.161C105.686 196.862 106.182 196.542 106.665 196.203C107.148 195.864 107.618 195.505 108.074 195.129C108.531 194.752 108.972 194.358 109.398 193.946C109.824 193.535 110.236 193.107 110.629 192.664C111.021 192.221 111.396 191.763 111.752 191.29C112.108 190.818 112.444 190.332 112.76 189.833C113.076 189.334 113.37 188.822 113.642 188.299C113.915 187.777 114.166 187.244 114.392 186.7C114.618 186.157 114.818 185.603 114.993 185.042C115.169 184.48 115.319 183.91 115.441 183.333L119.676 163.333L123.205 146.667H106.542H86.5471H69.8858Z" fill="#EEE5E5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.0559 76.6665L98.6641 136.666L194.683 76.6665L113.348 106.666L111.369 76.6665L96.6854 106.666L28.0559 76.6665Z" fill="#007CAB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M177.591 0L174.061 16.6666L167.003 49.9999L163.474 66.6666H180.137H213.462C214.039 66.6666 214.615 66.6373 215.188 66.5806C215.762 66.5239 216.333 66.44 216.9 66.3294C217.467 66.2188 218.031 66.0812 218.587 65.9193C219.144 65.7575 219.695 65.571 220.238 65.3607C220.781 65.1505 221.315 64.9174 221.842 64.6615C222.368 64.4055 222.886 64.1271 223.392 63.8281C223.898 63.5292 224.394 63.2089 224.877 62.8698C225.36 62.5307 225.831 62.1722 226.287 61.7956C226.743 61.419 227.185 61.0246 227.611 60.6133C228.037 60.2021 228.447 59.7739 228.84 59.3308C229.233 58.8875 229.609 58.4295 229.965 57.9571C230.321 57.4847 230.656 56.9989 230.972 56.5C231.288 56.0012 231.583 55.4887 231.855 54.9662C232.127 54.4437 232.378 53.9106 232.603 53.3672C232.829 52.8237 233.031 52.27 233.206 51.7083C233.382 51.1468 233.532 50.5771 233.654 50L240.712 16.6667L244.241 7.68946e-05H227.579H194.253L177.591 0Z" fill="#EEE5E5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M94.2771 0L80.1606 66.6666H146.811L160.928 0H94.2771Z" fill="#EEE5E5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.9636 0.000115342L7.4345 16.6667L0.376255 50.0001C0.254079 50.5771 0.162655 51.1468 0.100307 51.7083C0.0380859 52.27 0.00430454 52.8238 2.20965e-05 53.3672C-0.00501607 53.9107 0.0201957 54.4437 0.0703255 54.9662C0.121463 55.4887 0.199812 56.0012 0.304606 56.5C0.4094 56.9989 0.54064 57.4847 0.696445 57.9571C0.852376 58.4295 1.03301 58.8875 1.23796 59.3308C1.44302 59.7739 1.67205 60.2021 1.92401 60.6133C2.17599 61.0245 2.45135 61.419 2.74804 61.7956C3.04477 62.1722 3.36289 62.5307 3.70223 62.8698C4.04158 63.2089 4.40158 63.5293 4.78139 63.8282C5.16119 64.1271 5.55967 64.4055 5.97773 64.6615C6.3958 64.9175 6.83316 65.1504 7.28732 65.3607C7.74149 65.5711 8.21425 65.7575 8.70235 65.9193C9.19047 66.0811 9.69379 66.2188 10.2137 66.3294C10.7336 66.44 11.2692 66.5239 11.8188 66.5806C12.3682 66.6373 12.9315 66.6666 13.5085 66.6666H46.8339H63.4965L67.0257 49.9999L74.0839 16.6666L77.613 0H60.9503H27.6249L10.9636 0.000115342Z" fill="#EEE5E5"/>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,110 +0,0 @@
import { notFound } from "next/navigation";
import ReactMarkdown, { Components } from "react-markdown";
import { vipnagorgialla } from "@/components/Vipnagorgialla";
import SectionDivider from "@/components/SectionDivider";
import { getTranslations, setRequestLocale } from "next-intl/server";
import { loadRulesBun } from "@/lib/loadRules";
// Map of valid slugs to their translation keys
const rulesMap = {
lol: {
titleKey: "rules.lolRules",
},
cs2: {
titleKey: "rules.cs2Rules",
},
} as const;
type RuleSlug = keyof typeof rulesMap;
interface PageProps {
params: Promise<{ slug: string; locale: string }>;
}
async function getRuleContent(slug: string, locale: string) {
if (!Object.keys(rulesMap).includes(slug)) {
return null;
}
const ruleConfig = rulesMap[slug as RuleSlug];
try {
const content = await loadRulesBun(
slug as "cs2" | "lol",
locale as "et" | "en",
);
return {
content,
titleKey: ruleConfig.titleKey,
};
} catch (error) {
console.error(
`Error reading rule file for slug ${slug} in locale ${locale}:`,
error,
);
return null;
}
}
export default async function RulePage({ params }: PageProps) {
const { slug, locale } = await params;
setRequestLocale(locale);
const t = await getTranslations({ locale });
const ruleData = await getRuleContent(slug, locale);
if (!ruleData) {
notFound();
}
const headingStyle = `text-5xl sm:text-6xl ${vipnagorgialla.className} font-bold uppercase italic text-[#2A2C3F] dark:text-[#EEE5E5]`;
return (
<div>
<div className="flex flex-col min-h-[90vh] m-6 mt-16 md:m-16">
<h1 className={`${headingStyle} mt-8 md:mt-16 mb-4`}>
{t(ruleData.titleKey)}
</h1>
<div className="prose prose-lg dark:prose-invert max-w-none">
<ReactMarkdown
components={
{
h1: (props) => (
<h1 className="text-3xl md:text-4xl font-bold my-4">
{props.children}
</h1>
),
h2: (props) => (
<h2 className="text-2xl md:text-3xl font-semibold my-3">
{props.children}
</h2>
),
ol: (props) => (
<ol className="list-none ml-6 md:text-xl">
{props.children}
</ol>
),
ul: (props) => (
<ul className="list-disc ml-6 md:text-xl">
{props.children}
</ul>
),
p: (props) => <p className="md:text-xl">{props.children}</p>,
} as Components
}
>
{ruleData.content}
</ReactMarkdown>
</div>
</div>
<SectionDivider />
</div>
);
}
export async function generateStaticParams() {
return Object.keys(rulesMap).map((slug) => ({
slug,
}));
}

View File

@@ -1,66 +0,0 @@
import { vipnagorgialla } from "@/components/Vipnagorgialla";
import SectionDivider from "@/components/SectionDivider";
import { getTranslations, setRequestLocale } from "next-intl/server";
import NextLink from "next/link";
export default async function RulesMenu({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations({ locale });
const headingStyle = `text-4xl md:text-5xl lg:text-6xl ${vipnagorgialla.className} font-bold italic text-[#2A2C3F] dark:text-[#EEE5E5] uppercase`;
const boxStyle = `-skew-x-2 md:-skew-x-5 text-white md:px-12 hover:scale-103 transition-all duration-150 w-full md:w-xl lg:w-[400px]`;
const boxTextStyle = `text-2xl md:text-3xl ${vipnagorgialla.className} font-bold uppercase text-[#EEE5E5] pb-2 break-normal whitespace-pre-line`;
return (
<div>
<div className="flex flex-col md:m-16">
<h1 className={`${headingStyle} ml-3 mt-24 md:ml-0 md:mt-16 mb-4 px-4`}>
{t("rules.title")}
</h1>
<div className="flex flex-wrap flex-row lg:mt-16 justify-center lg:items-start gap-12 flex-grow mb-8">
<NextLink href="/kodukord">
<div className={`${boxStyle} bg-[#007CAB] py-20 px-8`}>
<h2 className={`${boxTextStyle}`}>{t("rules.houseRules")}</h2>
</div>
</NextLink>
<NextLink href="/reeglid/cs2">
<div className={`${boxStyle} bg-[#1F5673] py-20 px-8`}>
<h2 className={`${boxTextStyle}`}>{t("rules.cs2Rules")}</h2>
</div>
</NextLink>
<NextLink href="reeglid/lol">
<div className={`${boxStyle} bg-[#007CAB] py-20 px-8`}>
<h2 className={`${boxTextStyle}`}>{t("rules.lolRules")}</h2>
</div>
</NextLink>
{/* Minitourn. link coming soon*/}
{/*<Link href="">*/}
{/* ajutine div. kui asendate lingiga, siis saab selle ära võtta */}
<div className="cursor-not-allowed">
<div
className={`${boxStyle} bg-[#1F5673] py-16 px-8 opacity-50 pointer-events-none`}
>
<h2 className={`${boxTextStyle}`}>{t("rules.miniRules")}</h2>
</div>
</div>
{/*</Link>*/}
</div>
</div>
<SectionDivider />
</div>
);
}

View File

@@ -1,8 +1,11 @@
"use client";
import Image from "next/image";
import { Link } from "@/i18n/routing";
import { vipnagorgialla } from "@/components/Vipnagorgialla";
// Icons
import {
MdClose,
MdMenu,
MdSunny,
MdModeNight,
@@ -23,32 +26,63 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
// Fonts
// import { vipnagorgialla } from "@/components/Vipnagorgialla";
interface NavItem {
href:
| "/"
| "/ajakava"
| "/haldus"
| "/kodukord"
| "/messiala"
| "/piletid"
| "/striim"
| "/turniirid";
label: string;
}
interface HeaderProps {
isOpen: boolean;
onToggle: () => void;
themeLabels: {
light: string;
dark: string;
system: string;
};
navItems: NavItem[];
}
const Header = ({ isOpen, onToggle, themeLabels }: HeaderProps) => {
const Header = ({ themeLabels, navItems }: HeaderProps) => {
const { theme, setTheme } = useTheme();
// Filter nav items for the horizontal bar (exclude kodukord)
const mainNavItems = navItems.filter(
(item) => item.href !== "/kodukord"
);
return (
<header className="px-8 py-2 md:px-12 flex items-center bg-[#EEE5E5] dark:bg-[#0E0F19] border-b-3 border-[#1F5673] justify-between text-[#2A2C3F] dark:text-[#EEE5E5]">
<button onClick={onToggle}>
{isOpen ? (
<MdClose className="h-12 w-12 text-[#2A2C3F] dark:text-[#EEE5E5] cursor-pointer" />
) : (
<MdMenu className="h-12 w-12 text-[#2A2C3F] dark:text-[#EEE5E5] cursor-pointer" />
)}
</button>
<div className="flex items-center gap-2">
<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">
<Image
src="/tipilan-icon-white.svg"
alt="TipiLAN"
width={49}
height={40}
className="h-10 w-auto"
/>
</Link>
{/* 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>
<LanguageSwitcher />
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -57,8 +91,8 @@ const Header = ({ isOpen, onToggle, themeLabels }: HeaderProps) => {
size="icon"
className="size-10 cursor-pointer"
>
<MdSunny className="scale-135 text-[#2A2C3F] dark:hidden" />
<MdModeNight className="scale-135 dark:text-[#EEE5E5] not-dark:hidden" />
<MdSunny className="scale-135 text-[#EEE5E5] dark:hidden" />
<MdModeNight className="scale-135 text-[#EEE5E5] not-dark:hidden" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
@@ -93,6 +127,32 @@ const Header = ({ isOpen, onToggle, themeLabels }: HeaderProps) => {
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{/* Mobile menu button */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="md: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`}
>
{item.label}
</Link>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
</header>
);

View File

@@ -1,9 +1,5 @@
"use client";
import { useState, useEffect } from "react";
import { usePathname } from "@/i18n/routing";
import { Link } from "@/i18n/routing";
import { vipnagorgialla } from "@/components/Vipnagorgialla";
import Header from "./Header";
interface NavItem {
@@ -14,7 +10,6 @@ interface NavItem {
| "/kodukord"
| "/messiala"
| "/piletid"
| "/reeglid"
| "/striim"
| "/turniirid";
label: string;
@@ -33,52 +28,7 @@ export default function SidebarLayoutClient({
themeLabels,
navItems,
}: SidebarLayoutClientProps) {
const [isOpen, setIsOpen] = useState(false);
const pathname = usePathname();
const toggleSidebar = () => setIsOpen(!isOpen);
// Close sidebar when route changes
useEffect(() => {
if (isOpen) {
setIsOpen(false);
}
}, [pathname]);
return (
<>
<Header
isOpen={isOpen}
onToggle={toggleSidebar}
themeLabels={themeLabels}
/>
{/* Sidebar */}
<>
<div
className="fixed inset-0 backdrop-blur mt-16 z-20"
style={{ display: isOpen ? "block" : "none" }}
onClick={() => setIsOpen(false)}
></div>
<div
className={`text-3xl md:text-4xl ${vipnagorgialla.className} font-bold break-all italic uppercase fixed flex items-start xs:pl-25 pl-20 sm:pl-20 md:pl-24 flex-col gap-8 pt-16 top-0 left-0 h-[99vh] mt-16 -skew-x-5 border-r-3 border-[#1F5673] w-screen sm:w-96 md:w-128 bg-[#EEE5E5] dark:bg-[#0E0F19] text-[#2A2C3F] dark:text-[#EEE5E5] transition-transform transform z-20`}
style={{
transform: isOpen
? "translateX(-13%) skewX(calc(5deg * -1)"
: "translateX(-150%) skewX(calc(5deg * -1)",
}}
>
{navItems.map((item) => (
<Link
key={item.href}
href={item.href}
className="hover:text-[#00A3E0] md:hover:translate-x-2 transition duration-150"
>
{item.label}
</Link>
))}
</div>
</>
</>
<Header themeLabels={themeLabels} navItems={navItems} />
);
}

View File

@@ -19,7 +19,6 @@ export default async function SidebarLayoutServer() {
{ href: "/ajakava" as const, label: navT("schedule") },
{ href: "/turniirid" as const, label: navT("tournaments") },
{ href: "/kodukord" as const, label: navT("houserules") },
{ href: "/reeglid" as const, label: navT("rules") },
];
return <SidebarLayoutClient themeLabels={themeLabels} navItems={navItems} />;