diff --git a/next.config.ts b/next.config.ts index e9ffa30..582bf3e 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,24 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + async headers() { + return [ + { + source: "/(.*)", + headers: [ + { + key: "Content-Security-Policy", + value: + "frame-src 'self' https://player.twitch.tv https://embed.twitch.tv; frame-ancestors 'self';", + }, + { + key: "X-Frame-Options", + value: "SAMEORIGIN", + }, + ], + }, + ]; + }, }; export default nextConfig; diff --git a/src/app/messiala/page.tsx b/src/app/messiala/page.tsx index c74a105..383344f 100644 --- a/src/app/messiala/page.tsx +++ b/src/app/messiala/page.tsx @@ -1,485 +1,499 @@ "use client"; -import {vipnagorgialla} from "@/components/Vipnagorgialla"; +import { vipnagorgialla } from "@/components/Vipnagorgialla"; import * as THREE from "three"; -import {useEffect, useRef, useState} from "react"; -import {EyeClosed, Eye} from "lucide-react"; -import SectionDivider from "@/components/SectionDivider"; +import { useEffect, useRef, useState } from "react"; +import { EyeClosed, Eye } from "lucide-react"; // Define interface for the ref with toggle function interface MountRefCurrent extends HTMLDivElement { - toggleDividers?: (show: boolean) => void; + toggleDividers?: (show: boolean) => void; } export default function Expo() { - const mountRef = useRef(null); - const [hoveredRoom, setHoveredRoom] = useState(null); - const [mousePosition, setMousePosition] = useState({x: 0, y: 0}); - const [showDividers, setShowDividers] = useState(true); - - useEffect(() => { - if (!mountRef.current) return; - - // Copy ref to variable to avoid stale closure in cleanup - const mountElement = mountRef.current; - let dividersRef: THREE.Mesh[] = []; - - // Scene setup - const scene = new THREE.Scene(); - scene.background = new THREE.Color(0x0e0f19); - - // Get responsive dimensions - const getResponsiveDimensions = () => { - const container = mountRef.current; - if (!container) return {width: 800, height: 600}; - - const containerWidth = container.offsetWidth; - const maxWidth = Math.min(containerWidth, 800); - const width = Math.max(maxWidth, 300); // Minimum width - const height = (width * 600) / 800; // Maintain aspect ratio - - return {width, height}; - }; - - const {width, height} = getResponsiveDimensions(); - - // Isometric camera setup with responsive sizing - const aspect = width / height; - const baseFrustumSize = 14; - const frustumSize = width < 600 ? baseFrustumSize * 0.8 : baseFrustumSize; // Smaller frustum for mobile - const camera = new THREE.OrthographicCamera( - (frustumSize * aspect) / -2, - (frustumSize * aspect) / 2, - frustumSize / 2, - frustumSize / -2, - 1, - 1000, - ); + const mountRef = useRef(null); + const [hoveredRoom, setHoveredRoom] = useState(null); + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); + const [showDividers, setShowDividers] = useState(true); + + useEffect(() => { + if (!mountRef.current) return; + + // Copy ref to variable to avoid stale closure in cleanup + const mountElement = mountRef.current; + let dividersRef: THREE.Mesh[] = []; + + // Scene setup + const scene = new THREE.Scene(); + scene.background = new THREE.Color(0x0e0f19); + + // Get responsive dimensions + const getResponsiveDimensions = () => { + const container = mountRef.current; + if (!container) return { width: 800, height: 600 }; + + const containerWidth = container.offsetWidth; + const maxWidth = Math.min(containerWidth, 800); + const width = Math.max(maxWidth, 300); // Minimum width + const height = (width * 600) / 800; // Maintain aspect ratio + + return { width, height }; + }; + + const { width, height } = getResponsiveDimensions(); + + // Isometric camera setup with responsive sizing + const aspect = width / height; + const baseFrustumSize = 14; + const frustumSize = width < 600 ? baseFrustumSize * 0.8 : baseFrustumSize; // Smaller frustum for mobile + const camera = new THREE.OrthographicCamera( + (frustumSize * aspect) / -2, + (frustumSize * aspect) / 2, + frustumSize / 2, + frustumSize / -2, + 1, + 1000, + ); - // Position camera for isometric view - camera.position.set(10, 10, 14); - camera.lookAt(-1.4, 0, 0); - - // Renderer - const renderer = new THREE.WebGLRenderer({antialias: true}); - renderer.setSize(width, height); - renderer.shadowMap.enabled = true; - renderer.shadowMap.type = THREE.PCFSoftShadowMap; - mountElement.appendChild(renderer.domElement); - - // Raycaster for mouse interactions - const raycaster = new THREE.Raycaster(); - const mouse = new THREE.Vector2(); - - // Lighting - const ambientLight = new THREE.AmbientLight(0x404040, 1.2); - scene.add(ambientLight); - - const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5); - directionalLight.position.set(10, 10, 5); - directionalLight.castShadow = false; - directionalLight.shadow.mapSize.width = 2048; - directionalLight.shadow.mapSize.height = 2048; - scene.add(directionalLight); - - // Room colors and names - const roomColors = [ - 0xff6b35, // Orange - Mänguklubi - 0x4ecdc4, // Turquoise - Baariala - 0xffe66d, // Yellow - EVAL - 0xe74c3c, // Red - Redbull - 0x9b59b6, // Purple - Võitlusmängu ala - 0x3498db, // Blue - Sony - 0x2ecc71, // Green - Chillimisala - ]; - - const roomNames = [ - "Mänguklubi", - "Baariala", - "EVAL", - "Redbull", - "Võitlusmängu ala", - "Sony", - "Chillimisala", - ]; - - // Create individual rooms as rectangles with custom positions - const rooms: THREE.Mesh[] = []; - const roomData: Array<{ - mesh: THREE.Mesh; - name: string; - originalColor: number; - originalScale: THREE.Vector3; - }> = []; - const dividers: THREE.Mesh[] = []; - - // Define rooms with custom positions, sizes and colors - const roomDefinitions = [ - { - width: 7, - height: 0.7, - depth: 3, - x: 2.5, - z: 4, - color: roomColors[0], - name: roomNames[0], - }, // Mänguklubi - // { - // width: 2.5, - // height: 0.7, - // depth: 0.7, - // x: 1, - // z: 0, - // color: roomColors[1], - // name: roomNames[1], - // }, // Baariala - { - width: 1.8, - height: 0.7, - depth: 1.5, - x: 2.5, - z: -3.5, - color: roomColors[2], - name: roomNames[2], - }, // EVAL - { - width: 2.2, - height: 0.7, - depth: 4.5, - x: 5, - z: -2, - color: roomColors[3], - name: roomNames[3], - }, // Redbull - { - width: 3, - height: 0.7, - depth: 1.3, - x: 0, - z: -3.5, - color: roomColors[4], - name: roomNames[4], - }, // Võitlusmängu ala - { - width: 1.8, - height: 0.7, - depth: 1.5, - x: -2.55, - z: -3.5, - color: roomColors[5], - name: roomNames[5], - }, // Sony - { - width: 4, - height: 0.7, - depth: 4, - x: -5.5, - z: -2.3, - color: roomColors[6], - name: roomNames[6], - }, // Chillimisala - ]; - - roomDefinitions.forEach((roomDef) => { - const geometry = new THREE.BoxGeometry( - roomDef.width, - roomDef.height, - roomDef.depth, - ); - const material = new THREE.MeshLambertMaterial({ - color: roomDef.color, - }); - - const room = new THREE.Mesh(geometry, material); - room.position.set(roomDef.x, roomDef.height / 2, roomDef.z); - room.castShadow = true; - room.receiveShadow = true; - room.userData = {name: roomDef.name, originalColor: roomDef.color}; - - scene.add(room); - rooms.push(room); - roomData.push({ - mesh: room, - name: roomDef.name, - originalColor: roomDef.color, - originalScale: room.scale.clone(), - }); - }); - - // Create toggleable room dividers - const createTogglableDivider = ( - width: number, - height: number, - depth: number, - x: number, - z: number, - ) => { - const wallGeometry = new THREE.BoxGeometry(width, height, depth); - const wallMaterial = new THREE.MeshLambertMaterial({ - color: 0x555555, - transparent: true, - opacity: 0, - }); - - const wall = new THREE.Mesh(wallGeometry, wallMaterial); - wall.position.set(x, height / 2, z); - wall.visible = false; - scene.add(wall); - dividers.push(wall); - }; - - // Add strategic dividers between major areas - 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 - - // Store dividers reference for later access - dividersRef = [...dividers]; - - // Ground plane - const groundGeometry = new THREE.PlaneGeometry(14, 10.5); - const groundMaterial = new THREE.MeshLambertMaterial({color: 0xcccccc}); - const ground = new THREE.Mesh(groundGeometry, groundMaterial); - ground.rotation.x = -Math.PI / 2; - ground.position.x = -1.1; - ground.position.y = -0.5; - ground.receiveShadow = true; - scene.add(ground); - - // Second ground plane - const groundGeometry2 = new THREE.PlaneGeometry(2, 7); - const groundMaterial2 = new THREE.MeshLambertMaterial({ - color: 0xcccccc, - }); - const ground2 = new THREE.Mesh(groundGeometry2, groundMaterial2); - ground2.rotation.x = -Math.PI / 2; - ground2.position.x = -12.2; - ground2.position.y = -5; - ground2.receiveShadow = true; - scene.add(ground2); - - // Resize handler - const handleResize = () => { - const {width: newWidth, height: newHeight} = getResponsiveDimensions(); - - // Update camera - const newAspect = newWidth / newHeight; - const newFrustumSize = - newWidth < 600 ? baseFrustumSize * 0.8 : baseFrustumSize; - - camera.left = (newFrustumSize * newAspect) / -2; - camera.right = (newFrustumSize * newAspect) / 2; - camera.top = newFrustumSize / 2; - camera.bottom = newFrustumSize / -2; - camera.updateProjectionMatrix(); - - // Update renderer - renderer.setSize(newWidth, newHeight); - }; - - // Add resize event listener - window.addEventListener("resize", handleResize); - - // Mouse event handlers - const onMouseMove = (event: MouseEvent) => { - const rect = renderer.domElement.getBoundingClientRect(); - mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; - mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; - - // Update mouse position for tooltip - setMousePosition({x: event.clientX, y: event.clientY}); - - // Update raycaster - raycaster.setFromCamera(mouse, camera); - const intersects = raycaster.intersectObjects(rooms); - - // Reset all rooms to original state - roomData.forEach(({mesh, originalColor, originalScale}) => { - (mesh.material as THREE.MeshLambertMaterial).color.setHex( - originalColor, - ); - mesh.scale.copy(originalScale); - }); - - if (intersects.length > 0) { - const hoveredMesh = intersects[0].object as THREE.Mesh; - const roomInfo = roomData.find((r) => r.mesh === hoveredMesh); - - if (roomInfo) { - // Apply hover effects - (hoveredMesh.material as THREE.MeshLambertMaterial).color.setHex( - 0xffffff, - ); - hoveredMesh.scale.multiplyScalar(1.02); - setHoveredRoom(roomInfo.name); - } - } else { - setHoveredRoom(null); - } - }; - - // Add mouse event listener - renderer.domElement.addEventListener("mousemove", onMouseMove); - - // Animation loop - 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); + // Position camera for isometric view + camera.position.set(10, 10, 14); + camera.lookAt(-1.4, 0, 0); + + // Renderer + const renderer = new THREE.WebGLRenderer({ antialias: true }); + renderer.setSize(width, height); + renderer.shadowMap.enabled = true; + renderer.shadowMap.type = THREE.PCFSoftShadowMap; + mountElement.appendChild(renderer.domElement); + + // Raycaster for mouse interactions + const raycaster = new THREE.Raycaster(); + const mouse = new THREE.Vector2(); + + // Lighting + const ambientLight = new THREE.AmbientLight(0x404040, 1.2); + scene.add(ambientLight); + + const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5); + directionalLight.position.set(10, 10, 5); + directionalLight.castShadow = false; + directionalLight.shadow.mapSize.width = 2048; + directionalLight.shadow.mapSize.height = 2048; + scene.add(directionalLight); + + // Room colors and names + const roomColors = [ + 0x343434, // Gray - Lauamängude ala + 0x4ecdc4, // Turquoise - Baariala + 0xffe66d, // Yellow - EVAL + 0xff6600, // Orange - Redbull Sim Racing + 0xff1493, // Deep Pink - Võitlusmängu ala + 0x3498db, // Blue - Sony + 0x2ecc71, // Green - Lava + 0x080682, // Dark Blue - LVLup! + 0xc02841, // Red - RedBull + ]; + + // Create individual rooms as rectangles with custom positions + const rooms: THREE.Mesh[] = []; + const roomData: Array<{ + mesh: THREE.Mesh; + name: string; + originalColor: number; + originalScale: THREE.Vector3; + }> = []; + const dividers: THREE.Mesh[] = []; + + // Define rooms with custom positions, sizes and colors + const roomDefinitions = [ + { + width: 7, + height: 0.7, + depth: 3, + x: 2.5, + z: 4, + color: roomColors[0], + name: "Lauamängude ala", + }, + { + width: 3.5, + height: 0.7, + depth: 1.2, + x: 0.7, + z: -0.3, + color: roomColors[1], + name: "Baariala", + }, + { + width: 1.8, + height: 0.7, + depth: 1.5, + x: 1, + z: -3.5, + color: roomColors[2], + name: "EVAL", + }, + { + width: 2, + height: 0.7, + depth: 4.5, + x: 5.2, + z: -2, + color: roomColors[3], + name: "Red Bull Sim Racing", + }, + { + width: 3, + height: 0.7, + depth: 1.5, + x: -1.7, + z: -3.5, + color: roomColors[4], + name: "Võitlusmängu ala", + }, + // { + // width: 1.8, + // height: 0.7, + // depth: 1.5, + // x: -4.3, + // z: -3.5, + // color: roomColors[5], + // name: "Sony", + // }, + { + width: 3, + height: 0.7, + depth: 1.7, + x: -3.5, + z: -0.5, + color: roomColors[7], + name: "LVLup!", + }, + //{ + // width: 2, + // height: 0.7, + // depth: 4, + // x: -6.4, + // z: -2.3, + // color: roomColors[6], + // name: "Lava", + //}, + { + width: 1.8, + height: 0.7, + depth: 1.5, + x: 3, + z: -3.5, + color: roomColors[8], + name: "Red Bull", + }, + ]; + + roomDefinitions.forEach((roomDef) => { + const geometry = new THREE.BoxGeometry( + roomDef.width, + roomDef.height, + roomDef.depth, + ); + const material = new THREE.MeshLambertMaterial({ + color: roomDef.color, + }); + + const room = new THREE.Mesh(geometry, material); + room.position.set(roomDef.x, roomDef.height / 2, roomDef.z); + room.castShadow = true; + room.receiveShadow = true; + room.userData = { name: roomDef.name, originalColor: roomDef.color }; + + scene.add(room); + rooms.push(room); + roomData.push({ + mesh: room, + name: roomDef.name, + originalColor: roomDef.color, + originalScale: room.scale.clone(), + }); + }); + + // Create toggleable room dividers + const createTogglableDivider = ( + width: number, + height: number, + depth: number, + x: number, + z: number, + ) => { + const wallGeometry = new THREE.BoxGeometry(width, height, depth); + const wallMaterial = new THREE.MeshLambertMaterial({ + color: 0x555555, + transparent: true, + opacity: 0, + }); + + const wall = new THREE.Mesh(wallGeometry, wallMaterial); + wall.position.set(x, height / 2, z); + wall.visible = false; + scene.add(wall); + dividers.push(wall); + }; + + // Add strategic dividers between major areas + createTogglableDivider(10, 2, 2, -2.5, 1.5); // Wall between main entrance + createTogglableDivider(2, 2, 2, 5.5, 1.5); // Wall right next to Lauamängud & Redbull Sim Racing + + // Store dividers reference for later access + dividersRef = [...dividers]; + + // Ground plane + const groundGeometry = new THREE.PlaneGeometry(14, 10.5); + const groundMaterial = new THREE.MeshLambertMaterial({ color: 0xcccccc }); + const ground = new THREE.Mesh(groundGeometry, groundMaterial); + ground.rotation.x = -Math.PI / 2; + ground.position.x = -1.1; + ground.position.y = -0.5; + ground.receiveShadow = true; + scene.add(ground); + + // Second ground plane + const groundGeometry2 = new THREE.PlaneGeometry(2, 7); + const groundMaterial2 = new THREE.MeshLambertMaterial({ + color: 0xcccccc, + }); + const ground2 = new THREE.Mesh(groundGeometry2, groundMaterial2); + ground2.rotation.x = -Math.PI / 2; + ground2.position.x = -12.2; + ground2.position.y = -5; + ground2.receiveShadow = true; + scene.add(ground2); + + // Resize handler + const handleResize = () => { + const { width: newWidth, height: newHeight } = getResponsiveDimensions(); + + // Update camera + const newAspect = newWidth / newHeight; + const newFrustumSize = + newWidth < 600 ? baseFrustumSize * 0.8 : baseFrustumSize; + + camera.left = (newFrustumSize * newAspect) / -2; + camera.right = (newFrustumSize * newAspect) / 2; + camera.top = newFrustumSize / 2; + camera.bottom = newFrustumSize / -2; + camera.updateProjectionMatrix(); + + // Update renderer + renderer.setSize(newWidth, newHeight); + }; + + // Add resize event listener + window.addEventListener("resize", handleResize); + + // Mouse event handlers + const onMouseMove = (event: MouseEvent) => { + const rect = renderer.domElement.getBoundingClientRect(); + mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; + mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; + + // Update mouse position for tooltip + setMousePosition({ x: event.clientX, y: event.clientY }); + + // Update raycaster + raycaster.setFromCamera(mouse, camera); + const intersects = raycaster.intersectObjects(rooms); + + // Reset all rooms to original state + roomData.forEach(({ mesh, originalColor, originalScale }) => { + (mesh.material as THREE.MeshLambertMaterial).color.setHex( + originalColor, + ); + mesh.scale.copy(originalScale); + }); + + if (intersects.length > 0) { + const hoveredMesh = intersects[0].object as THREE.Mesh; + const roomInfo = roomData.find((r) => r.mesh === hoveredMesh); + + if (roomInfo) { + // Apply hover effects + (hoveredMesh.material as THREE.MeshLambertMaterial).color.setHex( + 0xffffff, + ); + hoveredMesh.scale.multiplyScalar(1.02); + setHoveredRoom(roomInfo.name); } - }, [showDividers]); - - return ( -
-
-

