Merge pull request #46 from Lapikud/development

React 19 update changed the way ref works. Removed redundant packages and updated markdown to use Bun
pull/56/head
Voltages 4 months ago committed by GitHub
commit 5b464e9f6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 19
      next.config.ts
  2. 2
      package.json
  3. 97
      src/app/kodukord/page.tsx
  4. 940
      src/app/messiala/page.tsx
  5. 146
      src/app/reeglid/[slug]/page.tsx
  6. 90
      src/app/reeglid/page.tsx
  7. 210
      src/app/striim/page.tsx
  8. 332
      src/app/turniirid/page.tsx
  9. 30
      src/components/ui/button.tsx
  10. 5
      src/data/rules/cs2.md

@ -1,7 +1,24 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ async headers() {
return [
{
source: "/(.*)",
headers: [
{
key: "Content-Security-Policy",
value:
"frame-src 'self' https://tipilan.ee https://player.twitch.tv https://embed.twitch.tv; frame-ancestors 'self' https://tipilan.ee;",
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN",
},
],
},
];
},
}; };
export default nextConfig; export default nextConfig;

@ -27,7 +27,6 @@
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"drizzle-orm": "^0.44.2", "drizzle-orm": "^0.44.2",
"gray-matter": "^4.0.3",
"lucide-react": "^0.522.0", "lucide-react": "^0.522.0",
"material-symbols": "^0.31.8", "material-symbols": "^0.31.8",
"next": "15.3.0", "next": "15.3.0",
@ -36,7 +35,6 @@
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"three": "^0.178.0", "three": "^0.178.0",
"tw-animate-css": "^1.3.4" "tw-animate-css": "^1.3.4"

@ -1,55 +1,56 @@
// app/kodukord/page.tsx (App Router) // app/kodukord/page.tsx (App Router)
import fs from "node:fs"; import ReactMarkdown, { Components } from "react-markdown";
import path from "node:path"; import { vipnagorgialla } from "@/components/Vipnagorgialla";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import {vipnagorgialla} from "@/components/Vipnagorgialla";
import SectionDivider from "@/components/SectionDivider"; import SectionDivider from "@/components/SectionDivider";
export const runtime = "nodejs"; // ensure fs is available (not Edge) export default async function Page() {
export const dynamic = "force-static"; // read at build time const file = Bun.file("src/data/kodukord.md");
const content = await file.text();
export default function Page() { return (
const filePath = path.join(process.cwd(), "src/data", "kodukord.md"); <div>
const content = fs.readFileSync(filePath, "utf8"); <div className="flex flex-col min-h-[90vh] m-6 mt-16 md:m-16">
{/* Page title (separate from markdown headings) */}
<h1
className={`text-4xl md:text-5xl lg:text-6xl ${vipnagorgialla.className} font-bold italic text-[#2A2C3F] dark:text-[#EEE5E5] mt-8 md:mt-16 mb-4 uppercase`}
>
Kodukord
</h1>
return ( <div className="prose prose-lg dark:prose-invert max-w-none">
<div> <ReactMarkdown
<div className="flex flex-col min-h-[90vh] m-6 mt-16 md:m-16"> components={
{/* Page title (separate from markdown headings) */} {
<h1 h1: (props) => (
className={`text-4xl md:text-5xl lg:text-6xl ${vipnagorgialla.className} font-bold italic text-[#2A2C3F] dark:text-[#EEE5E5] mt-8 md:mt-16 mb-4 uppercase`} <h1 className="text-3xl md:text-4xl font-bold my-4">
> {props.children}
Kodukord </h1>
</h1> ),
h2: (props) => (
<div className="prose prose-lg dark:prose-invert max-w-none"> <h2 className="text-2xl md:text-3xl font-semibold my-3">
<ReactMarkdown {props.children}
remarkPlugins={[remarkGfm]} </h2>
components={{ ),
h1: ({node, ...props}) => ( ol: (props) => (
<h1 className="text-3xl md:text-4xl font-bold my-4" {...props} /> <ol className="list-decimal ml-6 md:text-xl">
), {props.children}
h2: ({node, ...props}) => ( </ol>
<h2 className="text-2xl md:text-3xl font-semibold my-3" {...props} /> ),
), ul: (props) => (
ol: ({node, ...props}) => ( <ul className="list-disc ml-6 md:text-xl">
<ol className="list-decimal ml-6 md:text-xl" {...props} /> {props.children}
), </ul>
ul: ({node, ...props}) => ( ),
<ul className="list-disc ml-6 md:text-xl" {...props} /> p: (props) => <p className="md:text-xl">{props.children}</p>,
), } as Components
p: ({node, ...props}) => ( }
<p className="md:text-xl" {...props} /> >
), {content}
}} </ReactMarkdown>
>
{content}
</ReactMarkdown>
</div>
</div>
<SectionDivider/>
</div> </div>
); </div>
<SectionDivider />
</div>
);
} }

