Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions demo/zoo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { initAgent } from "../src/index.ts";
import * as agents from "../src/agents/index.ts";

const stage = document.getElementById("zoo-stage") as HTMLDivElement;
const status = document.getElementById("zoo-status") as HTMLSpanElement;
const count = document.getElementById("zoo-count") as HTMLSpanElement;
const animateAllBtn = document.getElementById("animate-all") as HTMLButtonElement;
const speakAllBtn = document.getElementById("speak-all") as HTMLButtonElement;
const resetAllBtn = document.getElementById("reset-all") as HTMLButtonElement;
const sharedAnimationSelect = document.getElementById("zoo-animation") as HTMLSelectElement;
const playSharedBtn = document.getElementById("play-shared") as HTMLButtonElement;

const entries = Object.entries(agents);
const zooAgents: { name: string; agent: any; x: number; y: number; animations: string[] }[] = [];

function layoutFor(index: number) {
const columns = 4;
const cellWidth = 230;
const cellHeight = 170;
const x = 36 + (index % columns) * cellWidth;
const y = 40 + Math.floor(index / columns) * cellHeight;
return { x, y };
}

function buildSharedAnimationList() {
const animationCounts = new Map<string, number>();

for (const entry of zooAgents) {
for (const animation of entry.animations) {
animationCounts.set(animation, (animationCounts.get(animation) || 0) + 1);
}
}

const sharedAnimations = [...animationCounts.entries()]
.filter(([, count]) => count > 1)
.sort((a, b) => a[0].localeCompare(b[0]));

sharedAnimationSelect.innerHTML = "";

if (sharedAnimations.length === 0) {
const option = document.createElement("option");
option.textContent = "No shared animations";
sharedAnimationSelect.appendChild(option);
sharedAnimationSelect.disabled = true;
playSharedBtn.disabled = true;
return;
}

for (const [animation, supportedBy] of sharedAnimations) {
const option = document.createElement("option");
option.value = animation;
option.textContent = `${animation} (${supportedBy})`;
sharedAnimationSelect.appendChild(option);
}

sharedAnimationSelect.disabled = false;
playSharedBtn.disabled = false;
}

async function loadZoo() {
status.textContent = "Loading all agents...";

for (const [index, [name, loader]] of entries.entries()) {
const position = layoutFor(index);
const agent = await initAgent(loader);
const animations = agent.animations().sort();

agent.show(true);
agent.moveTo(position.x, position.y, 0);

zooAgents.push({ name, agent, x: position.x, y: position.y, animations });
}

buildSharedAnimationList();
count.textContent = `${zooAgents.length} agents`;
status.textContent = "All agents loaded.";
}

animateAllBtn.addEventListener("click", () => {
for (const entry of zooAgents) {
entry.agent.animate();
}
status.textContent = "Animating all agents.";
});

speakAllBtn.addEventListener("click", () => {
zooAgents.forEach((entry, index) => {
window.setTimeout(() => {
entry.agent.speak(`Hello from ${entry.name}.`);
entry.agent.animate();
}, index * 250);
});
status.textContent = "Starting roll call.";
});

resetAllBtn.addEventListener("click", () => {
for (const entry of zooAgents) {
entry.agent.stop();
entry.agent.moveTo(entry.x, entry.y, 0);
}
status.textContent = "Reset all agents.";
});

playSharedBtn.addEventListener("click", () => {
const animation = sharedAnimationSelect.value;
let played = 0;

for (const entry of zooAgents) {
if (!entry.animations.includes(animation)) {
continue;
}

entry.agent.stop();
entry.agent.play(animation);
played += 1;
}

status.textContent = `Playing ${animation} on ${played} matching agents.`;
});

loadZoo().catch((error) => {
console.error(error);
status.textContent = "Failed to load the zoo demo.";
stage.textContent = error instanceof Error ? error.message : String(error);
});
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ <h2>Welcome to Clippy.js!</h2>
<p>
This is a demo. Click an agent below to try it out.<br />
See <a href="https://github.com/pi0/clippyjs" target="_blank">GitHub</a> for usage and
docs.
docs. Want every agent on one screen? Open the <a href="./zoo.html">agent zoo</a>.
</p>
<div class="agent-select-group" id="agent-buttons"></div>
<div class="anim-select-group">
Expand Down
99 changes: 99 additions & 0 deletions zoo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>📎 Clippy Agent Zoo</title>
<meta
name="description"
content="Browse all ClippyJS agents together and preview shared animations"
/>
<style>
@import url("./demo/styles.css");

body {
overflow: auto;
}

.zoo-shell {
min-height: 100vh;
background: teal;
padding: 20px;
box-sizing: border-box;
}

.zoo-window {
max-width: 1120px;
margin: 0 auto;
}

.zoo-stage {
position: relative;
min-height: 760px;
margin-top: 14px;
padding: 24px;
background:
linear-gradient(transparent 31px, rgba(255, 255, 255, 0.08) 32px),
linear-gradient(90deg, transparent 31px, rgba(255, 255, 255, 0.08) 32px), #008080;
background-size: 32px 32px;
overflow: hidden;
}

.zoo-toolbar {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
margin-top: 12px;
}

.zoo-note {
margin-top: 8px;
}
</style>
</head>
<body>
<div class="zoo-shell">
<div class="win98-window zoo-window">
<div class="win98-titlebar">
<span class="titlebar-text">Clippy.js Agent Zoo</span>
<div class="titlebar-buttons">
<a class="titlebar-btn" href="./index.html" aria-label="Back to demo">←</a>
</div>
</div>
<div class="win98-body">
<h2>All agents, one page</h2>
<p>
Preview every bundled agent at once and trigger shared animations without switching back
and forth.
</p>
<div class="zoo-toolbar">
<button class="agent-btn" id="animate-all">Animate all</button>
<button class="agent-btn" id="speak-all">Roll call</button>
<button class="agent-btn" id="reset-all">Reset positions</button>
<label for="zoo-animation">Shared animation:</label>
<select class="win98-select" id="zoo-animation" disabled>
<option>Loading...</option>
</select>
<button class="agent-btn" id="play-shared" disabled>Play on matching agents</button>
</div>
<p class="zoo-note">
<strong id="zoo-status">Loading agents...</strong>
</p>
<div class="zoo-stage" id="zoo-stage"></div>
</div>
<div class="win98-statusbar">
<span class="statusbar-section" id="zoo-count">0 agents</span>
<span class="statusbar-section" style="flex: 0; white-space: nowrap">
<a href="./index.html">Back to main demo</a>
</span>
</div>
</div>
</div>

<script type="module" src="./demo/zoo.ts"></script>
</body>
</html>