- Messiala -

-
-

- Tudengimaja -

-
-
-
- - Mänguklubi - -
-
-
- + } else { + setHoveredRoom(null); + } + }; + + // Add mouse event listener + renderer.domElement.addEventListener("mousemove", onMouseMove); + + // Animation loop + 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]); + + return ( +
+

+ Messiala +

+
+

+ Tudengimaja +

+
+
+
+ Baariala -
-
-
- +
+
+
+ EVAL -
-
-
- - Redbull +
+
+
+ + Lauamängude ala -
-
-
- - Võitlusmängu ala +
+
+
+ + LVLup! + +
+
+
+ + Red Bull -
-
-
- +
+
+
+ + Red Bull Sim Racing + +
+
+
+ Sony -
-
-
- - Chillimisala +
+
+
+ + Võitlusmängu ala -
-
-
-
-
- -
-
- - {/* Tooltip */} - {hoveredRoom && ( -
- {hoveredRoom} -
- )} -
-
- - +
- ); +
+
+
+ +
+
+ + {/* Tooltip */} + {hoveredRoom && ( +
+ {hoveredRoom} +
+ )} +
+
+ ); } diff --git a/src/app/striim/page.tsx b/src/app/striim/page.tsx new file mode 100644 index 0000000..a3384b7 --- /dev/null +++ b/src/app/striim/page.tsx @@ -0,0 +1,210 @@ +import { vipnagorgialla } from "@/components/Vipnagorgialla"; +import Link from "next/link"; +import Image from "next/image"; + +export default function Home() { + return ( +
+
+ {/* Title */} +
+ TipiLAN Logo + TipiLAN Logo + +
+

+ Ajakava +

+ + arrow_right_alt + +
+
+ + event_note + +

+ TipiLAN on pungil põnevatest turniiridest, mini-võistlustest ja + paljust muust. +

+
+ +
+ {/* Stream iframe from Twitch */} +
+ +
+
+ {/* Grid of buttons */} +
+ +
+

+ Turniirid +

+ + arrow_right_alt + +
+ +
+ + trophy + +

+ TipiLANil toimuvad suurejoonelised CS2 ja LoL turniirid, mille + auhinnafond on 10 000€. +

+
+ + +
+

+ Messiala +

+ + arrow_right_alt + +
+
+ + weekend + +

+ TipiLANi messialal paiknevad ettevõtted, lisategevused ja toimuvad + loengud. +

+
+ +
+ {/* Date */} + +
+

+ Bro­neeri oma koht juba täna! +

+ + arrow_right_alt + +
+

+ 24.-26. okt. +

+ + {/* Sponsors */} +
+
+

+ TipiLANi tõmbab käima... +

+
+ + Taltech (Tallinna Tehnikaülikool) + + + Redbull + + + Alecoq + + + EVAL + + + Balsnack + + + LVLup! + + + BFGL + +
+
+
+
+ ); +}