3d_example_google / index.html
eaglelandsonce's picture
Update index.html
89a67f1 verified
Raw History Blame Contribute Delete
23.9 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent Architecture 3D Interactive Simulation</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
-webkit-user-select: none;
}
body, html {
width: 100%;
height: 100%;
overflow: hidden;
background-color: #030712;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
color: #f8fafc;
}
#canvas3d {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
cursor: grab;
z-index: 1;
}
#canvas3d:active {
cursor: grabbing;
}
.hud-layer {
position: relative;
z-index: 10;
pointer-events: none;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 16px;
}
.glass {
background: rgba(15, 23, 42, 0.75);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.12);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
pointer-events: auto;
}
.pill {
border-radius: 9999px;
padding: 6px 14px;
display: flex;
align-items: center;
gap: 10px;
}
.btn {
background: rgba(30, 41, 59, 0.8);
border: 1px solid rgba(255, 255, 255, 0.1);
color: #cbd5e1;
padding: 6px 12px;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 6px;
}
.btn:hover {
background: rgba(51, 65, 85, 0.9);
color: #ffffff;
border-color: rgba(255, 255, 255, 0.25);
}
.btn-primary {
background: linear-gradient(135deg, #2563eb, #1d4ed8);
color: #ffffff;
border: none;
box-shadow: 0 4px 14px rgba(37, 99, 235, 0.4);
}
.btn-primary:hover {
background: linear-gradient(135deg, #3b82f6, #2563eb);
}
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.pulse-dot {
width: 8px;
height: 8px;
background: #38bdf8;
border-radius: 50%;
box-shadow: 0 0 10px #38bdf8;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.6); opacity: 0.5; }
}
.info-card {
border-radius: 16px;
padding: 16px;
max-width: 440px;
border-left: 4px solid #3b82f6;
transition: transform 0.3s ease, opacity 0.3s ease;
}
.info-card.hidden {
opacity: 0;
transform: translateY(20px);
pointer-events: none;
}
.bottom-bar {
display: flex;
flex-direction: column;
gap: 10px;
align-items: center;
max-width: 800px;
margin: 0 auto;
width: 100%;
}
.stage-track {
display: flex;
gap: 6px;
width: 100%;
justify-content: space-between;
}
.stage-btn {
flex: 1;
padding: 8px 4px;
text-align: center;
font-size: 11px;
font-weight: 600;
border-radius: 10px;
cursor: pointer;
background: rgba(15, 23, 42, 0.8);
border: 1px solid rgba(255, 255, 255, 0.08);
color: #94a3b8;
transition: all 0.2s ease;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.stage-btn:hover {
background: rgba(30, 41, 59, 0.9);
color: #f1f5f9;
}
.stage-btn.active {
background: #2563eb;
color: #ffffff;
border-color: #60a5fa;
box-shadow: 0 0 15px rgba(37, 99, 235, 0.5);
}
.camera-presets {
display: flex;
gap: 6px;
}
</style>
</head>
<body>
<!-- Fullscreen 3D Canvas -->
<canvas id="canvas3d"></canvas>
<!-- Interactive HUD Overlay -->
<div class="hud-layer">
<!-- Top Header & Controls -->
<header class="top-bar">
<div class="glass pill">
<span class="pulse-dot"></span>
<div style="font-size: 13px; font-weight: 700; letter-spacing: 0.5px;">
Agent Architecture <span style="color: #38bdf8; font-family: monospace;">3D</span>
</div>
<span style="font-size: 11px; color: #64748b;">| Drag to Rotate · Scroll to Zoom</span>
</div>
<div class="glass pill" style="gap: 6px;">
<button id="btn-prev" class="btn" title="Previous Stage">◀</button>
<button id="btn-play" class="btn btn-primary" style="padding: 6px 14px;">
<span id="play-text">⏸ Pause</span>
</button>
<button id="btn-next" class="btn" title="Next Stage">▶</button>
<div style="width: 1px; height: 16px; background: rgba(255,255,255,0.15); margin: 0 4px;"></div>
<button id="btn-orbit" class="btn" style="color: #38bdf8;">Spin: ON</button>
<button id="btn-toggle-info" class="btn">Info Box: ON</button>
</div>
<div class="glass pill camera-presets" style="padding: 4px 8px;">
<button onclick="setCamera('iso')" class="btn" style="font-size: 11px; padding: 4px 8px;">Isometric</button>
<button onclick="setCamera('top')" class="btn" style="font-size: 11px; padding: 4px 8px;">Top</button>
<button onclick="setCamera('front')" class="btn" style="font-size: 11px; padding: 4px 8px;">Front</button>
</div>
</header>
<!-- Floating Component Inspector (Toggleable) -->
<div id="inspector-card" class="glass info-card">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span id="card-tag" style="font-size: 10px; font-family: monospace; font-weight: 700; padding: 2px 8px; border-radius: 9999px; background: rgba(59, 130, 246, 0.2); color: #60a5fa;">
STAGE 1
</span>
<span id="card-sub" style="font-size: 11px; font-family: monospace; color: #94a3b8;">GEMINI.md · AGENTS.md</span>
</div>
<h2 id="card-title" style="font-size: 16px; font-weight: 700; margin-bottom: 6px; color: #ffffff;">1. CONTEXT FILES</h2>
<p id="card-desc" style="font-size: 12px; color: #cbd5e1; line-height: 1.5;">
The project's constitution loaded on every turn to ground the agent with workspace rules and project conventions.
</p>
<div id="card-detail" style="margin-top: 10px; padding: 8px 12px; background: rgba(2, 6, 23, 0.7); border-radius: 8px; border: 1px solid rgba(255,255,255,0.06); font-size: 11px; font-family: monospace; color: #38bdf8;">
⚡ Immutable root constraints & user preferences
</div>
</div>
<!-- Bottom Stage Timeline -->
<footer class="bottom-bar">
<div class="stage-track glass" style="border-radius: 14px; padding: 6px;">
<div onclick="jumpToStep(0)" class="stage-btn active" data-step="0">1. Context</div>
<div onclick="jumpToStep(1)" class="stage-btn" data-step="1">2. Skills</div>
<div onclick="jumpToStep(2)" class="stage-btn" data-step="2">3. Subagents</div>
<div onclick="jumpToStep(3)" class="stage-btn" data-step="3">4. MCP & Tools</div>
<div onclick="jumpToStep(4)" class="stage-btn" data-step="4">5. Hooks</div>
<div onclick="jumpToStep(5)" class="stage-btn" data-step="5">6. Output</div>
</div>
</footer>
</div>
<script>
const canvas = document.getElementById("canvas3d");
const ctx = canvas.getContext("2d");
let width = 0, height = 0, dpr = window.devicePixelRatio || 1;
function resize() {
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.scale(dpr, dpr);
}
window.addEventListener("resize", resize);
resize();
let yaw = 0.55, pitch = 0.38, zoom = 1.0;
let targetYaw = 0.55, targetPitch = 0.38, targetZoom = 1.0;
let isDragging = false, lastMouseX = 0, lastMouseY = 0;
let autoRotate = true, activeStep = 0, isPlaying = true, stepTimer = 0, showInfo = true;
const nodesData = [
{
id: "context",
title: "1. CONTEXT FILES",
sub: "GEMINI.md · AGENTS.md",
tag: "CONSTITUTION",
desc: "The project's constitution loaded on every turn to ground the agent with workspace rules and project conventions.",
detail: "Immutable root constraints & user preferences",
color: "#3b82f6",
glow: "rgba(59, 130, 246, 0.7)",
pos: { x: -190, y: -45, z: 110 },
radius: 30
},
{
id: "skills",
title: "2. SKILLS",
sub: "Reusable capability packages",
tag: "ON-DEMAND",
desc: "Conditional knowledge packages loaded dynamically only when task intent requires specific workflows or domain knowledge.",
detail: "Zero prompt bloat: loads only relevant guidelines",
color: "#14b8a6",
glow: "rgba(20, 184, 166, 0.7)",
pos: { x: 0, y: -150, z: 140 },
radius: 30
},
{
id: "subagents",
title: "3. SUBAGENTS",
sub: "Persona-driven specialists",
tag: "DELEGATION",
desc: "Narrow specialized agents (auditor, reviewer, doc-writer) spawned in isolated sandboxes to complete focused sub-tasks.",
detail: "Autonomous parallel execution & focused scratchpads",
color: "#f97316",
glow: "rgba(249, 115, 22, 0.7)",
pos: { x: 190, y: -45, z: 110 },
radius: 30
},
{
id: "mcp",
title: "4. MCP SERVERS & TOOLS",
sub: "Model Context Protocol + Native Tools",
tag: "EXECUTION",
desc: "USB-C of agent tooling. Standardized protocol connecting cloud APIs, databases, git, and local bash execution seamlessly.",
detail: "Universal integration layer (Postgres, GitHub, Slack, CLI)",
color: "#6366f1",
glow: "rgba(99, 102, 241, 0.7)",
pos: { x: 170, y: 120, z: -70 },
radius: 30
},
{
id: "hooks",
title: "5. HOOKS",
sub: "Deterministic guardrails",
tag: "SAFETY SHIELD",
desc: "Outside LLM cognition. Deterministic code that intercepts pre/post tool calls to block destructive commands and data leaks.",
detail: "Hard security perimeter you never delegate to an LLM",
color: "#f43f5e",
glow: "rgba(244, 63, 94, 0.8)",
pos: { x: 0, y: 170, z: -120 },
radius: 34
},
{
id: "delivery",
title: "6. SAFE EXECUTION",
sub: "Verified Output & Artifacts",
tag: "DELIVERY",
desc: "Safe, scoped response delivered back to user with full audit transparency, rich artifacts, and verified changes.",
detail: "Rendered interactive widgets, reports & code diffs",
color: "#eab308",
glow: "rgba(234, 179, 8, 0.7)",
pos: { x: -170, y: 120, z: -70 },
radius: 30
}
];
const coreNode = {
title: "ORCHESTRATOR",
color: "#38bdf8",
glow: "rgba(56, 189, 248, 0.8)",
pos: { x: 0, y: 0, z: 0 },
radius: 38
};
const particles = [];
const NUM_PARTICLES = 160;
for (let i = 0; i < NUM_PARTICLES; i++) {
const radius = 150 + Math.random() * 280;
const theta = Math.random() * Math.PI * 2;
const phi = (Math.random() - 0.5) * Math.PI;
particles.push({
x: radius * Math.cos(theta) * Math.cos(phi),
y: radius * Math.sin(phi),
z: radius * Math.sin(theta) * Math.cos(phi),
size: 1 + Math.random() * 2,
color: Math.random() > 0.5 ? "rgba(56, 189, 248, 0.4)" : "rgba(147, 197, 253, 0.25)"
});
}
const flowPath = [
{ from: 0, to: 1 },
{ from: 1, to: 2 },
{ from: 2, to: 3 },
{ from: 3, to: 4 },
{ from: 4, to: 5 },
{ from: 5, to: 0 }
];
const energyPackets = [];
for (let i = 0; i < flowPath.length; i++) {
for (let j = 0; j < 3; j++) {
energyPackets.push({
pathIndex: i,
progress: (j / 3) + Math.random() * 0.2,
speed: 0.008 + Math.random() * 0.004,
size: 4 + Math.random() * 2.5
});
}
}
function project3D(p, cx, cy) {
const cosY = Math.cos(yaw), sinY = Math.sin(yaw);
const cosP = Math.cos(pitch), sinP = Math.sin(pitch);
let x1 = p.x * cosY - p.z * sinY;
let z1 = p.x * sinY + p.z * cosY;
let y2 = p.y * cosP - z1 * sinP;
let z2 = p.y * sinP + z1 * cosP;
const cameraDist = 520 / zoom;
const fov = 440;
const depth = cameraDist + z2;
const scale = fov / Math.max(depth, 40);
return { x: cx + x1 * scale, y: cy + y2 * scale, scale, depth: z2 };
}
window.addEventListener("mousedown", (e) => {
if (e.target === canvas) {
isDragging = true;
lastMouseX = e.clientX;
lastMouseY = e.clientY;
}
});
window.addEventListener("mousemove", (e) => {
if (isDragging) {
const dx = e.clientX - lastMouseX;
const dy = e.clientY - lastMouseY;
targetYaw += dx * 0.006;
targetPitch = Math.max(-1.1, Math.min(1.1, targetPitch - dy * 0.006));
lastMouseX = e.clientX;
lastMouseY = e.clientY;
autoRotate = false;
document.getElementById("btn-orbit").textContent = "Spin: OFF";
}
});
window.addEventListener("mouseup", () => { isDragging = false; });
window.addEventListener("wheel", (e) => {
if (e.target === canvas) {
e.preventDefault();
targetZoom = Math.max(0.6, Math.min(2.2, targetZoom - e.deltaY * 0.001));
}
}, { passive: false });
function updateUI(stepIdx) {
activeStep = stepIdx;
const data = nodesData[stepIdx];
const inspector = document.getElementById("inspector-card");
if (inspector) {
inspector.style.borderLeftColor = data.color;
document.getElementById("card-tag").textContent = data.tag;
document.getElementById("card-tag").style.backgroundColor = `${data.color}25`;
document.getElementById("card-tag").style.color = data.color;
document.getElementById("card-title").textContent = data.title;
document.getElementById("card-sub").textContent = data.sub;
document.getElementById("card-desc").textContent = data.desc;
document.getElementById("card-detail").textContent = "⚡ " + data.detail;
}
document.querySelectorAll(".stage-btn").forEach((btn, idx) => {
if (idx === stepIdx) btn.classList.add("active");
else btn.classList.remove("active");
});
}
function jumpToStep(idx) {
updateUI(idx);
stepTimer = 0;
}
function nextStep() {
jumpToStep((activeStep + 1) % nodesData.length);
}
function prevStep() {
jumpToStep((activeStep - 1 + nodesData.length) % nodesData.length);
}
function setCamera(type) {
autoRotate = false;
document.getElementById("btn-orbit").textContent = "Spin: OFF";
if (type === 'iso') { targetYaw = 0.55; targetPitch = 0.38; targetZoom = 1.0; }
else if (type === 'top') { targetYaw = 0; targetPitch = 1.45; targetZoom = 0.95; }
else if (type === 'front') { targetYaw = 0; targetPitch = 0.05; targetZoom = 1.05; }
}
document.getElementById("btn-play").addEventListener("click", () => {
isPlaying = !isPlaying;
document.getElementById("play-text").textContent = isPlaying ? "⏸ Pause" : "▶ Play";
});
document.getElementById("btn-next").addEventListener("click", nextStep);
document.getElementById("btn-prev").addEventListener("click", prevStep);
document.getElementById("btn-orbit").addEventListener("click", () => {
autoRotate = !autoRotate;
document.getElementById("btn-orbit").textContent = autoRotate ? "Spin: ON" : "Spin: OFF";
});
document.getElementById("btn-toggle-info").addEventListener("click", () => {
showInfo = !showInfo;
const card = document.getElementById("inspector-card");
if (showInfo) {
card.classList.remove("hidden");
document.getElementById("btn-toggle-info").textContent = "Info Box: ON";
} else {
card.classList.add("hidden");
document.getElementById("btn-toggle-info").textContent = "Info Box: OFF";
}
});
let lastTime = performance.now();
function render(currentTime) {
const dt = (currentTime - lastTime) / 1000;
lastTime = currentTime;
yaw += (targetYaw - yaw) * 0.08;
pitch += (targetPitch - pitch) * 0.08;
zoom += (targetZoom - zoom) * 0.08;
if (autoRotate) targetYaw += 0.003;
if (isPlaying) {
stepTimer += dt;
if (stepTimer > 3.8) {
stepTimer = 0;
nextStep();
}
}
ctx.clearRect(0, 0, width, height);
const cx = width / 2, cy = height / 2;
ctx.save();
ctx.strokeStyle = "rgba(51, 65, 85, 0.22)";
ctx.lineWidth = 1;
for (let r = 110; r <= 290; r += 60) {
ctx.beginPath();
for (let angle = 0; angle <= Math.PI * 2; angle += 0.1) {
const pt = project3D({ x: Math.cos(angle) * r, y: 40, z: Math.sin(angle) * r }, cx, cy);
if (angle === 0) ctx.moveTo(pt.x, pt.y);
else ctx.lineTo(pt.x, pt.y);
}
ctx.closePath();
ctx.stroke();
}
ctx.restore();
for (let p of particles) {
const proj = project3D(p, cx, cy);
if (proj.scale > 0) {
ctx.beginPath();
ctx.arc(proj.x, proj.y, Math.max(0.5, p.size * proj.scale), 0, Math.PI * 2);
ctx.fillStyle = p.color;
ctx.fill();
}
}
const projCore = project3D(coreNode.pos, cx, cy);
nodesData.forEach((node, i) => {
const projNode = project3D(node.pos, cx, cy);
ctx.save();
ctx.beginPath();
ctx.moveTo(projCore.x, projCore.y);
ctx.lineTo(projNode.x, projNode.y);
ctx.strokeStyle = (i === activeStep) ? "rgba(56, 189, 248, 0.55)" : "rgba(71, 85, 105, 0.18)";
ctx.lineWidth = (i === activeStep) ? 2.2 * projNode.scale : 1 * projNode.scale;
if (i === activeStep) ctx.setLineDash([5, 5]);
ctx.stroke();
ctx.restore();
const nextNode = nodesData[(i + 1) % nodesData.length];
const projNext = project3D(nextNode.pos, cx, cy);
ctx.save();
ctx.beginPath();
ctx.moveTo(projNode.x, projNode.y);
ctx.lineTo(projNext.x, projNext.y);
ctx.strokeStyle = (i === activeStep) ? "rgba(59, 130, 246, 0.65)" : "rgba(51, 65, 85, 0.28)";
ctx.lineWidth = (i === activeStep) ? 2.8 * projNode.scale : 1.2 * projNode.scale;
ctx.stroke();
ctx.restore();
});
energyPackets.forEach(ep => {
ep.progress += ep.speed;
if (ep.progress > 1) ep.progress = 0;
const fromNode = nodesData[flowPath[ep.pathIndex].from];
const toNode = nodesData[flowPath[ep.pathIndex].to];
const interpPos = {
x: fromNode.pos.x + (toNode.pos.x - fromNode.pos.x) * ep.progress,
y: fromNode.pos.y + (toNode.pos.y - fromNode.pos.y) * ep.progress,
z: fromNode.pos.z + (toNode.pos.z - fromNode.pos.z) * ep.progress
};
const proj = project3D(interpPos, cx, cy);
if (proj.scale > 0) {
ctx.save();
ctx.beginPath();
const r = ep.size * proj.scale;
ctx.arc(proj.x, proj.y, Math.max(1, r), 0, Math.PI * 2);
ctx.fillStyle = (ep.pathIndex === activeStep) ? "#38bdf8" : "#93c5fd";
ctx.shadowColor = "#38bdf8";
ctx.shadowBlur = 8;
ctx.fill();
ctx.restore();
}
});
if (projCore.scale > 0) {
ctx.save();
const coreR = coreNode.radius * projCore.scale;
const glowGrad = ctx.createRadialGradient(projCore.x, projCore.y, coreR * 0.2, projCore.x, projCore.y, coreR * 2.2);
glowGrad.addColorStop(0, "rgba(56, 189, 248, 0.35)");
glowGrad.addColorStop(1, "rgba(56, 189, 248, 0)");
ctx.fillStyle = glowGrad;
ctx.beginPath();
ctx.arc(projCore.x, projCore.y, coreR * 2.2, 0, Math.PI * 2);
ctx.fill();
const pulse = 1 + Math.sin(currentTime * 0.004) * 0.1;
ctx.beginPath();
ctx.arc(projCore.x, projCore.y, coreR * pulse, 0, Math.PI * 2);
ctx.strokeStyle = "rgba(56, 189, 248, 0.6)";
ctx.lineWidth = 1.6 * projCore.scale;
ctx.stroke();
const sphereGrad = ctx.createRadialGradient(projCore.x - coreR * 0.3, projCore.y - coreR * 0.3, coreR * 0.1, projCore.x, projCore.y, coreR);
sphereGrad.addColorStop(0, "#e0f2fe");
sphereGrad.addColorStop(0.5, "#0284c7");
sphereGrad.addColorStop(1, "#0369a1");
ctx.fillStyle = sphereGrad;
ctx.beginPath();
ctx.arc(projCore.x, projCore.y, coreR, 0, Math.PI * 2);
ctx.fill();
ctx.font = `bold ${Math.max(8, 10 * projCore.scale)}px sans-serif`;
ctx.fillStyle = "#ffffff";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("ORCHESTRATOR", projCore.x, projCore.y);
ctx.restore();
}
const sortedNodes = nodesData.map((n, idx) => {
return { data: n, index: idx, proj: project3D(n.pos, cx, cy) };
}).sort((a, b) => a.proj.depth - b.proj.depth);
sortedNodes.forEach(item => {
const n = item.data;
const proj = item.proj;
const isActive = (item.index === activeStep);
if (proj.scale > 0) {
ctx.save();
const r = n.radius * proj.scale;
if (isActive) {
const activeGlow = ctx.createRadialGradient(proj.x, proj.y, r * 0.3, proj.x, proj.y, r * 2.4);
activeGlow.addColorStop(0, n.glow);
activeGlow.addColorStop(1, "rgba(0, 0, 0, 0)");
ctx.fillStyle = activeGlow;
ctx.beginPath();
ctx.arc(proj.x, proj.y, r * 2.4, 0, Math.PI * 2);
ctx.fill();
const pulseR = r * (1.25 + Math.sin(currentTime * 0.006) * 0.15);
ctx.beginPath();
ctx.arc(proj.x, proj.y, pulseR, 0, Math.PI * 2);
ctx.strokeStyle = n.color;
ctx.lineWidth = 2 * proj.scale;
ctx.stroke();
}
const nodeGrad = ctx.createRadialGradient(proj.x - r * 0.3, proj.y - r * 0.3, r * 0.1, proj.x, proj.y, r);
nodeGrad.addColorStop(0, "#ffffff");
nodeGrad.addColorStop(0.4, n.color);
nodeGrad.addColorStop(1, "#0f172a");
ctx.fillStyle = nodeGrad;
ctx.beginPath();
ctx.arc(proj.x, proj.y, r, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = isActive ? "#ffffff" : "rgba(255, 255, 255, 0.25)";
ctx.lineWidth = isActive ? 2 * proj.scale : 1 * proj.scale;
ctx.stroke();
ctx.font = `bold ${Math.max(9, 11 * proj.scale)}px sans-serif`;
ctx.fillStyle = isActive ? "#ffffff" : "#cbd5e1";
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
ctx.fillText(n.title.split(". ")[1] || n.title, proj.x, proj.y - r - 6);
ctx.font = `${Math.max(8, 9 * proj.scale)}px monospace`;
ctx.fillStyle = isActive ? n.color : "#94a3b8";
ctx.fillText(n.tag, proj.x, proj.y - r - 18);
ctx.restore();
}
});
requestAnimationFrame(render);
}
updateUI(0);
requestAnimationFrame(render);
</script>
</body>
</html>