Files
horus 45af56d9cf feat(mission-control): restore MC tabs - temple, office, memory, claude, pdf-viewer, resume, resume-upload, temple-3d, demos
Also added:
- Memory API endpoints
- Briefs API endpoints
- AnveVoice stats API
- Claude spawn API
- TTS proxy
- Cleopatra voice widget
- api-auth middleware
2026-03-23 16:30:44 +01:00

519 lines
19 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useRef, useState } from "react";
export default function Temple3DPage() {
const containerRef = useRef<HTMLDivElement>(null);
const [selectedAgent, setSelectedAgent] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!containerRef.current) return;
let cancelled = false;
import("three").then((THREE) => {
if (cancelled || !containerRef.current) return;
const container = containerRef.current;
// Scene
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a1a);
scene.fog = new THREE.FogExp2(0x0a0a1a, 0.015);
// Camera
const camera = new THREE.PerspectiveCamera(60, container.clientWidth / container.clientHeight, 0.1, 1000);
camera.position.set(0, 15, 35);
// Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
container.appendChild(renderer.domElement);
// Lighting
const ambient = new THREE.AmbientLight(0xffd700, 0.3);
scene.add(ambient);
const sun = new THREE.DirectionalLight(0xfff5e0, 1.2);
sun.position.set(50, 100, 50);
sun.castShadow = true;
sun.shadow.mapSize.width = 2048;
sun.shadow.mapSize.height = 2048;
scene.add(sun);
const blueLight = new THREE.PointLight(0x40e0d0, 0.8, 100);
blueLight.position.set(-30, 20, -20);
scene.add(blueLight);
const purpleLight = new THREE.PointLight(0x9b59b6, 0.5, 80);
purpleLight.position.set(30, 15, 10);
scene.add(purpleLight);
// Materials
const sandstone = new THREE.MeshStandardMaterial({
color: 0xc2a366,
roughness: 0.9,
metalness: 0.1
});
const darkSandstone = new THREE.MeshStandardMaterial({
color: 0x8b7355,
roughness: 0.85,
metalness: 0.1
});
const gold = new THREE.MeshStandardMaterial({
color: 0xffd700,
metalness: 0.9,
roughness: 0.2,
emissive: 0xffd700,
emissiveIntensity: 0.3
});
const lapis = new THREE.MeshStandardMaterial({
color: 0x1e3a5f,
metalness: 0.3,
roughness: 0.6
});
// Ground - sand
const groundGeo = new THREE.PlaneGeometry(200, 200);
const groundMat = new THREE.MeshStandardMaterial({ color: 0xd4b896, roughness: 1 });
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -0.1;
ground.receiveShadow = true;
scene.add(ground);
// ============== KARNAK TEMPLE STRUCTURE ==============
// First Pylon (massive gateway) - like Karnak
const pylon1Geo = new THREE.BoxGeometry(40, 25, 5);
const pylon1 = new THREE.Mesh(pylon1Geo, sandstone);
pylon1.position.set(0, 12.5, 25);
pylon1.castShadow = true;
pylon1.receiveShadow = true;
scene.add(pylon1);
// Pylon decorations (pillars on pylon)
for (let i = 0; i < 6; i++) {
const pillarGeo = new THREE.BoxGeometry(2, 22, 2);
const pillar = new THREE.Mesh(pillarGeo, darkSandstone);
pillar.position.set(-18 + i * 7, 11, 28);
pillar.castShadow = true;
scene.add(pillar);
}
// Courtyard columns - Lotus columns like Karnak
const columnPositions = [
[-15, 0], [-10, 0], [-5, 0], [0, 0], [5, 0], [10, 0], [15, 0],
[-15, -10], [-10, -10], [-5, -10], [0, -10], [5, -10], [10, -10], [15, -10],
];
columnPositions.forEach(([x, z]) => {
// Column shaft (bundled papyrus style)
const shaftGeo = new THREE.CylinderGeometry(1.2, 1.3, 18, 16);
const shaft = new THREE.Mesh(shaftGeo, sandstone);
shaft.position.set(x, 9, z);
shaft.castShadow = true;
scene.add(shaft);
// Column capital (open lotus)
const capitalGeo = new THREE.ConeGeometry(2, 4, 16);
const capital = new THREE.Mesh(capitalGeo, sandstone);
capital.position.set(x, 20, z);
capital.castShadow = true;
scene.add(capital);
});
// Hypostyle Hall (second section with smaller columns)
const hypostyleCols = [
[-12, -18], [-6, -18], [0, -18], [6, -18], [12, -18],
[-12, -22], [-6, -22], [0, -22], [6, -22], [12, -22],
];
hypostyleCols.forEach(([x, z]) => {
const shaftGeo = new THREE.CylinderGeometry(0.8, 0.9, 14, 12);
const shaft = new THREE.Mesh(shaftGeo, darkSandstone);
shaft.position.set(x, 7, z);
shaft.castShadow = true;
scene.add(shaft);
});
// Second Pylon
const pylon2Geo = new THREE.BoxGeometry(30, 20, 4);
const pylon2 = new THREE.Mesh(pylon2Geo, sandstone);
pylon2.position.set(0, 10, -25);
pylon2.castShadow = true;
scene.add(pylon2);
// Third Pylon
const pylon3Geo = new THREE.BoxGeometry(25, 18, 3);
const pylon3 = new THREE.Mesh(pylon3Geo, darkSandstone);
pylon3.position.set(0, 9, -35);
pylon3.castShadow = true;
scene.add(pylon3);
// Sacred Lake (water feature)
const lakeGeo = new THREE.CircleGeometry(8, 32);
const lakeMat = new THREE.MeshStandardMaterial({
color: 0x1e5f8f,
metalness: 0.3,
roughness: 0.2,
transparent: true,
opacity: 0.8
});
const lake = new THREE.Mesh(lakeGeo, lakeMat);
lake.rotation.x = -Math.PI / 2;
lake.position.set(-25, 0.1, 5);
scene.add(lake);
// Obelisks
const obeliskGeo = new THREE.CylinderGeometry(0.5, 0.8, 20, 4);
const obelisk1 = new THREE.Mesh(obeliskGeo, darkSandstone);
obelisk1.position.set(-8, 10, -25);
obelisk1.castShadow = true;
scene.add(obelisk1);
const obelisk2 = new THREE.Mesh(obeliskGeo, darkSandstone);
obelisk2.position.set(8, 10, -25);
obelisk2.castShadow = true;
scene.add(obelisk2);
// Golden pyramid cap on obelisks
const capGeo = new THREE.ConeGeometry(1.2, 3, 4);
const cap1 = new THREE.Mesh(capGeo, gold);
cap1.position.set(-8, 21.5, -25);
scene.add(cap1);
const cap2 = new THREE.Mesh(capGeo, gold);
cap2.position.set(8, 21.5, -25);
scene.add(cap2);
// Sphinx Avenue (ram-headed sphinxes leading to entrance)
for (let i = 0; i < 6; i++) {
// Body
const sphinxGeo = new THREE.BoxGeometry(3, 2, 6);
const sphinx = new THREE.Mesh(sphinxGeo, sandstone);
sphinx.position.set(-12 + i * 5, 1, 40 + (i % 2) * 3);
sphinx.castShadow = true;
scene.add(sphinx);
// Head
const headGeo = new THREE.SphereGeometry(1.2, 16, 16);
const head = new THREE.Mesh(headGeo, sandstone);
head.position.set(-12 + i * 5, 2.5, 42 + (i % 2) * 3);
head.castShadow = true;
scene.add(head);
}
// Holy of Holies - Sanctuary (dark room at center)
const sanctuaryGeo = new THREE.BoxGeometry(15, 12, 10);
const sanctuary = new THREE.Mesh(sanctuaryGeo, darkSandstone);
sanctuary.position.set(0, 6, -42);
sanctuary.castShadow = true;
scene.add(sanctuary);
// Solar barque (boat) in sanctuary area
const boatGeo = new THREE.BoxGeometry(12, 0.5, 3);
const boat = new THREE.Mesh(boatGeo, lapis);
boat.position.set(0, 0.5, -42);
scene.add(boat);
// Golden Eye of Horus at sanctuary entrance
const eyeGeo = new THREE.TorusGeometry(3, 0.5, 8, 32);
const eye = new THREE.Mesh(eyeGeo, gold);
eye.position.set(0, 5, -36.5);
scene.add(eye);
// Eye center
const eyeCenterGeo = new THREE.SphereGeometry(1.5, 16, 16);
const eyeCenter = new THREE.Mesh(eyeCenterGeo, new THREE.MeshStandardMaterial({
color: 0x000000,
emissive: 0xffd700,
emissiveIntensity: 0.2
}));
eyeCenter.position.set(0, 5, -36);
scene.add(eyeCenter);
// ============== EGYPTIAN AGENTS (Animated) ==============
const agents = [
{ name: "ANUBIS", symbol: "🐕", color: 0x1a1a1a, glow: 0xffd700, pos: [0, 1.5, 42], room: "AVENUE OF SPHINXES", role: "Guardian of Leads" },
{ name: "PTAH", symbol: "🔨", color: 0xf5deb3, glow: 0xffd700, pos: [-12, 1.5, 0], room: "COURTYARD", role: "Builder" },
{ name: "SESHAT", symbol: "📝", color: 0x228b22, glow: 0x228b22, pos: [-6, 1.5, 0], room: "COURTYARD", role: "Scribe" },
{ name: "SEKHMET", symbol: "🦁", color: 0xb22222, glow: 0xb22222, pos: [6, 1.5, 0], room: "COURTYARD", role: "Warrior" },
{ name: "THOTH", symbol: "🐦", color: 0xffffff, glow: 0x40e0d0, pos: [-6, 1.5, -18], room: "HYPOSTYLE HALL", role: "Knowledge Keeper" },
{ name: "MAAT", symbol: "⚖️", color: 0x40e0d0, glow: 0x40e0d0, pos: [6, 1.5, -18], room: "HYPOSTYLE HALL", role: "Truth" },
{ name: "CLEOPATRA", symbol: "👑", color: 0x9b59b6, glow: 0x9b59b6, pos: [0, 1.5, -22], room: "SECOND PYLON", role: "Voice of the Queen" },
{ name: "HORUS", symbol: "👁️", color: 0xffd700, glow: 0xffd700, pos: [0, 4, -42], room: "HOLY OF HOLIES", role: "Lord of the Sky" },
];
const agentMeshes: any[] = [];
agents.forEach((agent, i) => {
// Create Egyptian-styled agent (hieroglyphic figure shape)
const group = new THREE.Group();
// Body (wrapped mummy style for some, human for others)
const bodyGeo = new THREE.CylinderGeometry(0.3, 0.5, 1.5, 8);
const bodyMat = new THREE.MeshStandardMaterial({
color: agent.color,
emissive: agent.color,
emissiveIntensity: 0.4,
metalness: 0.2,
roughness: 0.6
});
const body = new THREE.Mesh(bodyGeo, bodyMat);
body.position.y = 0.75;
group.add(body);
// Head (sphere for all, representing hieroglyphic style)
const headGeo = new THREE.SphereGeometry(0.5, 16, 16);
const head = new THREE.Mesh(headGeo, bodyMat);
head.position.y = 1.8;
group.add(head);
// Symbol on chest (floating element)
const symbolGeo = new THREE.CircleGeometry(0.3, 16);
const symbolMat = new THREE.MeshBasicMaterial({ color: agent.glow });
const symbol = new THREE.Mesh(symbolGeo, symbolMat);
symbol.position.set(0, 1, 0.35);
group.add(symbol);
// Glow aura
const glowGeo = new THREE.SphereGeometry(1.2, 16, 16);
const glowMat = new THREE.MeshBasicMaterial({
color: agent.glow,
transparent: true,
opacity: 0.15
});
const glow = new THREE.Mesh(glowGeo, glowMat);
glow.position.y = 1.2;
group.add(glow);
group.position.set(agent.pos[0], agent.pos[1], agent.pos[2]);
group.userData = { name: agent.name, symbol: agent.symbol, role: agent.role, room: agent.room };
group.castShadow = true;
scene.add(group);
agentMeshes.push(group);
});
// ============== ANIMATION ==============
let time = 0;
const clock = new THREE.Clock();
const animate = () => {
if (cancelled) return;
requestAnimationFrame(animate);
const delta = clock.getDelta();
time += delta;
// Animate agents - floating and bobbing motion
agentMeshes.forEach((agent, i) => {
const originalY = agents[i].pos[1];
const bobSpeed = 1 + i * 0.2;
const bobAmount = 0.3;
// Floating bob
agent.position.y = originalY + Math.sin(time * bobSpeed) * bobAmount;
// Gentle rotation
agent.rotation.y = Math.sin(time * 0.5 + i) * 0.2;
// Symbol pulsing glow
const child = agent.children[2]; // symbol
if (child && child.material) {
child.material.opacity = 0.3 + Math.sin(time * 2 + i) * 0.2;
}
});
// Animate Eye of Horus
eye.rotation.z = Math.sin(time * 0.3) * 0.1;
eyeCenter.material.emissiveIntensity = 0.2 + Math.sin(time * 2) * 0.1;
// Animate lake water
lake.material.opacity = 0.7 + Math.sin(time * 0.5) * 0.1;
renderer.render(scene, camera);
};
animate();
// ============== CONTROLS ==============
let isDragging = false;
let theta = 0;
let phi = Math.PI / 6;
let target = new THREE.Vector3(0, 5, -20);
let radius = 50;
const updateCamera = () => {
camera.position.x = target.x + radius * Math.sin(phi) * Math.cos(theta);
camera.position.y = target.y + radius * Math.cos(phi);
camera.position.z = target.z + radius * Math.sin(phi) * Math.sin(theta);
camera.lookAt(target);
};
const onPointerDown = (e: PointerEvent) => {
isDragging = true;
};
const onPointerUp = () => {
isDragging = false;
};
const onPointerMove = (e: PointerEvent) => {
if (!isDragging) return;
theta -= e.movementX * 0.005;
phi = Math.max(0.1, Math.min(Math.PI / 2, phi + e.movementY * 0.005));
updateCamera();
};
const onWheel = (e: WheelEvent) => {
radius = Math.max(20, Math.min(100, radius + e.deltaY * 0.05));
updateCamera();
};
container.addEventListener("pointerdown", onPointerDown);
container.addEventListener("pointerup", onPointerUp);
container.addEventListener("pointermove", onPointerMove);
container.addEventListener("wheel", onWheel);
// Click detection
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const onClick = (e: MouseEvent) => {
const rect = container.getBoundingClientRect();
mouse.x = ((e.clientX - rect.left) / container.clientWidth) * 2 - 1;
mouse.y = -((e.clientY - rect.top) / container.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(agentMeshes, true);
// Find parent group
for (const hit of intersects) {
let obj: any = hit.object;
while (obj.parent && !obj.userData.name) {
obj = obj.parent;
}
if (obj.userData.name) {
setSelectedAgent(obj.userData);
break;
}
}
};
container.addEventListener("click", onClick);
updateCamera();
setLoading(false);
// Handle resize
const handleResize = () => {
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
};
window.addEventListener("resize", handleResize);
// Cleanup
return () => {
cancelled = true;
window.removeEventListener("resize", handleResize);
container.removeEventListener("pointerdown", onPointerDown);
container.removeEventListener("pointerup", onPointerUp);
container.removeEventListener("pointermove", onPointerMove);
container.removeEventListener("wheel", onWheel);
container.removeEventListener("click", onClick);
if (container.contains(renderer.domElement)) {
container.removeChild(renderer.domElement);
}
renderer.dispose();
};
});
}, []);
return (
<div className="min-h-screen bg-gradient-to-b from-stone-950 to-stone-900">
{/* Header */}
<div className="text-center py-3 px-4 bg-black/30">
<h1 className="text-2xl md:text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-amber-200 via-yellow-400 to-amber-200">
🏛 TEMPLE OF AI - KARNAK
</h1>
<p className="text-amber-300/60 text-xs mt-1">
Drag to orbit Scroll to zoom Click agents for details
</p>
</div>
{/* Loading */}
{loading && (
<div className="absolute inset-0 flex items-center justify-center bg-stone-950 z-50">
<div className="text-center">
<div className="text-6xl mb-4 animate-pulse">🏛</div>
<p className="text-amber-300">Building Karnak Temple...</p>
</div>
</div>
)}
{/* 3D Canvas */}
<div
ref={containerRef}
className="w-full h-[calc(100vh-100px)]"
style={{ cursor: "grab", touchAction: "none" }}
/>
{/* Agent Info Panel */}
{selectedAgent && (
<div className="fixed bottom-4 left-4 right-4 md:left-auto md:right-4 md:w-80 bg-black/90 backdrop-blur-xl rounded-2xl p-5 border border-amber-500/40 shadow-2xl z-50">
<div className="flex items-start justify-between mb-3">
<div>
<span className="text-4xl">{selectedAgent.symbol}</span>
</div>
<button
onClick={() => setSelectedAgent(null)}
className="text-amber-400 hover:text-amber-300 text-2xl leading-none"
>
×
</button>
</div>
<h3 className="text-xl font-bold text-amber-200 mb-1">{selectedAgent.name}</h3>
<p className="text-amber-400/60 text-sm mb-3">{selectedAgent.role}</p>
<div className="bg-stone-800/50 rounded-lg p-3">
<p className="text-xs text-amber-300/60 mb-1">Location</p>
<p className="text-amber-200 font-medium">{selectedAgent.room}</p>
</div>
</div>
)}
{/* Bottom Legend */}
<div className="fixed bottom-4 left-4 bg-black/70 backdrop-blur rounded-xl p-3 border border-amber-500/20">
<h4 className="text-amber-200 font-bold text-sm mb-2">🏛 Temple Map</h4>
<div className="text-xs text-amber-100/70 space-y-1">
<div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-amber-500"></span>
Avenue of Sphinxes
</div>
<div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-amber-600"></span>
First Pylon
</div>
<div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-amber-700"></span>
Courtyard
</div>
<div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-amber-800"></span>
Hypostyle Hall
</div>
<div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-yellow-600"></span>
Sanctuary
</div>
</div>
</div>
</div>
);
}