From jointjs-claude-playground
Creates an interactive website map — a zoomable JointJS tree showing every page of a documentation or marketing site, grouped by section, with click-to-open and search. Use when the user wants to visualize the structure of a website, explore a docs site, create a sitemap tree, or map out page hierarchy. Examples: 'create a site map for docs.jointjs.com', 'build a website explorer for react.dev', 'show me the structure of the Next.js docs', 'map out this documentation site', 'visualize the page hierarchy of stripe.com/docs', 'build a sitemap tree for our website'.
How this skill is triggered — by the user, by Claude, or both
Slash command
/jointjs-claude-playground:site-explorerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Generates a self-contained HTML file with a zoomable JointJS tree showing every page of a website — pages as nodes colored by section, parent/child URL hierarchy as links, click-to-open, search, and section filter pills.
Generates a self-contained HTML file with a zoomable JointJS tree showing every page of a website — pages as nodes colored by section, parent/child URL hierarchy as links, click-to-open, search, and section filter pills.
Key difference from other skills: Claude fetches the data (sitemap.xml or root page) during skill execution, then embeds it hardcoded in the HTML. The HTML itself makes no network requests.
templates/site-explorer.md and write the HTML fileopen <filename>-site-explorer.htmlFilename: <domain>-site-explorer.html — e.g. docs-jointjs-site-explorer.html, react-dev-site-explorer.html
Try in order, stop when you get data:
1. WebFetch('<baseUrl>/sitemap.xml')
2. WebFetch('<baseUrl>/sitemap_index.xml') → lists sub-sitemaps, fetch each
3. WebFetch('<baseUrl>/sitemap/')
4. WebFetch(baseUrl) → parse <a href> links from the HTML as fallback
Extract all <loc> values from XML, or all internal href values from HTML.
Filter to URLs that start with baseUrl — discard external links, anchors (#), and query strings.
Deduplicate. Aim for 20–200 URLs — if more, keep only paths with depth ≤ 3.
function buildTree(urls, baseUrl) {
const nodes = new Map();
const root = { id: '__root__', label: baseUrl.replace(/https?:\/\//, ''), url: baseUrl, depth: 0, children: [] };
nodes.set('__root__', root);
// Normalize: strip baseUrl, leading/trailing slashes
const paths = urls
.map(u => u.replace(baseUrl, '').replace(/^\/|\/$/g, ''))
.filter(p => p.length > 0)
.sort(); // sort so parents are always created before children
paths.forEach(path => {
const segments = path.split('/');
segments.forEach((seg, i) => {
const id = segments.slice(0, i + 1).join('/');
if (nodes.has(id)) return;
const parentId = i === 0 ? '__root__' : segments.slice(0, i).join('/');
const node = {
id,
label: decodeURIComponent(seg).replace(/-/g, ' '),
url: baseUrl + '/' + id,
path: '/' + id,
segment: seg,
depth: i + 1,
section: segments[0], // top-level segment = section
children: [],
};
nodes.set(id, node);
const parent = nodes.get(parentId);
if (parent) parent.children.push(node);
});
});
return { root, nodes };
}
Trim ghost nodes: if a URL segment appears only as a parent (no actual page at that URL), mark it isGhost: true — render it smaller/dimmer.
Compute x/y positions with a simple level-order layout. Center each level horizontally:
const NW = 150, NH = 44, H_GAP = 24, V_GAP = 72;
function layoutTree(root) {
// BFS to collect levels
const levels = [];
const queue = [root];
while (queue.length) {
const node = queue.shift();
if (!levels[node.depth]) levels[node.depth] = [];
levels[node.depth].push(node);
node.children.forEach(c => queue.push(c));
}
// Compute tree width from the widest level, then center all levels within it
const maxLevelW = Math.max(...levels.map(lvl => lvl.length * NW + Math.max(0, lvl.length - 1) * H_GAP));
const TREE_W = maxLevelW + 160; // padding on each side
levels.forEach((level, depth) => {
const totalW = level.length * NW + Math.max(0, level.length - 1) * H_GAP;
const offsetX = (TREE_W - totalW) / 2;
level.forEach((node, i) => {
node.x = offsetX + i * (NW + H_GAP);
node.y = depth * (NH + V_GAP) + 60;
});
});
}
For large trees (>80 nodes), collapse leaf-heavy subtrees: represent them as a single summary node ("14 pages") that expands on click.
See templates/site-explorer.md for the full HTML spec including:
PageNode JointJS element definitionAlways include in every generated HTML file — no exceptions. Full spec with position-conflict rules is in the template (## Powered by JointJS badge), but reproduced here so it is never missed:
CSS (inside <style>):
#powered-by {
position: absolute; top: 12px; right: 12px; z-index: 10;
display: flex; align-items: center; gap: 7px;
background: #161b22cc; border: 1px solid #30363d; border-radius: 8px;
padding: 10px 20px 10px 18px; text-decoration: none;
color: #8b949e; font-size: 11px; font-weight: 500;
backdrop-filter: blur(6px);
transition: border-color 160ms, color 160ms, background 160ms;
}
#powered-by:hover { border-color: #58a6ff; color: #c9d1d9; background: #1c2d40cc; }
#powered-by img { margin-left: 5px; height: 18px; width: auto; opacity: 0.85; transition: opacity 160ms; }
#powered-by:hover img { opacity: 1; }
HTML (first child inside #canvas-wrap):
<a id="powered-by" href="https://jointjs.com?utm_source=jointjs-claude-playground&utm_medium=site-explorer&utm_campaign=claude-code" target="_blank" rel="noopener">
<span style="color:#6e7681;font-size:10px;letter-spacing:0.04em;text-transform:uppercase">Powered by</span>
<div class="pb-sep"></div>
<img src="https://cdn.prod.website-files.com/63061d4ee85b5a18644f221c/633045c1d726c7116dcbe582_JJS_logo.svg" alt="JointJS" />
</a>
| Problem | Fix |
|---|---|
| Sitemap returns 404 | Fall back to fetching root page and parsing <a href> links |
| Sitemap index lists sub-sitemaps | Fetch each sub-sitemap and merge all <loc> values |
| Hundreds of URLs | Keep depth ≤ 3, or summarize leaf clusters into count nodes |
| URL segments are UUIDs/IDs | Label them as the parent + "#N" — they're likely dynamic routes |
| No clear section grouping | Use depth-1 segments as sections; if only one, use depth-2 |
| Ghost parent paths (no real page) | Render smaller, dimmer, no click handler |
npx claudepluginhub clientio/jointjs-claude-marketplace --plugin jointjs-claude-playgroundCreate branching, draggable HTML mind maps and concept maps for capturing brainstorms, mapping knowledge structures, exploring debugging hypotheses, or organizing nested ideas. Always include a Submit button (calls `submitToClaude`) to send the captured structure back to the agent for next steps. Use whenever the user wants to capture, organize, or explore branching ideas, hypotheses, knowledge structures, or any tree/graph-shaped thinking — especially when they say "brainstorm", "map out", "explore", or "what if".
Plans website page hierarchies, navigation, URL patterns, and internal linking. Helps structure sites for user experience and SEO. Invoked for site maps, information architecture, or restructuring.
Plans website page hierarchy, navigation, URL patterns, and internal linking for intuitive sites and SEO. Activates on sitemap, site structure, or IA discussions.