mirror of https://github.com/Lapikud/tipilan
Merge pull request #46 from Lapikud/development
React 19 update changed the way ref works. Removed redundant packages and updated markdown to use Bunpull/56/head
commit
5b464e9f6a
10 changed files with 1081 additions and 790 deletions
@ -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; |
||||||
|
|||||||
@ -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 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 boxTextStyle = `text-3xl ${vipnagorgialla.className} font-bold uppercase text-[#EEE5E5] pb-2`; |
||||||
|
|
||||||
// const SectionDivider = () => <div className="border-b-[3px] border-[#1F5673] w-full"/>;
|
// const SectionDivider = () => <div className="border-b-[3px] border-[#1F5673] w-full"/>;
|
||||||
|
|
||||||
return ( |
return ( |
||||||
<div> |
<div> |
||||||
<div className="flex flex-col md:m-16"> |
<div className="flex flex-col md:m-16"> |
||||||
<h1 className={`${headingStyle} mt-8 md:mt-16`}> |
<h1 className={`${headingStyle} ml-3 mt-24 md:ml-0 md:mt-16 mb-4 px-4`}> |
||||||
REEGLID |
REEGLID |
||||||
</h1> |
</h1> |
||||||
|
|
||||||
<div className='flex flex-wrap flex-row lg:mt-16 justify-center lg:items-start gap-12 flex-grow mb-8'> |
<div className="flex flex-wrap flex-row lg:mt-16 justify-center lg:items-start gap-12 flex-grow mb-8"> |
||||||
<Link href="/kodukord"> |
<Link href="/kodukord"> |
||||||
<div className={`${boxStyle} bg-[#007CAB] py-20`}> |
<div className={`${boxStyle} bg-[#007CAB] py-20 px-8`}> |
||||||
<h2 className={`${boxTextStyle}`}> |
<h2 className={`${boxTextStyle}`}>Kodukord</h2> |
||||||
Kodukord |
|
||||||
</h2> |
|
||||||
</div> |
|
||||||
</Link> |
|
||||||
|
|
||||||
<Link href="/reeglid/cs2"> |
|
||||||
<div className={`${boxStyle} bg-[#1F5673] py-20`}> |
|
||||||
<h2 className={`${boxTextStyle}`}> |
|
||||||
CS2 reeglid |
|
||||||
</h2> |
|
||||||
</div> |
|
||||||
</Link> |
|
||||||
|
|
||||||
|
|
||||||
<Link href="reeglid/lol"> |
|
||||||
<div className={`${boxStyle} bg-[#007CAB] py-20`}> |
|
||||||
<h2 className={`${boxTextStyle}`}> |
|
||||||
LoL reeglid |
|
||||||
</h2> |
|
||||||
</div> |
|
||||||
</Link> |
|
||||||
|
|
||||||
{/* Minitourn. link coming soon*/} |
|
||||||
{/*<Link href="">*/}
|
|
||||||
<div |
|
||||||
className={`${boxStyle} bg-[#1F5673] py-16`}> |
|
||||||
<h2 className={`${boxTextStyle}`}> |
|
||||||
Miniturniiride reeglid |
|
||||||
</h2> |
|
||||||
</div> |
|
||||||
{/*</Link>*/} |
|
||||||
|
|
||||||
</div> |
|
||||||
</div> |
</div> |
||||||
|
</Link> |
||||||
|
|
||||||
<SectionDivider /> |
<Link href="/reeglid/cs2"> |
||||||
|
<div className={`${boxStyle} bg-[#1F5673] py-20 px-8`}> |
||||||
|
<h2 className={`${boxTextStyle}`}>CS2 reeglid</h2> |
||||||
|
</div> |
||||||
|
</Link> |
||||||
|
|
||||||
|
<Link href="reeglid/lol"> |
||||||
|
<div className={`${boxStyle} bg-[#007CAB] py-20 px-8`}> |
||||||
|
<h2 className={`${boxTextStyle}`}>LoL reeglid</h2> |
||||||
|
</div> |
||||||
|
</Link> |
||||||
|
|
||||||
|
{/* 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­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> |
||||||
|
); |
||||||
|
} |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
## Tulekul |
||||||
|
|
||||||
|
CS2 turniiri reeglid on hetkel ettevalmistamisel ja avaldatakse peagi. |
||||||
|
|
||||||
|
Jälgige meie discordi! |
||||||
Loading…
Reference in new issue