@ -1,485 +1,499 @@
"use client"; "use client";
import {vipnagorgialla} from "@/components/Vipnagorgialla"; import { vipnagorgialla } from "@/components/Vipnagorgialla";
import * as THREE from "three"; import * as THREE from "three";
import {useEffect, useRef, useState} from "react"; import { useEffect, useRef, useState } from "react";
import {EyeClosed, Eye} from "lucide-react"; import { EyeClosed, Eye } from "lucide-react";
import SectionDivider from "@/components/SectionDivider";
// Define interface for the ref with toggle function // Define interface for the ref with toggle function
interface MountRefCurrent extends HTMLDivElement { interface MountRefCurrent extends HTMLDivElement {
toggleDividers?: (show: boolean) => void; toggleDividers?: (show: boolean) => void;
} }
export default function Expo() { export default function Expo() {
const mountRef = useRef<MountRefCurrent | null>(null); const mountRef = useRef<MountRefCurrent | null>(null);
const [hoveredRoom, setHoveredRoom] = useState<string | null>(null); const [hoveredRoom, setHoveredRoom] = useState<string | null>(null);
const [mousePosition, setMousePosition] = useState({x: 0, y: 0}); const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
const [showDividers, setShowDividers] = useState<boolean>(true); const [showDividers, setShowDividers] = useState<boolean>(true);
useEffect(() => { useEffect(() => {
if (!mountRef.current) return; if (!mountRef.current) return;
// Copy ref to variable to avoid stale closure in cleanup // Copy ref to variable to avoid stale closure in cleanup
const mountElement = mountRef.current; const mountElement = mountRef.current;
let dividersRef: THREE.Mesh[] = []; let dividersRef: THREE.Mesh[] = [];
// Scene setup // Scene setup
const scene = new THREE.Scene(); const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0e0f19); scene.background = new THREE.Color(0x0e0f19);
// Get responsive dimensions // Get responsive dimensions
const getResponsiveDimensions = () => { const getResponsiveDimensions = () => {
const container = mountRef.current; const container = mountRef.current;
if (!container) return {width: 800, height: 600}; if (!container) return { width: 800, height: 600 };
const containerWidth = container.offsetWidth; const containerWidth = container.offsetWidth;
const maxWidth = Math.min(containerWidth, 800); const maxWidth = Math.min(containerWidth, 800);
const width = Math.max(maxWidth, 300); // Minimum width const width = Math.max(maxWidth, 300); // Minimum width
const height = (width * 600) / 800; // Maintain aspect ratio const height = (width * 600) / 800; // Maintain aspect ratio
return {width, height}; return { width, height };
}; };
const {width, height} = getResponsiveDimensions(); const { width, height } = getResponsiveDimensions();
// Isometric camera setup with responsive sizing // Isometric camera setup with responsive sizing
const aspect = width / height; const aspect = width / height;
const baseFrustumSize = 14; const baseFrustumSize = 14;
const frustumSize = width < 600 ? baseFrustumSize * 0.8 : baseFrustumSize; // Smaller frustum for mobile const frustumSize = width < 600 ? baseFrustumSize * 0.8 : baseFrustumSize; // Smaller frustum for mobile
const camera = new THREE.OrthographicCamera( const camera = new THREE.OrthographicCamera(
(frustumSize * aspect) / -2, (frustumSize * aspect) / -2,
(frustumSize * aspect) / 2, (frustumSize * aspect) / 2,
frustumSize / 2, frustumSize / 2,
frustumSize / -2, frustumSize / -2,
1, 1,
1000, 1000,
); );
// Position camera for isometric view // Position camera for isometric view
camera.position.set(10, 10, 14); camera.position.set(10, 10, 14);
camera.lookAt(-1.4, 0, 0); camera.lookAt(-1.4, 0, 0);
// Renderer // Renderer
const renderer = new THREE.WebGLRenderer({antialias: true}); const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width, height); renderer.setSize(width, height);
renderer.shadowMap.enabled = true; renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; renderer.shadowMap.type = THREE.PCFSoftShadowMap;
mountElement.appendChild(renderer.domElement); mountElement.appendChild(renderer.domElement);
// Raycaster for mouse interactions // Raycaster for mouse interactions
const raycaster = new THREE.Raycaster(); const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2(); const mouse = new THREE.Vector2();
// Lighting // Lighting
const ambientLight = new THREE.AmbientLight(0x404040, 1.2); const ambientLight = new THREE.AmbientLight(0x404040, 1.2);
scene.add(ambientLight); scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5); const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5);
directionalLight.position.set(10, 10, 5); directionalLight.position.set(10, 10, 5);
directionalLight.castShadow = false; directionalLight.castShadow = false;
directionalLight.shadow.mapSize.width = 2048; directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048; directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight); scene.add(directionalLight);
// Room colors and names // Room colors and names
const roomColors = [ const roomColors = [
0xff6b35, // Orange - Mänguklubi 0x343434, // Gray - Lauamängude ala
0x4ecdc4, // Turquoise - Baariala 0x4ecdc4, // Turquoise - Baariala
0xffe66d, // Yellow - EVAL 0xffe66d, // Yellow - EVAL
0xe74c3c, // Red - Redbull 0xff6600, // Orange - Redbull Sim Racing
0x9b59b6, // Purple - Võitlusmängu ala 0xff1493, // Deep Pink - Võitlusmängu ala
0x3498db, // Blue - Sony 0x3498db, // Blue - Sony
0x2ecc71, // Green - Chillimisala 0x2ecc71, // Green - Lava
]; 0x080682, // Dark Blue - LVLup!
0xc02841, // Red - RedBull
const roomNames = [ ];
"Mänguklubi",
"Baariala", // Create individual rooms as rectangles with custom positions
"EVAL", const rooms: THREE.Mesh[] = [];
"Redbull", const roomData: Array<{
"Võitlusmängu ala", mesh: THREE.Mesh;
"Sony", name: string;
"Chillimisala", originalColor: number;
]; originalScale: THREE.Vector3;
}> = [];
// Create individual rooms as rectangles with custom positions const dividers: THREE.Mesh[] = [];
const rooms: THREE.Mesh[] = [];
const roomData: Array<{ // Define rooms with custom positions, sizes and colors
mesh: THREE.Mesh; const roomDefinitions = [
name: string; {
originalColor: number; width: 7,
originalScale: THREE.Vector3; height: 0.7,
}> = []; depth: 3,
const dividers: THREE.Mesh[] = []; x: 2.5,
z: 4,
// Define rooms with custom positions, sizes and colors color: roomColors[0],
const roomDefinitions = [ name: "Lauamängude ala",
{ },
width: 7, {
height: 0.7, width: 3.5,
depth: 3, height: 0.7,
x: 2.5, depth: 1.2,
z: 4, x: 0.7,
color: roomColors[0], z: -0.3,
name: roomNames[0], color: roomColors[1],
}, // Mänguklubi name: "Baariala",
// { },
// width: 2.5, {
// height: 0.7, width: 1.8,
// depth: 0.7, height: 0.7,
// x: 1, depth: 1.5,
// z: 0, x: 1,
// color: roomColors[1], z: -3.5,
// name: roomNames[1], color: roomColors[2],
// }, // Baariala name: "EVAL",
{ },
width: 1.8, {
height: 0.7, width: 2,
depth: 1.5, height: 0.7,
x: 2.5, depth: 4.5,
z: -3.5, x: 5.2,
color: roomColors[2], z: -2,
name: roomNames[2], color: roomColors[3],
}, // EVAL name: "Red Bull Sim Racing",
{ },
width: 2.2, {
height: 0.7, width: 3,
depth: 4.5, height: 0.7,
x: 5, depth: 1.5,
z: -2, x: -1.7,
color: roomColors[3], z: -3.5,
name: roomNames[3], color: roomColors[4],
}, // Redbull name: "Võitlusmängu ala",
{ },
width: 3, // {
height: 0.7, // width: 1.8,
depth: 1.3, // height: 0.7,
x: 0, // depth: 1.5,
z: -3.5, // x: -4.3,
color: roomColors[4], // z: -3.5,
name: roomNames[4], // color: roomColors[5],
}, // Võitlusmängu ala // name: "Sony",
{ // },
width: 1.8, {
height: 0.7, width: 3,
depth: 1.5, height: 0.7,
x: -2.55, depth: 1.7,
z: -3.5, x: -3.5,
color: roomColors[5], z: -0.5,
name: roomNames[5], color: roomColors[7],
}, // Sony name: "LVLup!",
{ },
width: 4, //{
height: 0.7, // width: 2,
depth: 4, // height: 0.7,
x: -5.5, // depth: 4,
z: -2.3, // x: -6.4,
color: roomColors[6], // z: -2.3,
name: roomNames[6], // color: roomColors[6],
}, // Chillimisala // name: "Lava",
]; //},
{
roomDefinitions.forEach((roomDef) => { width: 1.8,
const geometry = new THREE.BoxGeometry( height: 0.7,
roomDef.width, depth: 1.5,
roomDef.height, x: 3,
roomDef.depth, z: -3.5,
); color: roomColors[8],
const material = new THREE.MeshLambertMaterial({ name: "Red Bull",
color: roomDef.color, },
}); ];
const room = new THREE.Mesh(geometry, material); roomDefinitions.forEach((roomDef) => {
room.position.set(roomDef.x, roomDef.height / 2, roomDef.z); const geometry = new THREE.BoxGeometry(
room.castShadow = true; roomDef.width,
room.receiveShadow = true; roomDef.height,
room.userData = {name: roomDef.name, originalColor: roomDef.color}; roomDef.depth,
);
scene.add(room); const material = new THREE.MeshLambertMaterial({
rooms.push(room); color: roomDef.color,
roomData.push({ });
mesh: room,
name: roomDef.name, const room = new THREE.Mesh(geometry, material);
originalColor: roomDef.color, room.position.set(roomDef.x, roomDef.height / 2, roomDef.z);
originalScale: room.scale.clone(), room.castShadow = true;
}); room.receiveShadow = true;
}); room.userData = { name: roomDef.name, originalColor: roomDef.color };
// Create toggleable room dividers scene.add(room);
const createTogglableDivider = ( rooms.push(room);
width: number, roomData.push({
height: number, mesh: room,
depth: number, name: roomDef.name,
x: number, originalColor: roomDef.color,
z: number, originalScale: room.scale.clone(),
) => { });
const wallGeometry = new THREE.BoxGeometry(width, height, depth); });
const wallMaterial = new THREE.MeshLambertMaterial({
color: 0x555555, // Create toggleable room dividers
transparent: true, const createTogglableDivider = (
opacity: 0, width: number,
}); height: number,
depth: number,
const wall = new THREE.Mesh(wallGeometry, wallMaterial); x: number,
wall.position.set(x, height / 2, z); z: number,
wall.visible = false; ) => {
scene.add(wall); const wallGeometry = new THREE.BoxGeometry(width, height, depth);
dividers.push(wall); const wallMaterial = new THREE.MeshLambertMaterial({
}; color: 0x555555,
transparent: true,
// Add strategic dividers between major areas opacity: 0,
createTogglableDivider(10, 2, 2, -2.5, 1.5); // Wall between main entrance });
createTogglableDivider(2, 2, 2, 5.5, 1.5); // Wall right next to Mänguklubi & Redbull
const wall = new THREE.Mesh(wallGeometry, wallMaterial);
// Store dividers reference for later access wall.position.set(x, height / 2, z);
dividersRef = [...dividers]; wall.visible = false;
scene.add(wall);
// Ground plane dividers.push(wall);
const groundGeometry = new THREE.PlaneGeometry(14, 10.5); };
const groundMaterial = new THREE.MeshLambertMaterial({color: 0xcccccc});
const ground = new THREE.Mesh(groundGeometry, groundMaterial); // Add strategic dividers between major areas
ground.rotation.x = -Math.PI / 2; createTogglableDivider(10, 2, 2, -2.5, 1.5); // Wall between main entrance
ground.position.x = -1.1; createTogglableDivider(2, 2, 2, 5.5, 1.5); // Wall right next to Lauamängud & Redbull Sim Racing
ground.position.y = -0.5;
ground.receiveShadow = true; // Store dividers reference for later access
scene.add(ground); dividersRef = [...dividers];
// Second ground plane // Ground plane
const groundGeometry2 = new THREE.PlaneGeometry(2, 7); const groundGeometry = new THREE.PlaneGeometry(14, 10.5);
const groundMaterial2 = new THREE.MeshLambertMaterial({ const groundMaterial = new THREE.MeshLambertMaterial({ color: 0xcccccc });
color: 0xcccccc, const ground = new THREE.Mesh(groundGeometry, groundMaterial);
}); ground.rotation.x = -Math.PI / 2;
const ground2 = new THREE.Mesh(groundGeometry2, groundMaterial2); ground.position.x = -1.1;
ground2.rotation.x = -Math.PI / 2; ground.position.y = -0.5;
ground2.position.x = -12.2; ground.receiveShadow = true;
ground2.position.y = -5; scene.add(ground);
ground2.receiveShadow = true;
scene.add(ground2); // Second ground plane
const groundGeometry2 = new THREE.PlaneGeometry(2, 7);
// Resize handler const groundMaterial2 = new THREE.MeshLambertMaterial({
const handleResize = () => { color: 0xcccccc,
const {width: newWidth, height: newHeight} = getResponsiveDimensions(); });
const ground2 = new THREE.Mesh(groundGeometry2, groundMaterial2);
// Update camera ground2.rotation.x = -Math.PI / 2;
const newAspect = newWidth / newHeight; ground2.position.x = -12.2;
const newFrustumSize = ground2.position.y = -5;
newWidth < 600 ? baseFrustumSize * 0.8 : baseFrustumSize; ground2.receiveShadow = true;
scene.add(ground2);
camera.left = (newFrustumSize * newAspect) / -2;
camera.right = (newFrustumSize * newAspect) / 2; // Resize handler
camera.top = newFrustumSize / 2; const handleResize = () => {
camera.bottom = newFrustumSize / -2; const { width: newWidth, height: newHeight } = getResponsiveDimensions();
camera.updateProjectionMatrix();
// Update camera
// Update renderer const newAspect = newWidth / newHeight;
renderer.setSize(newWidth, newHeight); const newFrustumSize =
}; newWidth < 600 ? baseFrustumSize * 0.8 : baseFrustumSize;
// Add resize event listener camera.left = (newFrustumSize * newAspect) / -2;
window.addEventListener("resize", handleResize); camera.right = (newFrustumSize * newAspect) / 2;
camera.top = newFrustumSize / 2;
// Mouse event handlers camera.bottom = newFrustumSize / -2;
const onMouseMove = (event: MouseEvent) => { camera.updateProjectionMatrix();
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; // Update renderer
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; renderer.setSize(newWidth, newHeight);
};
// Update mouse position for tooltip
setMousePosition({x: event.clientX, y: event.clientY}); // Add resize event listener
window.addEventListener("resize", handleResize);
// Update raycaster
raycaster.setFromCamera(mouse, camera); // Mouse event handlers
const intersects = raycaster.intersectObjects(rooms); const onMouseMove = (event: MouseEvent) => {
const rect = renderer.domElement.getBoundingClientRect();
// Reset all rooms to original state mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
roomData.forEach(({mesh, originalColor, originalScale}) => { mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
(mesh.material as THREE.MeshLambertMaterial).color.setHex(
originalColor, // Update mouse position for tooltip
); setMousePosition({ x: event.clientX, y: event.clientY });
mesh.scale.copy(originalScale);
}); // Update raycaster
raycaster.setFromCamera(mouse, camera);
if (intersects.length > 0) { const intersects = raycaster.intersectObjects(rooms);
const hoveredMesh = intersects[0].object as THREE.Mesh;
const roomInfo = roomData.find((r) => r.mesh === hoveredMesh); // Reset all rooms to original state
roomData.forEach(({ mesh, originalColor, originalScale }) => {
if (roomInfo) { (mesh.material as THREE.MeshLambertMaterial).color.setHex(
// Apply hover effects originalColor,
(hoveredMesh.material as THREE.MeshLambertMaterial).color.setHex( );
0xffffff, mesh.scale.copy(originalScale);
); });
hoveredMesh.scale.multiplyScalar(1.02);
setHoveredRoom(roomInfo.name); if (intersects.length > 0) {
} const hoveredMesh = intersects[0].object as THREE.Mesh;
} else { const roomInfo = roomData.find((r) => r.mesh === hoveredMesh);
setHoveredRoom(null);
} if (roomInfo) {
}; // Apply hover effects
(hoveredMesh.material as THREE.MeshLambertMaterial).color.setHex(
// Add mouse event listener 0xffffff,
renderer.domElement.addEventListener("mousemove", onMouseMove); );
hoveredMesh.scale.multiplyScalar(1.02);
// Animation loop setHoveredRoom(roomInfo.name);
const animate = () => {
requestAnimationFrame(animate);
// Gentle floating animation for rooms
rooms.forEach((room, index) => {
const originalY = 0.25; // height / 2 for the room height of 0.5
const baseY = originalY + Math.sin(Date.now() * 0.001 + index) * 0.05;
// Maintain current scale while updating Y position
room.position.y = baseY;
});
renderer.render(scene, camera);
};
animate();
// Function to toggle dividers
const toggleDividers = (show: boolean) => {
dividersRef.forEach((divider) => {
divider.visible = show;
(divider.material as THREE.MeshLambertMaterial).opacity = show
? 0.4
: 0;
});
};
// Expose toggle function to parent scope
mountElement.toggleDividers = toggleDividers;
// Cleanup
return () => {
window.removeEventListener("resize", handleResize);
renderer.domElement.removeEventListener("mousemove", onMouseMove);
if (mountElement && renderer.domElement) {
mountElement.removeChild(renderer.domElement);
}
renderer.dispose();
};
}, []);
// Update dividers when showDividers state changes
useEffect(() => {
if (mountRef.current?.toggleDividers) {
mountRef.current.toggleDividers(showDividers);
} }
}, [showDividers]); } else {
setHoveredRoom(null);
return ( }
<div> };
<div className="flex flex-col min-h-[90vh] m-6 mt-16 md:m-16">
<h1 // Add mouse event listener
className={`text-4xl md:text-5xl lg:text-6xl ${vipnagorgialla.className} font-bold italic text-[#2A2C3F] dark:text-[#EEE5E5] mt-8 md:mt-16 mb-4 uppercase`} renderer.domElement.addEventListener("mousemove", onMouseMove);
>
Messiala // Animation loop
</h1> const animate = () => {
<div className="mb-6"> requestAnimationFrame(animate);
<h2 className="text-2xl text-[#2A2C3F] dark:text-[#EEE5E5] mb-3">
Tudengimaja // Gentle floating animation for rooms
</h2> rooms.forEach((room, index) => {
<div className="flex flex-wrap gap-4 pb-4"> const originalY = 0.25; // height / 2 for the room height of 0.5
<div className="flex items-center gap-2"> const baseY = originalY + Math.sin(Date.now() * 0.001 + index) * 0.05;
<div
className="w-4 h-4 border border-gray-300" // Maintain current scale while updating Y position
style={{backgroundColor: "#ff6b35"}} room.position.y = baseY;
></div> });
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
Mänguklubi renderer.render(scene, camera);
</span> };
</div>
<div className="items-center gap-2 hidden"> animate();
<div
className="w-4 h-4 border border-gray-300" // Function to toggle dividers
style={{backgroundColor: "#4ecdc4"}} const toggleDividers = (show: boolean) => {
></div> dividersRef.forEach((divider) => {
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]"> divider.visible = show;
(divider.material as THREE.MeshLambertMaterial).opacity = show
? 0.4
: 0;
});
};
// Expose toggle function to parent scope
mountElement.toggleDividers = toggleDividers;
// Cleanup
return () => {
window.removeEventListener("resize", handleResize);
renderer.domElement.removeEventListener("mousemove", onMouseMove);
if (mountElement && renderer.domElement) {
mountElement.removeChild(renderer.domElement);
}
renderer.dispose();
};
}, []);
// Update dividers when showDividers state changes
useEffect(() => {
if (mountRef.current?.toggleDividers) {
mountRef.current.toggleDividers(showDividers);
}
}, [showDividers]);
return (
<div className="flex flex-col min-h-[90vh] m-6 mt-16 md:m-16">
<h1
className={`text-4xl md:text-5xl lg:text-6xl ${vipnagorgialla.className} font-bold italic text-[#2A2C3F] dark:text-[#EEE5E5] mt-8 md:mt-16 mb-4 uppercase`}
>
Messiala
</h1>
<div className="mb-6">
<h2 className="text-2xl text-[#2A2C3F] dark:text-[#EEE5E5] mb-3">
Tudengimaja
</h2>
<div className="flex flex-wrap gap-4 pb-4">
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#4ecdc4" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
Baariala Baariala
</span> </span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
className="w-4 h-4 border border-gray-300" className="w-4 h-4 border border-gray-300"
style={{backgroundColor: "#ffe66d"}} style={{ backgroundColor: "#ffe66d" }}
></div> ></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]"> <span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
EVAL EVAL
</span> </span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
className="w-4 h-4 border border-gray-300" className="w-4 h-4 border border-gray-300"
style={{backgroundColor: "#e74c3c"}} style={{ backgroundColor: "#343434" }}
></div> ></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]"> <span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
Redbull Lauamängude ala
</span> </span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
className="w-4 h-4 border border-gray-300" className="w-4 h-4 border border-gray-300"
style={{backgroundColor: "#9b59b6"}} style={{ backgroundColor: "#080682" }}
></div> ></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]"> <span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
Võitlusmängu ala LVLup!
</span>
</div>
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#C02841" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
Red Bull
</span> </span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
className="w-4 h-4 border border-gray-300" className="w-4 h-4 border border-gray-300"
style={{backgroundColor: "#3498db"}} style={{ backgroundColor: "#ff6600" }}
></div> ></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]"> <span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
Red Bull Sim Racing
</span>
</div>
<div className="items-center gap-2 hidden">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#3498db" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
Sony Sony
</span> </span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
className="w-4 h-4 border border-gray-300" className="w-4 h-4 border border-gray-300"
style={{backgroundColor: "#2ecc71"}} style={{ backgroundColor: "#ff1493" }}
></div> ></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]"> <span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
Chillimisala Võitlusmängu ala
</span> </span>
</div> </div>
</div>
<div className="flex flex-col lg:flex-row gap-8 items-start">
<div className="flex-shrink-0 border-3 border-[#1F5673] w-full max-w-[800px] relative">
<div ref={mountRef} className="w-full"/>
<button
onClick={() => setShowDividers(!showDividers)}
className={`absolute top-2 right-2 px-3 py-2 bg-[#1F5673] text-white hover:bg-[#2A7A9B] ${vipnagorgialla.className} uppercase italic text-sm font-semibold flex items-center transition-colors shadow-lg z-10`}
>
{showDividers ? (
<EyeClosed className="w-6 h-6 mr-2"/>
) : (
<Eye className="w-6 h-6 mr-2"/>
)}
{showDividers ? "Peida" : "Näita"}
</button>
</div>
</div>
{/* Tooltip */}
{hoveredRoom && (
<div
className="fixed bg-black bg-opacity-80 text-white px-3 py-2 rounded-lg text-sm pointer-events-none z-50"
style={{
left: mousePosition.x + 10,
top: mousePosition.y - 10,
}}
>
{hoveredRoom}
</div>
)}
</div>
</div>
<SectionDivider />
</div> </div>
); <div className="flex flex-col lg:flex-row gap-8 items-start">
<div className="flex-shrink-0 border-3 border-[#1F5673] w-full max-w-[800px] relative">
<div ref={mountRef} className="w-full" />
<button
onClick={() => setShowDividers(!showDividers)}
className={`absolute top-2 right-2 px-3 py-2 bg-[#1F5673] text-white hover:bg-[#2A7A9B] ${vipnagorgialla.className} uppercase italic text-sm font-semibold flex items-center transition-colors shadow-lg z-10`}
>
{showDividers ? (
<EyeClosed className="w-6 h-6 mr-2" />
) : (
<Eye className="w-6 h-6 mr-2" />
)}
{showDividers ? "Peida" : "Näita"}
</button>
</div>
</div>
{/* Tooltip */}
{hoveredRoom && (
<div
className="fixed bg-black bg-opacity-80 text-white px-3 py-2 rounded-lg text-sm pointer-events-none z-50"
style={{
left: mousePosition.x + 10,
top: mousePosition.y - 10,
}}
>
{hoveredRoom}
</div>
)}
</div>
</div>
);
} }

