Merge pull request #50 from Lapikud/development

Add entrance hall to expo area
pull/56/head
Voltages 4 months ago committed by GitHub
commit b2d5e1d202
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 32
      package.json
  2. BIN
      public/spaces/fuajeeTalTech.glb
  3. 480
      src/app/[locale]/messiala/page.tsx
  4. 7
      src/app/[locale]/reeglid/page.tsx
  5. 13
      src/components/ui/skeleton.tsx
  6. 4
      src/i18n/routing.ts
  7. 25
      translations/en.json
  8. 22
      translations/et.json

@ -15,20 +15,20 @@
"drizzle:studio": "drizzle-kit studio" "drizzle:studio": "drizzle-kit studio"
}, },
"dependencies": { "dependencies": {
"@libsql/client": "^0.15.9", "@libsql/client": "^0.15.12",
"@paralleldrive/cuid2": "^2.2.2", "@paralleldrive/cuid2": "^2.2.2",
"@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-select": "^2.2.5", "@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tooltip": "^1.2.7", "@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
"@types/three": "^0.178.1", "@types/three": "^0.178.1",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"drizzle-orm": "^0.44.2", "drizzle-orm": "^0.44.4",
"lucide-react": "^0.522.0", "lucide-react": "^0.522.0",
"material-symbols": "^0.31.8", "material-symbols": "^0.31.9",
"next": "15.3.0", "next": "15.3.0",
"next-intl": "^4.3.4", "next-intl": "^4.3.4",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
@ -38,23 +38,23 @@
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"three": "^0.178.0", "three": "^0.178.0",
"tw-animate-css": "^1.3.4" "tw-animate-css": "^1.3.7"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@tailwindcss/postcss": "^4.1.10", "@tailwindcss/postcss": "^4.1.12",
"@types/bun": "^1.2.18", "@types/bun": "^1.2.20",
"@types/node": "^20.19.1", "@types/node": "^20.19.11",
"@types/react": "^19.1.9", "@types/react": "^19.1.10",
"@types/react-dom": "^19.1.7", "@types/react-dom": "^19.1.7",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
"dotenv": "^16.3.1", "dotenv": "^16.6.1",
"drizzle-kit": "^0.31.4", "drizzle-kit": "^0.31.4",
"eslint": "^9.29.0", "eslint": "^9.33.0",
"eslint-config-next": "15.3.0", "eslint-config-next": "15.3.0",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"tailwindcss": "^4.1.10", "tailwindcss": "^4.1.12",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.8.3" "typescript": "^5.9.2"
} }
} }

Binary file not shown.

