{"manifest":{"name":"JSON Tree Explorer","version":"1.0.0","description":"Interactive JSON tree viewer with collapsible nodes, color-coded types, real-time search, and copy-to-clipboard. Paste any JSON and explore it visually.","tags":["json","visualization","data","debugging","tool"],"standard":"agentskills.io","standard_version":"1.0","content_checksum":"40a61499ff89381bbc2e38a8d082bbbd63e13e33a0f395f9c44f6aaf32d191a0","bundle_checksum":"50510d69b90f975df5f0826371399225fc03c546a2878aa232b755d5c75a5857","metadata":{"runtime":{"language":"html","entrypoint":"explorer.html","timeout_seconds":30},"dependencies":{"system":["browser"]}},"files":[{"path":"explorer.html","name":"explorer.html","mime_type":"text/html","checksum":"21e793906ec67c98c363a648b7bf6840f7515113626f2c47ec63ee184a21a0a2"},{"path":"README.md","name":"README.md","mime_type":"text/markdown","checksum":"89da537c4dfdf90abdc8676a1011baee3bd2e6a566270d0e66bd33538e22b6b2"}]},"files":{"SKILL.md":"See skill files — primary content is `explorer.html`.","explorer.html":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<title>JSON Tree Explorer</title>\n<style>\n* { margin: 0; padding: 0; box-sizing: border-box; }\nbody { background: #0a0a1a; color: #e2e8f0; font-family: 'Segoe UI', system-ui, sans-serif; height: 100vh; display: flex; flex-direction: column; }\nheader { background: rgba(6,182,212,0.1); border-bottom: 1px solid rgba(6,182,212,0.2); padding: 10px 18px; display: flex; align-items: center; gap: 12px; }\nheader h1 { font-size: 15px; font-weight: 700; color: #22d3ee; }\n.badge { background: rgba(6,182,212,0.15); border: 1px solid rgba(6,182,212,0.3); color: #06b6d4; font-size: 10px; padding: 2px 8px; border-radius: 20px; font-family: monospace; }\n.hright { margin-left: auto; display: flex; gap: 8px; }\nbutton { background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.12); color: #94a3b8; font-size: 11px; padding: 4px 12px; border-radius: 5px; cursor: pointer; font-family: monospace; transition: all 0.15s; }\nbutton:hover { background: rgba(6,182,212,0.15); border-color: rgba(6,182,212,0.4); color: #22d3ee; }\nmain { display: flex; flex: 1; overflow: hidden; }\n.input-pane { width: 320px; display: flex; flex-direction: column; border-right: 1px solid rgba(255,255,255,0.07); }\n.pane-header { background: rgba(255,255,255,0.03); padding: 7px 14px; font-size: 11px; color: #555; border-bottom: 1px solid rgba(255,255,255,0.06); font-family: monospace; }\ntextarea { flex: 1; background: transparent; border: none; outline: none; padding: 14px; color: #e2e8f0; font-family: monospace; font-size: 12px; line-height: 1.6; resize: none; }\n.tree-pane { flex: 1; overflow-y: auto; padding: 14px 18px; }\n.node { margin: 1px 0; }\n.key { color: #c084fc; font-weight: 600; }\n.colon { color: #555; }\n.toggle { cursor: pointer; user-select: none; color: #475569; font-size: 11px; padding: 0 4px; }\n.toggle:hover { color: #94a3b8; }\n.string { color: #4ade80; }\n.number { color: #60a5fa; }\n.boolean { color: #fb923c; }\n.null { color: #f87171; font-style: italic; }\n.bracket { color: #64748b; }\n.count { color: #374151; font-size: 10px; font-family: monospace; }\n.children { margin-left: 20px; border-left: 1px solid rgba(255,255,255,0.05); padding-left: 10px; }\n.error { color: #f87171; font-family: monospace; font-size: 12px; padding: 14px; }\n.row { display: flex; align-items: flex-start; gap: 4px; font-family: monospace; font-size: 12.5px; line-height: 1.8; }\n.search-bar { padding: 8px 14px; border-bottom: 1px solid rgba(255,255,255,0.06); }\n.search-bar input { width: 100%; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); color: #e2e8f0; font-size: 12px; padding: 5px 10px; border-radius: 5px; outline: none; font-family: monospace; }\n.search-bar input:focus { border-color: rgba(6,182,212,0.4); }\n.highlight { background: rgba(6,182,212,0.2); border-radius: 2px; }\n::-webkit-scrollbar { width: 5px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: rgba(6,182,212,0.2); border-radius: 3px; }\n</style>\n</head>\n<body>\n<header>\n  <h1>JSON Tree Explorer</h1>\n  <span class=\"badge\">SkillSlap</span>\n  <div class=\"hright\">\n    <button onclick=\"expandAll()\">Expand All</button>\n    <button onclick=\"collapseAll()\">Collapse All</button>\n    <button onclick=\"copyFormatted()\">Copy Formatted</button>\n  </div>\n</header>\n<main>\n  <div class=\"input-pane\">\n    <div class=\"pane-header\">📋 Paste JSON</div>\n    <textarea id=\"input\" spellcheck=\"false\" placeholder='Paste JSON here...'></textarea>\n    <div class=\"search-bar\"><input id=\"search\" placeholder=\"🔍 Search keys and values...\" oninput=\"doSearch()\"></div>\n  </div>\n  <div class=\"tree-pane\" id=\"tree\"></div>\n</main>\n<script>\nconst inp = document.getElementById('input');\nconst treeEl = document.getElementById('tree');\nconst searchEl = document.getElementById('search');\nlet searchTerm = '';\nlet collapsed = new Set();\nlet parsed = null;\n\nconst DEFAULT_JSON = {\n  \"skill\": {\n    \"id\": \"a1b2c3d4\",\n    \"title\": \"JSON Tree Explorer\",\n    \"version\": \"1.0.0\",\n    \"tags\": [\"json\", \"visualization\", \"tool\"],\n    \"verified\": true,\n    \"score\": 0.94,\n    \"author\": {\n      \"username\": \"atapifire\",\n      \"verified\": true,\n      \"skills_count\": 12\n    },\n    \"metadata\": {\n      \"runtime\": {\"entrypoint\": \"explorer.html\", \"language\": \"html\"},\n      \"dependencies\": {\"system\": [\"browser\"]}\n    },\n    \"stats\": {\"slaps\": 42, \"tips\": 7, \"forks\": 3},\n    \"description\": \"An interactive JSON tree explorer built in pure JavaScript.\",\n    \"created_at\": \"2026-02-20T00:00:00Z\",\n    \"featured\": null\n  }\n};\n\nfunction esc(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }\n\nfunction hl(text) {\n  if (!searchTerm) return esc(text);\n  const re = new RegExp('(' + searchTerm.replace(/[.*+?^${}()|[\\]\\\\]/g,'\\\\$&') + ')', 'gi');\n  return esc(text).replace(re, '<span class=\"highlight\">$1</span>');\n}\n\nfunction render(val, key, path, depth) {\n  const id = path.join('.');\n  const indent = depth * 20;\n  const isCollapsed = collapsed.has(id);\n\n  if (val === null) {\n    return `<div class=\"row node\" style=\"margin-left:${indent}px\">${key ? `<span class=\"key\">${hl(key)}</span><span class=\"colon\">: </span>` : ''}<span class=\"null\">null</span></div>`;\n  }\n  if (typeof val === 'string') {\n    return `<div class=\"row node\" style=\"margin-left:${indent}px\">${key ? `<span class=\"key\">${hl(key)}</span><span class=\"colon\">: </span>` : ''}<span class=\"string\">\"${hl(val)}\"</span></div>`;\n  }\n  if (typeof val === 'number') {\n    return `<div class=\"row node\" style=\"margin-left:${indent}px\">${key ? `<span class=\"key\">${hl(key)}</span><span class=\"colon\">: </span>` : ''}<span class=\"number\">${hl(val)}</span></div>`;\n  }\n  if (typeof val === 'boolean') {\n    return `<div class=\"row node\" style=\"margin-left:${indent}px\">${key ? `<span class=\"key\">${hl(key)}</span><span class=\"colon\">: </span>` : ''}<span class=\"boolean\">${val}</span></div>`;\n  }\n  if (Array.isArray(val)) {\n    const count = val.length;\n    if (count === 0) return `<div class=\"row node\" style=\"margin-left:${indent}px\">${key ? `<span class=\"key\">${hl(key)}</span><span class=\"colon\">: </span>` : ''}<span class=\"bracket\">[]</span></div>`;\n    const inner = isCollapsed ? '' : val.map((v,i) => render(v, null, [...path,i], depth+1)).join('');\n    return `<div class=\"node\"><div class=\"row\" style=\"margin-left:${indent}px\"><span class=\"toggle\" onclick=\"toggleNode('${id}')\">${isCollapsed?'▶':'▼'}</span>${key ? `<span class=\"key\">${hl(key)}</span><span class=\"colon\">: </span>` : ''}<span class=\"bracket\">[</span><span class=\"count\">${count} items</span>${isCollapsed?'<span class=\"bracket\">]</span>':''}</div>${isCollapsed?'':'<div>'+inner+'</div><div class=\"row\" style=\"margin-left:'+indent+'px\"><span class=\"bracket\">]</span></div>'}</div>`;\n  }\n  if (typeof val === 'object') {\n    const keys = Object.keys(val);\n    if (keys.length === 0) return `<div class=\"row node\" style=\"margin-left:${indent}px\">${key ? `<span class=\"key\">${hl(key)}</span><span class=\"colon\">: </span>` : ''}<span class=\"bracket\">{}</span></div>`;\n    const inner = isCollapsed ? '' : keys.map(k => render(val[k], k, [...path,k], depth+1)).join('');\n    return `<div class=\"node\"><div class=\"row\" style=\"margin-left:${indent}px\"><span class=\"toggle\" onclick=\"toggleNode('${id}')\">${isCollapsed?'▶':'▼'}</span>${key ? `<span class=\"key\">${hl(key)}</span><span class=\"colon\">: </span>` : ''}<span class=\"bracket\">{</span><span class=\"count\">${keys.length} keys</span>${isCollapsed?'<span class=\"bracket\">}</span>':''}</div>${isCollapsed?'':'<div>'+inner+'</div><div class=\"row\" style=\"margin-left:'+indent+'px\"><span class=\"bracket\">}</span></div>'}</div>`;\n  }\n  return '';\n}\n\nfunction renderTree() {\n  if (!parsed) { treeEl.innerHTML = '<div class=\"error\">No valid JSON</div>'; return; }\n  treeEl.innerHTML = render(parsed, null, ['root'], 0);\n}\n\nfunction toggleNode(id) { collapsed.has(id) ? collapsed.delete(id) : collapsed.add(id); renderTree(); }\nfunction expandAll() { collapsed.clear(); renderTree(); }\nfunction collapseAll() {\n  function collectIds(val, path) {\n    if (val && typeof val === 'object') {\n      const id = path.join('.');\n      collapsed.add(id);\n      const keys = Array.isArray(val) ? val.map((_,i)=>i) : Object.keys(val);\n      keys.forEach(k => collectIds(val[k], [...path, k]));\n    }\n  }\n  collectIds(parsed, ['root']);\n  renderTree();\n}\nfunction copyFormatted() {\n  if (parsed) navigator.clipboard?.writeText(JSON.stringify(parsed, null, 2));\n}\nfunction doSearch() { searchTerm = searchEl.value.trim(); renderTree(); }\n\nfunction parseAndRender() {\n  const text = inp.value.trim();\n  if (!text) { parsed = DEFAULT_JSON; inp.value = JSON.stringify(DEFAULT_JSON, null, 2); renderTree(); return; }\n  try { parsed = JSON.parse(text); treeEl.innerHTML = ''; renderTree(); }\n  catch(e) { treeEl.innerHTML = `<div class=\"error\">⚠ JSON parse error: ${esc(e.message)}</div>`; }\n}\n\ninp.value = JSON.stringify(DEFAULT_JSON, null, 2);\nparsed = DEFAULT_JSON;\nrenderTree();\ninp.addEventListener('input', parseAndRender);\n</script>\n</body>\n</html>","README.md":"# JSON Tree Explorer\n\nAn interactive JSON tree viewer with collapsible nodes, syntax highlighting, and search.\n\n## Features\n- Paste any JSON and instantly see a color-coded tree view\n- Collapse/expand individual nodes or all at once\n- Color coding: strings (green), numbers (blue), booleans (orange), null (red), keys (purple)\n- Real-time search highlighting across keys and values\n- Copy formatted JSON to clipboard\n- Handles deeply nested structures gracefully\n\n## Usage\nPaste JSON into the left panel. Use Expand All / Collapse All buttons, or click ▶/▼ next to any node. Type in the search bar to highlight matching keys and values.\n"}}