TipiLAN-i mirror
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1061 lines
37 KiB

"use client";
import { vipnagorgialla } from "@/components/Vipnagorgialla";
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { useEffect, useRef, useState, useMemo } from "react";
import { EyeClosed, Eye } from "lucide-react";
import SectionDivider from "@/components/SectionDivider";
import { useTranslations } from "next-intl";
import {
roomNameKeys,
staticRoomNames,
roomMeta,
RoomNameKey,
} from "@/data/roomNames";
import gamedevData from "@/data/gamedev.json";
// Define interface for the ref with toggle function
interface MountRefCurrent extends HTMLDivElement {
toggleDividers?: (show: boolean) => void;
switchView?: (view: "tudengimaja" | "fuajee") => void;
}
export default function Expo() {
const mountRef = useRef<MountRefCurrent | null>(null);
const [hoveredRoom, setHoveredRoom] = useState<string | null>(null);
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
const [showDividers, setShowDividers] = useState<boolean>(true);
const [currentView, setCurrentView] = useState<"tudengimaja" | "fuajee">(
"fuajee",
);
const currentViewRef = useRef<"tudengimaja" | "fuajee">("fuajee");
const t = useTranslations();
// Room names using translations and staticRoomNames
const roomNames = useMemo(() => {
const names: Record<RoomNameKey, string> = {} as never;
roomNameKeys.forEach((key) => {
if (staticRoomNames[key]) {
names[key] = staticRoomNames[key]!;
} else {
// fallback to translation key or just key
names[key] = t(`expo.areas.${key}`, { default: key });
}
});
return names;
}, [t]);
useEffect(() => {
if (!mountRef.current) return;
// Copy ref to variable to avoid stale closure in cleanup
const mountElement = mountRef.current;
let dividersRef: THREE.Mesh[] = [];
const fuajeeMeshes: THREE.Mesh[] = [];
let tudengimajaObjects: THREE.Object3D[] = [];
let fuajeeMesh: THREE.Group | null = null;
const fuajeeRooms: 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 = baseFrustumSize; // Keep consistent frustum size
const camera = new THREE.OrthographicCamera(
(frustumSize * aspect) / -2,
(frustumSize * aspect) / 2,
frustumSize / 2,
frustumSize / -2,
1,
1000,
);
// Camera positions for different views
const cameraPositions = {
tudengimaja: {
position: new THREE.Vector3(10, 10, 14),
lookAt: new THREE.Vector3(-1.4, 0, 0),
},
fuajee: {
position: new THREE.Vector3(30, 20, 15),
lookAt: new THREE.Vector3(0, 0, 0),
},
};
// Position camera for isometric view (default to fuajee)
camera.position.copy(cameraPositions.fuajee.position);
camera.lookAt(cameraPositions.fuajee.lookAt);
// 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);
// Create individual rooms as rectangles with custom positions using roomMeta
const rooms: THREE.Mesh[] = [];
const roomData: Array<{
mesh: THREE.Mesh;
name: string;
originalColor: number;
originalScale: THREE.Vector3;
view: "tudengimaja" | "fuajee";
}> = [];
const dividers: THREE.Mesh[] = [];
// Generate rooms for tudengimaja and fuajee using roomMeta
roomNameKeys.forEach((key) => {
const metas = roomMeta[key];
if (!metas) return;
metas.forEach((meta) => {
if (meta.view !== "tudengimaja") return;
const geometry = new THREE.BoxGeometry(
meta.size.width,
meta.size.height,
meta.size.depth,
);
const material = new THREE.MeshLambertMaterial({
color: meta.color,
});
const room = new THREE.Mesh(geometry, material);
room.position.set(meta.position.x, meta.position.y, meta.position.z);
room.castShadow = true;
room.receiveShadow = true;
room.userData = { name: roomNames[key], originalColor: meta.color };
scene.add(room);
rooms.push(room);
roomData.push({
mesh: room,
name: roomNames[key],
originalColor: meta.color,
originalScale: room.scale.clone(),
view: "tudengimaja",
});
});
});
// 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: 0x2e5570,
// 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(2, 2, 1, -6.5, 1); // Wall behind photowall
createTogglableDivider(4, 2, 2, -3.5, 1.5); // Wall between main entrance
createTogglableDivider(2, 2, 1, -0.5, 1.5); // Wall behind bar
createTogglableDivider(2, 2, 2, 1.5, 1.5); // Wall between main entrance
// 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 ground2 = new THREE.Mesh(groundGeometry2, groundMaterial);
ground2.rotation.x = -Math.PI / 2;
ground2.position.x = -12.2;
ground2.position.y = -5;
ground2.receiveShadow = true;
scene.add(ground2);
// Store tudengimaja objects (rooms, ground, dividers)
tudengimajaObjects = [...rooms, ground, ground2, ...dividers];
// Set initial visibility for fuajee default view
tudengimajaObjects.forEach((obj) => (obj.visible = false));
// Load fuajee GLTF model
const loader = new GLTFLoader();
loader.load(
"/spaces/fuajeeTalTech.glb",
(gltf) => {
fuajeeMesh = gltf.scene;
fuajeeMesh.position.set(-1.5, 1, 0);
fuajeeMesh.scale.set(0.3, 0.3, 0.3);
fuajeeMesh.visible = true; // Initially visible for fuajee default
// Traverse the model to collect meshes
fuajeeMesh.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.castShadow = true;
child.receiveShadow = true;
fuajeeMeshes.push(child);
}
});
scene.add(fuajeeMesh);
// Create example rooms for fuajee after the model loads
createfuajeeRooms();
// Set initial visibility for fuajee view
tudengimajaObjects.forEach((obj) => (obj.visible = false));
fuajeeMesh.visible = true;
fuajeeRooms.forEach((room) => (room.visible = true));
},
(progress) => {
console.log(
"Loading progress:",
(progress.loaded / progress.total) * 100 + "%",
);
},
(error) => {
console.error("Error loading GLTF:", error);
},
);
// Function to create rooms for fuajee using roomMeta
const createfuajeeRooms = () => {
roomNameKeys.forEach((key) => {
const metas = roomMeta[key];
if (!metas) return;
metas.forEach((meta) => {
if (meta.view !== "fuajee") return;
const geometry = new THREE.BoxGeometry(
meta.size.width,
meta.size.height,
meta.size.depth,
);
const material = new THREE.MeshLambertMaterial({
color: meta.color,
});
const room = new THREE.Mesh(geometry, material);
room.position.set(meta.position.x, meta.position.y, meta.position.z);
room.castShadow = true;
room.receiveShadow = true;
room.userData = { name: roomNames[key], originalColor: meta.color };
room.visible = true; // Initially visible for fuajee default
scene.add(room);
fuajeeRooms.push(room);
roomData.push({
mesh: room,
name: roomNames[key],
originalColor: meta.color,
originalScale: room.scale.clone(),
view: "fuajee",
});
});
});
};
// Resize handler
const handleResize = () => {
const { width: newWidth, height: newHeight } = getResponsiveDimensions();
// Update camera
const newAspect = newWidth / newHeight;
const newFrustumSize = 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 });
// Handle mouse interactions based on current view
if (currentViewRef.current === "tudengimaja") {
// Update raycaster
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(rooms);
// Reset all tudengimaja rooms to original state
roomData
.filter((r) => r.view === "tudengimaja")
.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);
}
} else if (currentViewRef.current === "fuajee") {
// Update raycaster for fuajee rooms
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(fuajeeRooms);
// Reset all fuajee rooms to original state
roomData
.filter((r) => r.view === "fuajee")
.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 with better visibility
(hoveredMesh.material as THREE.MeshLambertMaterial).color.setHex(
0xffffff,
);
hoveredMesh.scale.multiplyScalar(1.1);
setHoveredRoom(roomInfo.name);
}
} else {
setHoveredRoom(null);
}
} else {
setHoveredRoom(null);
}
};
// Add mouse event listener
renderer.domElement.addEventListener("mousemove", onMouseMove);
// Function to switch camera views
const switchView = (view: "tudengimaja" | "fuajee") => {
const targetPosition = cameraPositions[view].position;
const targetLookAt = cameraPositions[view].lookAt;
// Animate camera transition
const startPosition = camera.position.clone();
const startLookAt = new THREE.Vector3();
camera.getWorldDirection(startLookAt);
startLookAt.multiplyScalar(-1).add(camera.position);
let progress = 0;
const animateCamera = () => {
progress += 0.05;
if (progress >= 1) {
progress = 1;
}
// Smooth interpolation
const easeProgress = 1 - Math.cos(progress * Math.PI * 0.5);
camera.position.lerpVectors(
startPosition,
targetPosition,
easeProgress,
);
const currentLookAt = new THREE.Vector3().lerpVectors(
startLookAt,
targetLookAt,
easeProgress,
);
camera.lookAt(currentLookAt);
if (progress < 1) {
requestAnimationFrame(animateCamera);
}
};
animateCamera();
// Reset hover state when switching views
setHoveredRoom(null);
// Reset all room states to original
roomData.forEach(({ mesh, originalColor, originalScale }) => {
(mesh.material as THREE.MeshLambertMaterial).color.setHex(
originalColor,
);
mesh.scale.copy(originalScale);
});
// Toggle visibility of objects based on view
if (view === "fuajee") {
tudengimajaObjects.forEach((obj) => (obj.visible = false));
if (fuajeeMesh) {
fuajeeMesh.visible = true;
}
fuajeeRooms.forEach((room) => (room.visible = true));
} else {
tudengimajaObjects.forEach((obj) => (obj.visible = true));
if (fuajeeMesh) {
fuajeeMesh.visible = false;
}
fuajeeRooms.forEach((room) => (room.visible = false));
// Re-apply divider visibility state
if (mountElement.toggleDividers) {
mountElement.toggleDividers(showDividers);
}
}
};
// Animation loop
const animate = () => {
requestAnimationFrame(animate);
// Gentle floating animation for rooms
if (currentViewRef.current === "tudengimaja") {
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;
});
} else if (currentViewRef.current === "fuajee") {
fuajeeRooms.forEach((room, index) => {
const originalY = 2.25; // height / 2 for the room height of 0.5 + 2 offset
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 functions to parent scope
mountElement.toggleDividers = toggleDividers;
mountElement.switchView = switchView;
// Cleanup
return () => {
window.removeEventListener("resize", handleResize);
renderer.domElement.removeEventListener("mousemove", onMouseMove);
if (mountElement && renderer.domElement) {
mountElement.removeChild(renderer.domElement);
}
renderer.dispose();
};
}, [roomNames]);
// Update dividers when showDividers state changes
useEffect(() => {
if (mountRef.current?.toggleDividers) {
mountRef.current.toggleDividers(showDividers);
}
}, [showDividers]);
// Handle view switching
const handleViewSwitch = (view: "tudengimaja" | "fuajee") => {
setCurrentView(view);
currentViewRef.current = view; // Update ref immediately
setHoveredRoom(null); // Clear any existing hover state
if (mountRef.current?.switchView) {
mountRef.current.switchView(view);
}
};
return (
<div>
<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`}
>
{t("expo.title")}
</h1>
<div className="mb-6">
<h2 className="text-2xl text-[#2A2C3F] dark:text-[#EEE5E5] mb-3">
{currentView === "tudengimaja"
? t("schedule.locations.studentHouse")
: t("schedule.locations.entranceHall")}
</h2>
{currentView === "tudengimaja" && (
<div className="flex flex-wrap gap-4 pb-4">
{/* Bar */}
<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]">
{t("expo.areas.bar")}
</span>
</div>
{/* EVAL */}
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#4d86f7" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
EVAL
</span>
</div>
{/* LVLup */}
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#d34e35" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
LVLup!
</span>
</div>
{/* Red Bull */}
<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>
</div>
{/* Sim Racing */}
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#d8b43c" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
{t("expo.areas.simRacing")}
</span>
</div>
{/* Fighting */}
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#a8f494" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
{t("expo.areas.fighting")}
</span>
</div>
{/* K-space */}
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#2c5da3" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
K-space.ee
</span>
</div>
{/* Photowall */}
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#d12e7d" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
{t("expo.areas.photowall")}
</span>
</div>
{/* Buckshot Roulette */}
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#edb4b1" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
Buckshot Roulette
</span>
</div>
{/* Chill Area */}
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#05512e" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
{t("expo.areas.chillArea")}
</span>
</div>
{/* Alzgamer */}
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#d08331" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
Alzgamer
</span>
</div>
{/* WC */}
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#332b5d" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
WC
</span>
</div>
</div>
)}
{currentView === "fuajee" && (
<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: "#183bbf" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
{t("expo.areas.estoniagamedev")}
</span>
</div>
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#228b22" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
{t("expo.areas.gameup")}
</span>
</div>
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#ff6347" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
{t("expo.areas.info")}
</span>
</div>
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#d12e7d" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
{t("expo.areas.ittk")}
</span>
</div>
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#20b2aa" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
{t("expo.areas.studentformula")}
</span>
</div>
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#365591" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
{t("expo.areas.tartuyk")}
</span>
</div>
<div className="flex items-center gap-2">
<div
className="w-4 h-4 border border-gray-300"
style={{ backgroundColor: "#a82838" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
{t("expo.areas.tly")}
</span>
</div>
</div>
)}
<div className="flex flex-col lg:flex-row gap-8 items-start">
<div className="relative w-full max-w-[800px]">
<div className="flex-shrink-0 border-3 border-[#1F5673] w-full relative">
<div ref={mountRef} className="w-full" />
{/* Left Arrow - Only show when on fuajee to go back to tudengimaja */}
{currentView === "fuajee" && (
<button
onClick={() => handleViewSwitch("tudengimaja")}
className={`absolute bottom-2 left-1/2 transform -translate-x-1/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 cursor-pointer`}
title="Switch to Tudengimaja"
aria-label="Switch to Tudengimaja view"
>
<span className="material-symbols-outlined !text-2xl !font-bold text-white mr-2 transform rotate-180">
arrow_right_alt
</span>
{t("schedule.locations.studentHouse")}
</button>
)}
{/* Right Arrow - Only show when on tudengimaja to go to fuajee */}
{currentView === "tudengimaja" && (
<button
onClick={() => handleViewSwitch("fuajee")}
className={`absolute bottom-2 left-1/2 transform -translate-x-1/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 cursor-pointer`}
title="Switch to Fuajee"
aria-label="Switch to Fuajee view"
>
{t("schedule.locations.entranceHall")}
<span className="material-symbols-outlined !text-2xl !font-bold text-white ml-2">
arrow_right_alt
</span>
</button>
)}
{currentView === "tudengimaja" && (
<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 cursor-pointer`}
>
{showDividers ? (
<EyeClosed className="w-6 h-6 mr-2" />
) : (
<Eye className="w-6 h-6 mr-2" />
)}
{showDividers ? t("expo.hide") : t("expo.show")}
</button>
)}
</div>
</div>
</div>
{/* Tooltip - only show for current view */}
{hoveredRoom &&
((currentView === "tudengimaja" &&
[
roomNames.boardGames,
roomNames.bar,
roomNames.eval,
roomNames.simRacing,
roomNames.fighting,
roomNames.lvlup,
roomNames.redbull,
roomNames.kspace,
roomNames.photowall,
roomNames.buckshotroulette,
roomNames.wc,
roomNames.chillArea,
roomNames.alzgamer,
].includes(hoveredRoom)) ||
(currentView === "fuajee" &&
[
roomNames.tartuyk,
roomNames.estoniagamedev,
roomNames.info,
roomNames.tly,
roomNames.ittk,
roomNames.gameup,
roomNames.studentformula,
].includes(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 />
{/* MINITURNIIRID Section */}
<div className="flex flex-col lg:flex-row items-center gap-8 m-6 md:m-16 mb-16">
<div className="flex-1">
<h2
className={`text-4xl md:text-5xl lg:text-6xl ${vipnagorgialla.className} font-bold italic text-[#2A2C3F] dark:text-[#EEE5E5] mb-6 uppercase`}
>
MINITURNIIRID
</h2>
<p className="text-[#2A2C3F] dark:text-[#EEE5E5] text-lg mb-4">
TipiLANil toimub mitmeid erinevaid lõbusaid ja võistlushimu
tekitavaid miniturniire. Osaleda saavad ka niisama külastajad!
Auhinnafond on kõigi turniiride peale 1250.
</p>
</div>
<div className="flex-shrink-0 relative overflow-hidden">
<img
src="/images/minitournament_logo.png"
alt="Miniturniirid logo"
style={{
width: "649.7214965820312px",
height: "400.99997840027544px",
transform: "rotate(0deg)",
opacity: 1,
position: "relative",
}}
/>
</div>
</div>
<SectionDivider />
{/* PUHKA JA MÄNGI Section */}
<div className="flex flex-col m-6 md:m-16 mb-16">
<h2
className={`text-4xl md:text-5xl lg:text-6xl ${vipnagorgialla.className} font-bold italic text-[#2A2C3F] dark:text-[#EEE5E5] mb-8 uppercase`}
>
PUHKA JA MÄNGI
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{/* Card 1 - Chill-ala */}
<div className="flex flex-col">
<div className="bg-[#1a1a2e] rounded-lg overflow-hidden mb-4">
<div className="relative h-48">
<img
src="/images/EXPO/chill_ala.jpg"
alt="Chill-ala"
className="w-full h-full object-cover"
/>
</div>
</div>
<h3
className="text-[#2A2C3F] dark:text-[#EEE5E5]"
style={{
fontFamily: "Work Sans",
fontWeight: 700,
fontSize: "36px",
lineHeight: "100%",
letterSpacing: "-3%",
verticalAlign: "middle",
}}
>
Chill-ala koos turniiride otseülekandega
</h3>
</div>
{/* Card 2 - Mänguklubi */}
<div className="flex flex-col">
<div className="bg-[#1a1a2e] rounded-lg overflow-hidden mb-4">
<div className="relative h-48">
<img
src="/images/EXPO/mklubi.jpg"
alt="Mänguklubi"
className="w-full h-full object-cover"
/>
</div>
</div>
<h3
className="text-[#2A2C3F] dark:text-[#EEE5E5]"
style={{
fontFamily: "Work Sans",
fontWeight: 700,
fontSize: "36px",
lineHeight: "100%",
letterSpacing: "-3%",
verticalAlign: "middle",
}}
>
Mänguklubi lauamängud ja konsoolid
</h3>
</div>
{/* Card 3 - Baariala */}
<div className="flex flex-col">
<div className="bg-[#1a1a2e] rounded-lg overflow-hidden mb-4">
<div className="relative h-48">
<img
src="/images/EXPO/baar.jpg"
alt="Baariala"
className="w-full h-full object-cover"
/>
</div>
</div>
<h3
className="text-[#2A2C3F] dark:text-[#EEE5E5]"
style={{
fontFamily: "Work Sans",
fontWeight: 700,
fontSize: "36px",
lineHeight: "100%",
letterSpacing: "-3%",
verticalAlign: "middle",
}}
>
Baariala jookide ja snäkkidega
</h3>
</div>
</div>
</div>
<SectionDivider />
{/* EESTI MÄNGUARENDAJAD Section */}
<div className="flex flex-col m-6 md:m-16 mb-16">
<h2
className={`text-4xl md:text-5xl lg:text-6xl ${vipnagorgialla.className} font-bold italic text-[#2A2C3F] dark:text-[#EEE5E5] mb-8 uppercase`}
>
Eesti mänguarendajad
</h2>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6">
{gamedevData.games.map((game) => (
<div key={game.id} className="flex flex-col">
<div className="bg-[#1a1a2e] rounded-lg overflow-hidden aspect-square mb-2">
<img
src={game.logo}
alt={game.name}
className="w-full h-full object-contain p-4"
/>
</div>
<h3 className="text-[#2A2C3F] dark:text-[#EEE5E5] text-center font-semibold mb-1">
{game.name}
</h3>
<p className="text-[#666] dark:text-[#AAA] text-center text-sm">
{game.developer}
</p>
</div>
))}
</div>
</div>
<SectionDivider />
{/* ÜLIKOOLID Section */}
<div className="flex flex-col m-6 md:m-16 mb-16">
<h2
className={`text-4xl md:text-5xl lg:text-6xl ${vipnagorgialla.className} font-bold italic text-[#2A2C3F] dark:text-[#EEE5E5] mb-8 uppercase`}
>
Ülikoolid
</h2>
{/* First 12 games in 3x4 grid */}
<div className="grid grid-cols-3 md:grid-cols-4 gap-6 mb-6">
{gamedevData.universities?.slice(0, 12).map((university) => (
<div key={university.id} className="flex flex-col">
<div className="bg-[#1a1a2e] rounded-lg overflow-hidden aspect-square mb-2">
<img
src={university.logo}
alt={university.name}
className="w-full h-full object-contain p-4"
/>
</div>
<h3 className="text-[#2A2C3F] dark:text-[#EEE5E5] text-center font-semibold mb-1">
{university.name}
</h3>
<p className="text-[#666] dark:text-[#AAA] text-center text-sm">
{university.university}
</p>
</div>
))}
</div>
{/* Remaining games in new grid */}
{gamedevData.universities && gamedevData.universities.length > 12 && (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6">
{gamedevData.universities.slice(12).map((university) => (
<div key={university.id} className="flex flex-col">
<div className="bg-[#1a1a2e] rounded-lg overflow-hidden aspect-square mb-2">
<img
src={university.logo}
alt={university.name}
className="w-full h-full object-contain p-4"
/>
</div>
<h3 className="text-[#2A2C3F] dark:text-[#EEE5E5] text-center font-semibold mb-1">
{university.name}
</h3>
<p className="text-[#666] dark:text-[#AAA] text-center text-sm">
{university.university}
</p>
</div>
))}
</div>
)}
</div>
<SectionDivider />
</div>
);
}