@ -2,6 +2,7 @@
import { vipnagorgialla } from "@/components/Vipnagorgialla"; import { vipnagorgialla } from "@/components/Vipnagorgialla";
import * as THREE from "three"; import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { useEffect, useRef, useState, useMemo } from "react"; import { useEffect, useRef, useState, useMemo } from "react";
import { EyeClosed, Eye } from "lucide-react"; import { EyeClosed, Eye } from "lucide-react";
import SectionDivider from "@/components/SectionDivider"; import SectionDivider from "@/components/SectionDivider";
@ -10,6 +11,7 @@ import { useTranslations } from "next-intl";
// 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;
switchView?: (view: "tudengimaja" | "fuajee") => void;
} }
export default function Expo() { export default function Expo() {
@ -17,6 +19,10 @@ export default function Expo() {
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);
const [currentView, setCurrentView] = useState<"tudengimaja" | "fuajee">(
"tudengimaja",
);
const currentViewRef = useRef<"tudengimaja" | "fuajee">("tudengimaja");
const t = useTranslations(); const t = useTranslations();
// Define room names with translations // Define room names with translations
@ -29,6 +35,15 @@ export default function Expo() {
fighting: t("expo.areas.fighting"), fighting: t("expo.areas.fighting"),
lvlup: "LVLup!", lvlup: "LVLup!",
redbull: "Red Bull", redbull: "Red Bull",
// fuajee rooms
ityk: t("expo.areas.ityk"),
estoniagamedev: t("expo.areas.estoniagamedev"),
info: t("expo.areas.info"),
tartuyk: t("expo.areas.tartuyk"),
tly: t("expo.areas.tly"),
gameup: "GameUP!",
ittk: t("expo.areas.ittk"),
photobooth: t("expo.areas.photobooth"),
}), }),
[t], [t],
); );
@ -39,6 +54,10 @@ export default function Expo() {
// 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[] = [];
const fuajeeMeshes: THREE.Mesh[] = [];
let tudengimajaObjects: THREE.Object3D[] = [];
let fuajeeMesh: THREE.Group | null = null;
const fuajeeRooms: THREE.Mesh[] = [];
// Scene setup // Scene setup
const scene = new THREE.Scene(); const scene = new THREE.Scene();
@ -62,7 +81,7 @@ export default function Expo() {
// 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 = baseFrustumSize; // Keep consistent frustum size
const camera = new THREE.OrthographicCamera( const camera = new THREE.OrthographicCamera(
(frustumSize * aspect) / -2, (frustumSize * aspect) / -2,
(frustumSize * aspect) / 2, (frustumSize * aspect) / 2,
@ -72,9 +91,21 @@ export default function Expo() {
1000, 1000,
); );
// Position camera for isometric view // Camera positions for different views
camera.position.set(10, 10, 14); const cameraPositions = {
camera.lookAt(-1.4, 0, 0); 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 tudengimaja)
camera.position.copy(cameraPositions.tudengimaja.position);
camera.lookAt(cameraPositions.tudengimaja.lookAt);
// Renderer // Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true }); const renderer = new THREE.WebGLRenderer({ antialias: true });
@ -118,6 +149,7 @@ export default function Expo() {
name: string; name: string;
originalColor: number; originalColor: number;
originalScale: THREE.Vector3; originalScale: THREE.Vector3;
view: "tudengimaja" | "fuajee";
}> = []; }> = [];
const dividers: THREE.Mesh[] = []; const dividers: THREE.Mesh[] = [];
@ -229,6 +261,7 @@ export default function Expo() {
name: roomDef.name, name: roomDef.name,
originalColor: roomDef.color, originalColor: roomDef.color,
originalScale: room.scale.clone(), originalScale: room.scale.clone(),
view: "tudengimaja",
}); });
}); });
@ -283,14 +316,168 @@ export default function Expo() {
ground2.receiveShadow = true; ground2.receiveShadow = true;
scene.add(ground2); scene.add(ground2);
// Store tudengimaja objects (rooms, ground, dividers)
tudengimajaObjects = [...rooms, ground, ground2, ...dividers];
// 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 = false; // Initially hidden
// 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();
},
(progress) => {
console.log(
"Loading progress:",
(progress.loaded / progress.total) * 100 + "%",
);
},
(error) => {
console.error("Error loading GLTF:", error);
},
);
// Function to create example rooms for fuajee
const createfuajeeRooms = () => {
const fuajeeRoomColors = [
0x7b1642, // ITÜK - Cherry Red
0x365591, // Light Blue - Tartu Ülikool
0xa82838, // Red - Tallinna Ülikool
0x183bbf, // Dark Blue - Eesti Gamedev
0xd12e7d, // Purple - Taltech
0x228b22, // Green - GameUP
0xff6347, // Orange - Info
0x20b2aa, // Light Sea Green - Photobooth
];
const fuajeeRoomDefinitions = [
{
width: 5,
height: 0.5,
depth: 3.5,
x: -6,
z: 2.8,
color: fuajeeRoomColors[0],
name: roomNames.ityk,
},
{
width: 5,
height: 0.5,
depth: 2,
x: 2.2,
z: -1.5,
color: fuajeeRoomColors[1],
name: roomNames.tartuyk,
},
{
width: 6,
height: 0.5,
depth: 2,
x: -5.8,
z: -1.2,
color: fuajeeRoomColors[3],
name: roomNames.estoniagamedev,
},
{
width: 2,
height: 0.5,
depth: 2,
x: -1.5,
z: -1.5,
color: fuajeeRoomColors[6],
name: roomNames.info,
},
{
width: 2,
height: 0.5,
depth: 1.5,
x: 6,
z: -1.7,
color: fuajeeRoomColors[2],
name: roomNames.tly,
},
{
width: 2,
height: 0.5,
depth: 1.5,
x: 11,
z: -1.7,
color: fuajeeRoomColors[4],
name: roomNames.ittk,
},
{
width: 2,
height: 0.5,
depth: 1.5,
x: 13.5,
z: -1.7,
color: fuajeeRoomColors[7],
name: roomNames.photobooth,
},
{
width: 2,
height: 0.5,
depth: 1.5,
x: 8.5,
z: -1.7,
color: fuajeeRoomColors[5],
name: roomNames.gameup,
},
];
fuajeeRoomDefinitions.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 + 2, roomDef.z);
room.castShadow = true;
room.receiveShadow = true;
room.userData = { name: roomDef.name, originalColor: roomDef.color };
room.visible = false; // Initially hidden
scene.add(room);
fuajeeRooms.push(room);
roomData.push({
mesh: room,
name: roomDef.name,
originalColor: roomDef.color,
originalScale: room.scale.clone(),
view: "fuajee",
});
});
};
// Resize handler // Resize handler
const handleResize = () => { const handleResize = () => {
const { width: newWidth, height: newHeight } = getResponsiveDimensions(); const { width: newWidth, height: newHeight } = getResponsiveDimensions();
// Update camera // Update camera
const newAspect = newWidth / newHeight; const newAspect = newWidth / newHeight;
const newFrustumSize = const newFrustumSize = baseFrustumSize;
newWidth < 600 ? baseFrustumSize * 0.8 : baseFrustumSize;
camera.left = (newFrustumSize * newAspect) / -2; camera.left = (newFrustumSize * newAspect) / -2;
camera.right = (newFrustumSize * newAspect) / 2; camera.right = (newFrustumSize * newAspect) / 2;
@ -314,12 +501,16 @@ export default function Expo() {
// Update mouse position for tooltip // Update mouse position for tooltip
setMousePosition({ x: event.clientX, y: event.clientY }); setMousePosition({ x: event.clientX, y: event.clientY });
// Handle mouse interactions based on current view
if (currentViewRef.current === "tudengimaja") {
// Update raycaster // Update raycaster
raycaster.setFromCamera(mouse, camera); raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(rooms); const intersects = raycaster.intersectObjects(rooms);
// Reset all rooms to original state // Reset all tudengimaja rooms to original state
roomData.forEach(({ mesh, originalColor, originalScale }) => { roomData
.filter((r) => r.view === "tudengimaja")
.forEach(({ mesh, originalColor, originalScale }) => {
(mesh.material as THREE.MeshLambertMaterial).color.setHex( (mesh.material as THREE.MeshLambertMaterial).color.setHex(
originalColor, originalColor,
); );
@ -341,16 +532,123 @@ export default function Expo() {
} else { } else {
setHoveredRoom(null); 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 // Add mouse event listener
renderer.domElement.addEventListener("mousemove", onMouseMove); 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 // Animation loop
const animate = () => { const animate = () => {
requestAnimationFrame(animate); requestAnimationFrame(animate);
// Gentle floating animation for rooms // Gentle floating animation for rooms
if (currentViewRef.current === "tudengimaja") {
rooms.forEach((room, index) => { rooms.forEach((room, index) => {
const originalY = 0.25; // height / 2 for the room height of 0.5 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; const baseY = originalY + Math.sin(Date.now() * 0.001 + index) * 0.05;
@ -358,6 +656,15 @@ export default function Expo() {
// Maintain current scale while updating Y position // Maintain current scale while updating Y position
room.position.y = baseY; 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); renderer.render(scene, camera);
}; };
@ -374,8 +681,9 @@ export default function Expo() {
}); });
}; };
// Expose toggle function to parent scope // Expose functions to parent scope
mountElement.toggleDividers = toggleDividers; mountElement.toggleDividers = toggleDividers;
mountElement.switchView = switchView;
// Cleanup // Cleanup
return () => { return () => {
@ -395,6 +703,16 @@ export default function Expo() {
} }
}, [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 ( return (
<div> <div>
<div className="flex flex-col min-h-[90vh] m-6 mt-16 md:m-16 "> <div className="flex flex-col min-h-[90vh] m-6 mt-16 md:m-16 ">
@ -405,8 +723,12 @@ export default function Expo() {
</h1> </h1>
<div className="mb-6"> <div className="mb-6">
<h2 className="text-2xl text-[#2A2C3F] dark:text-[#EEE5E5] mb-3"> <h2 className="text-2xl text-[#2A2C3F] dark:text-[#EEE5E5] mb-3">
{t("schedule.locations.studentHouse")} {currentView === "tudengimaja"
? t("schedule.locations.studentHouse")
: t("schedule.locations.entranceHall")}
</h2> </h2>
{currentView === "tudengimaja" && (
<div className="flex flex-wrap gap-4 pb-4"> <div className="flex flex-wrap gap-4 pb-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <div
@ -481,9 +803,118 @@ export default function Expo() {
</span> </span>
</div> </div>
</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: "#7b1642" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
{t("expo.areas.ityk")}
</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: "#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: "#a82838" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
{t("expo.areas.tly")}
</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: "#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: "#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: "#20b2aa" }}
></div>
<span className="text-sm text-[#2A2C3F] dark:text-[#EEE5E5]">
{t("expo.areas.photobooth")}
</span>
</div>
</div>
)}
<div className="flex flex-col lg:flex-row gap-8 items-start"> <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 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" /> <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="group absolute left-4 bottom-4 p-4 md:p-6 transition-all duration-300 hover:scale-110 z-20 touch-manipulation min-h-[48px] min-w-[48px] flex items-center justify-center"
title="Switch to Tudengimaja"
aria-label="Switch to Tudengimaja view"
>
<span className="material-symbols-outlined !text-[clamp(2.5rem,2rem+2vw,4rem)] !font-bold text-[#007CAB] dark:text-[#00A3E0] group-hover:-translate-x-2 dark:group-hover:text-[#EEE5E5] group-hover:text-[#EEE5E5] transition transform rotate-180">
arrow_right_alt
</span>
</button>
)}
{/* Right Arrow - Only show when on tudengimaja to go to fuajee */}
{currentView === "tudengimaja" && (
<button
onClick={() => handleViewSwitch("fuajee")}
className="group absolute right-4 bottom-4 p-4 md:p-6 transition-all duration-300 hover:scale-110 z-20 touch-manipulation min-h-[48px] min-w-[48px] flex items-center justify-center"
title="Switch to Fuajee"
aria-label="Switch to Fuajee view"
>
<span className="material-symbols-outlined !text-[clamp(2.5rem,2rem+2vw,4rem)] !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>
</button>
)}
{currentView === "tudengimaja" && (
<button <button
onClick={() => setShowDividers(!showDividers)} 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`} 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`}
@ -496,11 +927,34 @@ export default function Expo() {
{showDividers ? t("expo.hide") : t("expo.show")} {showDividers ? t("expo.hide") : t("expo.show")}
</button> </button>
)}
</div>
</div> </div>
</div> </div>
{/* Tooltip */} {/* Tooltip - only show for current view */}
{hoveredRoom && ( {hoveredRoom &&
((currentView === "tudengimaja" &&
[
roomNames.boardGames,
roomNames.bar,
roomNames.eval,
roomNames.simRacing,
roomNames.fighting,
roomNames.lvlup,
roomNames.redbull,
].includes(hoveredRoom)) ||
(currentView === "fuajee" &&
[
roomNames.ityk,
roomNames.tartuyk,
roomNames.estoniagamedev,
roomNames.info,
roomNames.tly,
roomNames.ittk,
roomNames.photobooth,
roomNames.gameup,
].includes(hoveredRoom))) && (
<div <div
className="fixed bg-black bg-opacity-80 text-white px-3 py-2 rounded-lg text-sm pointer-events-none z-50" className="fixed bg-black bg-opacity-80 text-white px-3 py-2 rounded-lg text-sm pointer-events-none z-50"
style={{ style={{

@ -9,16 +9,17 @@ export default async function RulesMenu({
params: Promise<{ locale: string }>; params: Promise<{ locale: string }>;
}) { }) {
const { locale } = await params; const { locale } = await params;
setRequestLocale(locale); setRequestLocale(locale);
const t = await getTranslations({ locale }); const t = await getTranslations({ locale });
const headingStyle = `text-4xl md:text-5xl lg:text-6xl ${vipnagorgialla.className} font-bold italic text-[#2A2C3F] dark:text-[#EEE5E5] uppercase`; const 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"/>;
return ( return (
<div> <div>
<div className="flex flex-col md:m-16"> <div className="flex flex-col md:m-16">
@ -49,7 +50,7 @@ export default async function RulesMenu({
{/*<Link href="">*/} {/*<Link href="">*/}
<div className={`${boxStyle} bg-[#1F5673] py-16 px-8`}> <div className={`${boxStyle} bg-[#1F5673] py-16 px-8`}>
<h2 className={`${boxTextStyle}`}> <h2 className={`${boxTextStyle}`}>
{t("tournaments.mini.titleSingular")} {t("rules.title")} {t("rules.miniRules")}
</h2> </h2>
</div> </div>
{/*</Link>*/} {/*</Link>*/}

@ -0,0 +1,13 @@
import { cn } from "@/lib/utils"
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="skeleton"
className={cn("bg-accent animate-pulse rounded-md", className)}
{...props}
/>
)
}
export { Skeleton }

@ -24,7 +24,7 @@ export const routing = defineRouting({
}, },
"/kodukord": { "/kodukord": {
et: "/kodukord", et: "/kodukord",
en: "/rules", en: "/houserules",
}, },
"/messiala": { "/messiala": {
et: "/messiala", et: "/messiala",
@ -36,7 +36,7 @@ export const routing = defineRouting({
}, },
"/reeglid": { "/reeglid": {
et: "/reeglid", et: "/reeglid",
en: "/regulations", en: "/gamerules",
}, },
"/striim": { "/striim": {
et: "/striim", et: "/striim",

@ -6,7 +6,7 @@
"houserules": "House rules", "houserules": "House rules",
"expo": "Expo", "expo": "Expo",
"tickets": "Tickets", "tickets": "Tickets",
"rules": "Rules", "rules": "Game rules",
"stream": "Stream", "stream": "Stream",
"tournaments": "Tournaments" "tournaments": "Tournaments"
}, },
@ -116,7 +116,6 @@
"buyTicket": "BUY TICKETS" "buyTicket": "BUY TICKETS"
}, },
"mini": { "mini": {
"titleSingular": "Mini-tournament",
"title": "Mini-tournaments", "title": "Mini-tournaments",
"timing": "Timing to be announced", "timing": "Timing to be announced",
"description1": "TipiLAN hosts various fun and competitive mini-tournaments. Mini-tournaments take place in the following games: SimRacing, Tekken, FIFA, Minecraft Bedwars, Buckshot Roulette, LostGamer and many more.", "description1": "TipiLAN hosts various fun and competitive mini-tournaments. Mini-tournaments take place in the following games: SimRacing, Tekken, FIFA, Minecraft Bedwars, Buckshot Roulette, LostGamer and many more.",
@ -147,7 +146,8 @@
"registrationSetup": "Registration and setup in auditorium", "registrationSetup": "Registration and setup in auditorium",
"auditorium": "Auditorium", "auditorium": "Auditorium",
"studentHouse": "Student House (Tudengimaja)", "studentHouse": "Student House (Tudengimaja)",
"auditoriumAndStudentHouse": "Auditorium and Student House" "auditoriumAndStudentHouse": "Auditorium and Student House",
"entranceHall": "Entrance Hall"
} }
}, },
"stream": { "stream": {
@ -176,16 +176,25 @@
"bar": "Bar Area", "bar": "Bar Area",
"boardGames": "Board Games Area", "boardGames": "Board Games Area",
"simRacing": "Red Bull Sim Racing", "simRacing": "Red Bull Sim Racing",
"fighting": "Fighting Games Area" "fighting": "Fighting Games Area",
}, "photobooth": "Photo booth",
"hide": "Hide", "ityk": "TalTech IT Faculty Student Council",
"show": "Show" "tartuyk": "Tartu University",
"estoniagamedev": "Estonia Gamedev",
"info": "Information booth",
"tly": "Tallinn University",
"ittk": "TalTech School of Information Technologies",
"gameup": "GameUP!"
},
"hide": "Hide walls",
"show": "Show walls"
}, },
"rules": { "rules": {
"title": "Rules", "title": "Rules",
"houseRules": "House Rules", "houseRules": "House Rules",
"cs2Rules": "CS2 Rules", "cs2Rules": "CS2 Rules",
"lolRules": "LoL Rules" "lolRules": "LoL Rules",
"miniRules": "Mini-tournament Rules"
}, },
"admin": { "admin": {
"title": "Admin", "title": "Admin",

@ -147,7 +147,8 @@
"registrationSetup": "Registreerimine ja setup aulas", "registrationSetup": "Registreerimine ja setup aulas",
"auditorium": "Aula", "auditorium": "Aula",
"studentHouse": "Tudengimaja", "studentHouse": "Tudengimaja",
"auditoriumAndStudentHouse": "Aula ja Tudengimaja" "auditoriumAndStudentHouse": "Aula ja Tudengimaja",
"entranceHall": "Fuajee"
} }
}, },
"stream": { "stream": {
@ -176,16 +177,25 @@
"bar": "Baariala", "bar": "Baariala",
"boardGames": "Lauamängude ala", "boardGames": "Lauamängude ala",
"simRacing": "Red Bull Sim Racing", "simRacing": "Red Bull Sim Racing",
"fighting": "Võitlusmängu ala" "fighting": "Võitlusmängu ala",
}, "photobooth": "Fotoboks",
"hide": "Peida", "ityk": "IT-teaduskonna üliõpilaskogu",
"show": "Näita" "tartuyk": "Tartu Ülikool",
"estoniagamedev": "Eesti Gamedev",
"info": "Infoboks",
"tly": "Tallinna Ülikool",
"ittk": "TalTech IT-Teaduskond",
"gameup": "GameUP!"
},
"hide": "Peida seinad",
"show": "Näita seinu"
}, },
"rules": { "rules": {
"title": "Reeglid", "title": "Reeglid",
"houseRules": "Kodukord", "houseRules": "Kodukord",
"cs2Rules": "CS2 Reeglid", "cs2Rules": "CS2 Reeglid",
"lolRules": "LoL Reeglid" "lolRules": "LoL Reeglid",
"miniRules": "Miniturniiride Reeglid"
}, },
"admin": { "admin": {
"title": "Haldus", "title": "Haldus",

Loading…
Cancel
Save