@ -1,49 +1,103 @@
import {vipnagorgialla} from "@/components/Vipnagorgialla"; import { notFound } from "next/navigation";
import path from "node:path"; import ReactMarkdown, { Components } from "react-markdown";
import fs from "node:fs/promises"; import { vipnagorgialla } from "@/components/Vipnagorgialla";
import ReactMarkdown from "react-markdown";
import SectionDivider from "@/components/SectionDivider"; import SectionDivider from "@/components/SectionDivider";
type Props = { // Map of valid slugs to their corresponding file paths and titles
params: Promise<{ slug: string }>; const rulesMap = {
}; lol: {
filePath: "src/data/rules/lol.md",
export default async function RulePage({params}: Props) { title: "LOL Reeglid",
const {slug} = await params; },
cs2: {
const filePath = path.join(process.cwd(), "src/data/rules", `${slug}.md`); filePath: "src/data/rules/cs2.md",
let file: string; title: "CS2 Reeglid",
},
try { } as const;
file = await fs.readFile(filePath, "utf8");
} catch { type RuleSlug = keyof typeof rulesMap;
file = `# ${slug.toUpperCase()} REEGLID\n\nSisu hetkel puudub.`;
} interface PageProps {
params: Promise<{ slug: string }>;
const data = {title: undefined as string | undefined}; }
return ( async function getRuleContent(slug: string) {
<> if (!Object.keys(rulesMap).includes(slug)) {
<div className="mb-16"> return null;
<h1 }
className={`not-prose ${vipnagorgialla.className} font-bold italic uppercase text-[64px] leading-[96px] tracking-[-0.02em] text-[#2A2C3F] dark:text-[#EEE5E5] mx-auto mt-16 mb-6 px-8`}
> const ruleConfig = rulesMap[slug as RuleSlug];
{data.title || `${slug.toUpperCase()} REEGLID`}
</h1> try {
const file = Bun.file(ruleConfig.filePath);
<div const content = await file.text();
className={`mx-auto px-8 font-worksans return {
[&_ol]:ml-6 content,
[&_ol_ol]:ml-10 title: ruleConfig.title,
[&_ol_ol_ol]:ml-14 };
[&_h2]:font-bold } catch (error) {
`} console.error(`Error reading rule file for slug ${slug}:`, error);
> return null;
<ReactMarkdown>{file}</ReactMarkdown> }
</div> }
</div>
export default async function RulePage({ params }: PageProps) {
<SectionDivider/> const { slug } = await params;
</> const ruleData = await getRuleContent(slug);
);
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`}>
{ruleData.title}
</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,
}));
} }

@ -1,64 +1,52 @@
import {vipnagorgialla} from "@/components/Vipnagorgialla"; import { vipnagorgialla } from "@/components/Vipnagorgialla";
import Link from "next/link"; import Link from "next/link";
import SectionDivider from "@/components/SectionDivider"; import SectionDivider from "@/components/SectionDivider";
export default function RulesMenu() { export default function RulesMenu() {
const headingStyle = `text-5xl sm:text-6xl ${vipnagorgialla.className} font-bold italic text-[#2A2C3F] dark:text-[#EEE5E5]`; 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-3xl ${vipnagorgialla.className} font-bold uppercase text-[#EEE5E5] pb-2`;
// const SectionDivider = () => <div className="border-b-[3px] border-[#1F5673] w-full"/>; 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]`;
return ( const boxTextStyle = `text-3xl ${vipnagorgialla.className} font-bold uppercase text-[#EEE5E5] pb-2`;
<div>
<div className="flex flex-col md:m-16">
<h1 className={`${headingStyle} mt-8 md:mt-16`}>
REEGLID
</h1>
<div className='flex flex-wrap flex-row lg:mt-16 justify-center lg:items-start gap-12 flex-grow mb-8'> // const SectionDivider = () => <div className="border-b-[3px] border-[#1F5673] w-full"/>;
<Link href="/kodukord">
<div className={`${boxStyle} bg-[#007CAB] py-20`}>
<h2 className={`${boxTextStyle}`}>
Kodukord
</h2>
</div>
</Link>
<Link href="/reeglid/cs2"> return (
<div className={`${boxStyle} bg-[#1F5673] py-20`}> <div>
<h2 className={`${boxTextStyle}`}> <div className="flex flex-col md:m-16">
CS2 reeglid <h1 className={`${headingStyle} ml-3 mt-24 md:ml-0 md:mt-16 mb-4 px-4`}>
</h2> REEGLID
</div> </h1>
</Link>
<div className="flex flex-wrap flex-row lg:mt-16 justify-center lg:items-start gap-12 flex-grow mb-8">
<Link href="/kodukord">
<div className={`${boxStyle} bg-[#007CAB] py-20 px-8`}>
<h2 className={`${boxTextStyle}`}>Kodukord</h2>
</div>
</Link>
<Link href="reeglid/lol"> <Link href="/reeglid/cs2">
<div className={`${boxStyle} bg-[#007CAB] py-20`}> <div className={`${boxStyle} bg-[#1F5673] py-20 px-8`}>
<h2 className={`${boxTextStyle}`}> <h2 className={`${boxTextStyle}`}>CS2 reeglid</h2>
LoL reeglid </div>
</h2> </Link>
</div>
</Link>
{/* Minitourn. link coming soon*/} <Link href="reeglid/lol">
{/*<Link href="">*/} <div className={`${boxStyle} bg-[#007CAB] py-20 px-8`}>
<div <h2 className={`${boxTextStyle}`}>LoL reeglid</h2>
className={`${boxStyle} bg-[#1F5673] py-16`}>
<h2 className={`${boxTextStyle}`}>
Miniturniiride reeglid
</h2>
</div>
{/*</Link>*/}
</div>
</div> </div>
</Link>
<SectionDivider />
{/* Minitourn. link coming soon*/}
{/*<Link href="">*/}
<div className={`${boxStyle} bg-[#1F5673] py-16 px-8`}>
<h2 className={`${boxTextStyle}`}>Miniturniiride reeglid</h2>
</div>
{/*</Link>*/}
</div> </div>
); </div>
}
<SectionDivider />
</div>
);
}

@ -0,0 +1,210 @@
import { vipnagorgialla } from "@/components/Vipnagorgialla";
import Link from "next/link";
import Image from "next/image";
export default function Home() {
return (
<div>
<div className="grid grid-cols-1 md:grid-cols-2">
{/* Title */}
<div className="grid grid-cols-1 items-center justify-between mt-18 gap-12 pt-8">
<Image
src="/tipilan-white.svg"
width={850}
height={120}
alt="TipiLAN Logo"
className="px-8 py-8 md:px-12 md:py-14 dark:hidden w-[max(300px,min(100%,850px))] h-auto"
/>
<Image
src="/tipilan-dark.svg"
width={850}
height={120}
alt="TipiLAN Logo"
className="px-8 py-8 md:px-12 md:py-14 not-dark:hidden w-[max(300px,min(100%,850px))] h-auto2"
/>
<Link
href="/ajakava"
className="px-8 md:px-12 py-8 flex flex-col gap-4 border-b-3 border-t-3 group border-[#1F5673] hover:bg-[#007CAB] dark:hover:bg-[#00A3E0] transition"
>
<div className="cursor-pointer flex flex-row justify-between gap-4 items-center">
<h2
className={`text-[clamp(2rem,1.8rem+1vw,3rem)] ${vipnagorgialla.className} font-bold italic uppercase dark:text-[#EEE5E5] text-[#2A2C3F] group-hover:text-black dark:group-hover:text-[#2A2C3F]`}
>
Ajakava
</h2>
<span className="material-symbols-outlined !text-[clamp(2rem,1.5rem+1.5vw,3.5rem)] !font-bold text-[#007CAB] dark:text-[#00A3E0] group-hover:translate-x-2 dark:group-hover:text-[#EEE5E5] group-hover:text-[#EEE5E5] transition">
arrow_right_alt
</span>
</div>
<div className="flex flex-col gap-4">
<span className="material-symbols-outlined !text-[clamp(2rem,1.5rem+1.5vw,3.5rem)] text-[#007CAB] dark:text-[#00A3E0] dark:group-hover:text-[#EEE5E5] group-hover:text-[#EEE5E5]">
event_note
</span>
<p className="text-[clamp(0.875rem,0.75rem+0.5vw,1.25rem)] tracking-[-0.045rem] dark:group-hover:text-[#2A2C3F] group-hover:text-black">
TipiLAN on pungil põnevatest turniiridest, mini-võistlustest ja
paljust muust.
</p>
</div>
</Link>
</div>
{/* Stream iframe from Twitch */}
<div className="border-[#1F5673] -ml-0.75 border-l-0 md:border-l-3 border-b-3 h-full pt-0 md:pt-16">
<iframe
src="https://player.twitch.tv/?channel=tipilan_ee&parent=localhost&parent=tipilan.ee"
height="100%"
width="100%"
className="w-full h-full min-h-[400px]"
allow="autoplay; encrypted-media"
></iframe>
</div>
</div>
{/* Grid of buttons */}
<div className="grid grid-cols-1 xl:grid-cols-2 border-[#1F5673]">
<Link
href="/turniirid"
className="px-8 md:px-12 py-8 flex flex-col gap-4 border-b-3 lg:border-r-3 group border-[#1F5673] hover:bg-[#007CAB] dark:hover:bg-[#00A3E0] transition"
>
<div className="cursor-pointer flex flex-row justify-between gap-4 items-center">
<h2
className={`text-[clamp(2rem,1.8rem+1vw,3rem)] ${vipnagorgialla.className} font-bold italic uppercase dark:text-[#EEE5E5] text-[#2A2C3F] dark:group-hover:text-[#2A2C3F] group-hover:text-black`}
>
Turniirid
</h2>
<span className="material-symbols-outlined !text-[clamp(2rem,1.5rem+1.5vw,3.5rem)] !font-bold text-[#007CAB] dark:text-[#00A3E0] group-hover:translate-x-2 dark:group-hover:text-[#EEE5E5] group-hover:text-[#EEE5E5] transition">
arrow_right_alt
</span>
</div>
<div className="flex flex-col gap-4">
<span className="material-symbols-outlined !text-[clamp(2rem,1.5rem+1.5vw,3.5rem)] text-[#007CAB] dark:text-[#00A3E0] dark:group-hover:text-[#EEE5E5] group-hover:text-[#EEE5E5]">
trophy
</span>
<p className="text-[clamp(0.875rem,0.75rem+0.5vw,1.25rem)] tracking-[-0.045rem] dark:group-hover:text-[#2A2C3F] group-hover:text-black">
TipiLANil toimuvad suurejoonelised CS2 ja LoL turniirid, mille
auhinnafond on 10 000.
</p>
</div>
</Link>
<Link
href="/messiala"
className="px-8 md:px-12 py-8 flex flex-col gap-4 border-b-3 border-[#1F5673] group hover:bg-[#007CAB] dark:hover:bg-[#00A3E0] transition-all"
>
<div className="cursor-pointer flex flex-row justify-between gap-4 items-center">
<h2
className={`text-[clamp(2rem,1.8rem+1vw,3rem)] ${vipnagorgialla.className} font-bold italic uppercase dark:text-[#EEE5E5] text-[#2A2C3F] dark:group-hover:text-[#2A2C3F] group-hover:text-black`}
>
Messiala
</h2>
<span className="material-symbols-outlined !text-[clamp(2rem,1.5rem+1.5vw,3.5rem)] !font-bold text-[#007CAB] dark:text-[#00A3E0] group-hover:translate-x-2 dark:group-hover:text-[#EEE5E5] group-hover:text-[#EEE5E5] transition">
arrow_right_alt
</span>
</div>
<div className="flex flex-col gap-4">
<span className="material-symbols-outlined !text-[clamp(2rem,1.5rem+1.5vw,3.5rem)] text-[#007CAB] dark:text-[#00A3E0] dark:group-hover:text-[#EEE5E5] group-hover:text-[#EEE5E5]">
weekend
</span>
<p className="text-[clamp(0.875rem,0.75rem+0.5vw,1.25rem)] tracking-[-0.045rem] dark:group-hover:text-[#2A2C3F] group-hover:text-black">
TipiLANi messialal paiknevad ettevõtted, lisategevused ja toimuvad
loengud.
</p>
</div>
</Link>
</div>
{/* Date */}
<Link
href="/piletid"
className={`p-8 md:p-12 flex flex-col ${vipnagorgialla.className} font-bold italic border-b-3 border-[#1F5673] hover:bg-[#007CAB] dark:hover:bg-[#00A3E0] group transition`}
>
<div className="cursor-pointer text-left flex flex-row justify-between xl:justify-start gap-8">
<h3 className="text-4xl md:text-5xl dark:text-[#EEE5E5] dark:group-hover:text-[#2A2C3F] text-[#2A2C3F] group-hover:text-black">
Bro&shy;neeri oma koht juba täna!
</h3>
<span className="material-symbols-outlined !text-[clamp(2rem,1.5rem+1.5vw,3.5rem)] !font-bold text-[#007CAB] dark:text-[#00A3E0] hidden md:block group-hover:translate-x-2 group-hover:text-[#EEE5E5] dark:group-hover:text-[#EEE5E5] transition">
arrow_right_alt
</span>
</div>
<h2 className="text-[clamp(2.5rem,2.25rem+1.25vw,3.75rem)] text-[#007CAB] dark:text-[#00A3E0] dark:group-hover:text-[#EEE5E5] group-hover:text-[#EEE5E5]">
24.-26. okt.
</h2>
</Link>
{/* Sponsors */}
<div
className={`p-12 flex flex-col ${vipnagorgialla.className} font-bold italic border-b-3 border-[#1F5673]`}
>
<div className="text-left flex flex-col justify-between xl:justify-start">
<h3 className="text-4xl md:text-5xl dark:text-[#EEE5E5] text-[#2A2C3F] group-hover:text-black pb-8">
TipiLANi tõmbab käima...
</h3>
<div className="flex flex-row flex-wrap gap-8 md:gap-18 items-center">
<Link href="https://taltech.ee" target="_blank">
<Image
src="/sponsors/taltech-color.png"
alt="Taltech (Tallinna Tehnikaülikool)"
width={192}
height={192}
className="object-contain"
/>
</Link>
<Link href="https://www.redbull.com/ee-et/" target="_blank">
<Image
src="/sponsors/redbull.png"
alt="Redbull"
width={80}
height={80}
className="object-contain"
/>
</Link>
<Link href="https://www.alecoq.ee" target="_blank">
<Image
src="/sponsors/alecoq.svg"
alt="Alecoq"
width={200}
height={200}
className="object-contain"
/>
</Link>
<Link href="https://www.simracing.ee/" target="_blank">
<Image
src="/sponsors/EVAL.png"
alt="EVAL"
width={200}
height={200}
className="object-contain"
/>
</Link>
<Link href="https://balsnack.ee" target="_blank">
<Image
src="/sponsors/balsnack.svg"
alt="Balsnack"
width={200}
height={200}
className="object-contain"
/>
</Link>
<Link
href="https://www.rara.ee/sundmused/interaktiivne-videomangude-muuseum-lvlup/"
target="_blank"
>
<Image
src="/sponsors/lvlup_logo_export.svg"
alt="LVLup!"
width={192}
height={192}
className="object-contain"
/>
</Link>
<Link href="https://www.facebook.com/bfglOfficial" target="_blank">
<Image
src="/sponsors/BFGL.png"
alt="BFGL"
width={192}
height={192}
className="object-contain"
/>
</Link>
</div>
</div>
</div>
</div>
);
}

@ -4,182 +4,178 @@ import Image from "next/image";
import SectionDivider from "@/components/SectionDivider"; import SectionDivider from "@/components/SectionDivider";
export default function Tourney() { export default function Tourney() {
const headingStyle = `text-3xl md:text-5xl lg:text-5xl ${vipnagorgialla.className} font-bold uppercase text-[#2A2C3F] dark:text-[#EEE5E5] -skew-x-2 md:-skew-x-5`; const headingStyle = `text-3xl md:text-5xl lg:text-5xl ${vipnagorgialla.className} font-bold uppercase text-[#2A2C3F] dark:text-[#EEE5E5] -skew-x-2 md:-skew-x-5`;
// const SectionDivider = () => <hr className="border-t-[3px] border-[#1F5673]" />; // const SectionDivider = () => <hr className="border-t-[3px] border-[#1F5673]" />;
return ( return (
<div className="flex flex-col min-h-[90vh] mt-16"> <div className="flex flex-col min-h-[90vh] mt-16">
<h1 <h1
className={`text-4xl md:text-5xl lg:text-6xl ${vipnagorgialla.className} font-bold italic uppercase text-[#2A2C3F] dark:text-[#EEE5E5] mt-8 md:mt-16 mb-4 m-6 md:m-16`} className={`text-4xl md:text-5xl lg:text-6xl ${vipnagorgialla.className} font-bold italic uppercase text-[#2A2C3F] dark:text-[#EEE5E5] mt-8 md:mt-16 mb-4 m-6 md:m-16`}
> >
Turniirid Turniirid
</h1> </h1>
{/*<p className="text-2xl text-[#2A2C3F] dark:text-[#EEE5E5]">*/} {/*<p className="text-2xl text-[#2A2C3F] dark:text-[#EEE5E5]">*/}
{/* Kui tahate oma oskusi proovile panna, siis vaadake siia tagasi! Rohkem*/} {/* Kui tahate oma oskusi proovile panna, siis vaadake siia tagasi! Rohkem*/}
{/* infot lähiajal.*/} {/* infot lähiajal.*/}
{/*</p>*/} {/*</p>*/}
<div className="flex flex-col gap-8 md:gap-16">
{/* CS2 turniir */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-16 items-center mx-8 md:mx-16 lg:mx-32 xl:mx-48">
<div className="-skew-x-2 md:-skew-x-5">
<h2 className={`${headingStyle}`}>
CS2 turniir
</h2>
<p className={"text-2xl mb-4 text-neutral-500"}>
Toimumisaeg veel selgumisel
</p>
<p className="text-balance">
TipiLANil toimub Eesti ühe suurima auhinnafondiga CS2 turniire juba sel sügisel. Haara kaasa
sõbrad ja
saa osa adrenaliinirohkest kogemusest!
</p>
<br />
<p className="text-balance">
Auhinnafond on suuruses 5250, mis jaotatakse TOP3 meeskonna vahel ära. Iga tiimiliige saab
vastavalt
saavutatud kohale auhinnaks kas 600, 300 või 150.
</p>
<br />
<div className={"flex flex-row flex-wrap gap-8"}> <div className="flex flex-col gap-8 md:gap-16">
<Link href="/reeglid/cs2" target="_blank"> {/* CS2 turniir */}
<button <div className="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-16 items-center mx-8 md:mx-16 lg:mx-32 xl:mx-48">
className={`px-4 py-2 bg-[#1F5673] cursor-pointer ${vipnagorgialla.className} font-bold italic`} <div className="-skew-x-2 md:-skew-x-5">
> <h2 className={`${headingStyle}`}>CS2 turniir</h2>
LOE REEGLEID <p className={"text-2xl mb-4 text-neutral-500"}>
</button> Toimumisaeg veel selgumisel
</Link> </p>
<a href="https://fienta.com/et/tipilan" target="_blank"> <p className="text-balance">
<button TipiLANil toimub Eesti ühe suurima auhinnafondiga CS2 turniire
className={`px-4 py-2 bg-[#007CAB] cursor-pointer ${vipnagorgialla.className} font-bold italic`} juba sel sügisel. Haara kaasa sõbrad ja saa osa adrenaliinirohkest
> kogemusest!
OSTA PILET </p>
</button> <br />
</a> <p className="text-balance">
</div> Auhinnafond on suuruses 5250, mis jaotatakse TOP3 meeskonna vahel
</div> ära. Iga tiimiliige saab vastavalt saavutatud kohale auhinnaks kas
<div className="hidden md:block"> 600, 300 või 150.
<div> </p>
{/* Outside div needs to remain so that overflow won't occur*/} <br />
<Image
src="/images/cs2_tournament_logo.png"
alt="CS2 tournament"
width={600}
height={400}
/>
</div>
</div>
</div>
<SectionDivider /> <div className={"flex flex-row flex-wrap gap-8"}>
<Link href="/reeglid/cs2" target="_blank">
{/* LoL turniir */} <button
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-16 items-center mx-8 md:mx-16 lg:mx-32 xl:mx-48"> className={`px-4 py-2 bg-[#1F5673] cursor-pointer ${vipnagorgialla.className} font-bold italic text-[#ECE5E5]`}
<div className="hidden md:block"> >
<div> LOE REEGLEID
{/* Outside div needs to remain so that overflow won't occur*/} </button>
<Image </Link>
src="/images/lol_tournament_logo.png" <a href="https://fienta.com/et/tipilan" target="_blank">
alt="LoL tournament" <button
width={600} className={`px-4 py-2 bg-[#007CAB] cursor-pointer ${vipnagorgialla.className} font-bold italic text-[#ECE5E5]`}
height={400} >
/> OSTA PILET
</div> </button>
</div> </a>
<div className="flex-auto text-right -skew-x-2 md:-skew-x-5"> </div>
<h2 className={`${headingStyle}`}> </div>
LoL turniir <div className="hidden md:block">
</h2> <div>
<p className={"text-2xl mb-4 text-neutral-500"}> {/* Outside div needs to remain so that overflow won't occur*/}
Toimumisaeg veel selgumisel <Image
</p> src="/images/cs2_tournament_logo.png"
<p className="text-balance"> alt="CS2 tournament"
TipiLANil toimub Eesti ühe suurima auhinnafondiga LoL turniire juba sel sügisel. width={600}
Haara kaasa sõbrad ja saa osa adrenaliinirohkest kogemusest! height={400}
</p> />
<br /> </div>
<p className="text-balance"> </div>
Auhinnafond on suuruses 3500, mis jaotatakse TOP3 meeskonna vahel ära. Iga tiimiliige saab </div>
vastavalt saavutatud kohale auhinnaks kas 400, 200 või 100.
</p>
<br />
<div className="flex flex-row flex-wrap gap-4 md:gap-8 justify-end">
<Link href="/kodukord" target="_blank">
<button
className={`px-4 py-2 bg-[#1F5673] cursor-pointer ${vipnagorgialla.className} font-bold italic`}
>
LOE REEGLEID
</button>
</Link>
<a href="https://fienta.com/et/tipilan" target="_blank">
<button
className={`px-4 py-2 bg-[#007CAB] cursor-pointer ${vipnagorgialla.className} font-bold italic`}
>
OSTA PILET
</button>
</a>
</div>
<SectionDivider />
</div>
</div>
<SectionDivider /> {/* LoL turniir */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-16 items-center mx-8 md:mx-16 lg:mx-32 xl:mx-48">
<div className="hidden md:block">
<div>
{/* Outside div needs to remain so that overflow won't occur*/}
<Image
src="/images/lol_tournament_logo.png"
alt="LoL tournament"
width={600}
height={400}
/>
</div>
</div>
<div className="flex-auto text-right -skew-x-2 md:-skew-x-5">
<h2 className={`${headingStyle}`}>LoL turniir</h2>
<p className={"text-2xl mb-4 text-neutral-500"}>
Toimumisaeg veel selgumisel
</p>
<p className="text-balance">
TipiLANil toimub Eesti ühe suurima auhinnafondiga LoL turniire
juba sel sügisel. Haara kaasa sõbrad ja saa osa adrenaliinirohkest
kogemusest!
</p>
<br />
<p className="text-balance">
Auhinnafond on suuruses 3500, mis jaotatakse TOP3 meeskonna vahel
ära. Iga tiimiliige saab vastavalt saavutatud kohale auhinnaks kas
400, 200 või 100.
</p>
<br />
<div className="flex flex-row flex-wrap gap-4 md:gap-8 justify-end">
<Link href="/kodukord" target="_blank">
<button
className={`px-4 py-2 bg-[#1F5673] cursor-pointer ${vipnagorgialla.className} font-bold italic text-[#ECE5E5]`}
>
LOE REEGLEID
</button>
</Link>
<a href="https://fienta.com/et/tipilan" target="_blank">
<button
className={`px-4 py-2 bg-[#007CAB] cursor-pointer ${vipnagorgialla.className} font-bold italic text-[#ECE5E5]`}
>
OSTA PILET
</button>
</a>
</div>
</div>
</div>
{/* Mini-turniirid */} <SectionDivider />
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-16 items-center mx-8 md:mx-16 lg:mx-32 xl:mx-48">
<div className="-skew-x-2 md:-skew-x-5">
<h2 className={`${headingStyle}`}>
Mini&shy;turniirid
</h2>
<p className={"text-2xl mb-4 text-neutral-500"}>
Toimumisaeg veel selgumisel
</p>
<p className="text-balance">
TipiLANil toimub mitmeid erinevaid lõbusaid ja võistlushimu tekitavaid miniturniire.
Miniturniirid toimuvad järgnevates mängudes: SimRacing, Tekken, FIFA, Minecraft Bedwars,
Buckshot Roulette, LostGamer ja palju muud.
</p>
<br />
<p className="text-balance">
Auhinnafond on kõigi turniiride peale 1250 ja reeglina saab rahalise auhinna miniturniiri võitja.
</p>
<br />
<div className="flex flex-row flex-wrap gap-4 md:gap-8">
<Link href="/kodukord" target="_blank">
<button
className={`px-4 py-2 bg-[#1F5673] cursor-pointer ${vipnagorgialla.className} font-bold italic`}
>
LOE REEGLEID
</button>
</Link>
<a href="https://fienta.com/et/tipilan" target="_blank">
<button
className={`px-4 py-2 bg-[#007CAB] cursor-pointer ${vipnagorgialla.className} font-bold italic`}
>
OSTA PILET
</button>
</a>
</div>
</div>
<div className="hidden md:block">
<div>
{/* Outside div needs to remain so that overflow won't occur*/}
<Image
src="/images/minitournament_logo.png"
alt="mini tournaments"
width={600}
height={400}
/>
</div>
</div>
</div>
<SectionDivider /> {/* Mini-turniirid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-16 items-center mx-8 md:mx-16 lg:mx-32 xl:mx-48">
<div className="-skew-x-2 md:-skew-x-5">
<h2 className={`${headingStyle}`}>Mini&shy;turniirid</h2>
<p className={"text-2xl mb-4 text-neutral-500"}>
Toimumisaeg veel selgumisel
</p>
<p className="text-balance">
TipiLANil toimub mitmeid erinevaid lõbusaid ja võistlushimu
tekitavaid miniturniire. Miniturniirid toimuvad järgnevates
mängudes: SimRacing, Tekken, FIFA, Minecraft Bedwars, Buckshot
Roulette, LostGamer ja palju muud.
</p>
<br />
<p className="text-balance">
Auhinnafond on kõigi turniiride peale 1250 ja reeglina saab
rahalise auhinna miniturniiri võitja.
</p>
<br />
<div className="flex flex-row flex-wrap gap-4 md:gap-8">
<Link href="/kodukord" target="_blank">
<button
className={`px-4 py-2 bg-[#1F5673] cursor-pointer ${vipnagorgialla.className} font-bold italic text-[#ECE5E5]`}
>
LOE REEGLEID
</button>
</Link>
<a href="https://fienta.com/et/tipilan" target="_blank">
<button
className={`px-4 py-2 bg-[#007CAB] cursor-pointer ${vipnagorgialla.className} font-bold italic text-[#ECE5E5]`}
>
OSTA PILET
</button>
</a>
</div> </div>
</div>
<div className="hidden md:block">
<div>
{/* Outside div needs to remain so that overflow won't occur*/}
<Image
src="/images/minitournament_logo.png"
alt="mini tournaments"
width={600}
height={400}
/>
</div>
</div>
</div> </div>
);
} <SectionDivider />
</div>
</div>
);
}

@ -1,8 +1,8 @@
import * as React from "react" import * as React from "react";
import { Slot } from "@radix-ui/react-slot" import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority" import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
@ -32,8 +32,8 @@ const buttonVariants = cva(
variant: "default", variant: "default",
size: "default", size: "default",
}, },
} },
) );
function Button({ function Button({
className, className,
@ -43,17 +43,25 @@ function Button({
...props ...props
}: React.ComponentProps<"button"> & }: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & { VariantProps<typeof buttonVariants> & {
asChild?: boolean asChild?: boolean;
}) { }) {
const Comp = asChild ? Slot : "button" if (asChild) {
return (
<Slot
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...(props as React.ComponentProps<typeof Slot>)}
/>
);
}
return ( return (
<Comp <button
data-slot="button" data-slot="button"
className={cn(buttonVariants({ variant, size, className }))} className={cn(buttonVariants({ variant, size, className }))}
{...props} {...props}
/> />
) );
} }
export { Button, buttonVariants } export { Button, buttonVariants };

@ -0,0 +1,5 @@
## Tulekul
CS2 turniiri reeglid on hetkel ettevalmistamisel ja avaldatakse peagi.
Jälgige meie discordi!
Loading…
Cancel
Save