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
This commit is contained in:
@@ -0,0 +1,518 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user