0
TOOLS#05891bf9
IJV Plugin Marketplace
@Owner·deposited 2w ago·updated 2w ago·27 views
TOOLS#05891bf9
IJV Plugin Marketplace
OW
@Owner
27Views
0Comments
0Forks
0Saves
SHARE · REMIX
IJV Plugin Marketplace — a HTML tools widget by @Owner.
CONTROLS
pluginscoworkmarketplaceclaudetools
No comments yet. Be the first!
✦ Remix with AI
SDK in this widgetNo Vibes SDK features detected yet
Generated prompt
You are helping me modify a vibe-coded widget from itjustvibes.com.
[VIBE CODE: "IJV Plugin Marketplace" by @Owner]
Source: https://itjustvibes.com/Owner/ijv-plugin-marketplace
Type: HTML
--- SOURCE CODE ---
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IJV Plugin Marketplace</title>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300&display=swap" rel="stylesheet">
<style>
:root {
--bg: #07070F;
--surface: #0E0E1C;
--surface2: #131322;
--border: #1C1C32;
--border2: #282840;
--accent: #7B5CF5;
--accent-glow: rgba(123,92,245,0.14);
--accent-bright: #9B7BFF;
--cyan: #2DD4E8;
--green: #3DD68C;
--orange: #F59E0B;
--pink: #EC4899;
--text: #EAEAF8;
--muted: #7777A0;
--dim: #444468;
--r: 10px;
--font: 'Space Grotesk', sans-serif;
--mono: 'DM Mono', monospace;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
background: var(--bg);
color: var(--text);
font-family: var(--font);
display: flex;
flex-direction: column;
min-height: 100vh;
overflow-x: hidden;
}
/* ── Header ── */
.hdr {
position: sticky; top: 0; z-index: 50;
background: rgba(7,7,15,0.92);
backdrop-filter: blur(16px);
border-bottom: 1px solid var(--border);
padding: 0 20px;
}
.hdr-inner {
max-width: 1160px; margin: 0 auto;
display: flex; align-items: center; gap: 16px; height: 52px;
}
.logo {
display: flex; align-items: center; gap: 9px; flex-shrink: 0; text-decoration: none;
}
.logo-gem {
width: 26px; height: 26px; border-radius: 7px;
background: linear-gradient(135deg, var(--accent) 0%, var(--cyan) 100%);
display: flex; align-items: center; justify-content: center;
font-size: 13px; font-weight: 800; color: #fff; letter-spacing: -0.5px;
}
.logo-text { font-size: 14px; font-weight: 600; color: var(--text); }
.logo-pill {
font-size: 9px; font-family: var(--mono); letter-spacing: 0.06em;
color: var(--accent-bright);
background: var(--accent-glow);
border: 1px solid rgba(123,92,245,0.25);
border-radius: 4px; padding: 2px 6px;
}
.search-wrap { flex: 1; max-width: 440px; position: relative; }
.search-ico {
position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
color: var(--dim); pointer-events: none;
}
#search {
width: 100%; padding: 8px 12px 8px 34px;
background: var(--surface); border: 1px solid var(--border);
border-radius: 8px; color: var(--text); font-size: 13px;
font-family: var(--font); outline: none; transition: border-color .15s, box-shadow .15s;
}
#search:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
#search::placeholder { color: var(--dim); }
.hdr-count {
margin-left: auto; font-family: var(--mono); font-size: 11px; color: var(--muted);
}
.hdr-count b { color: var(--accent-bright); }
/* ── Layout ── */
.layout {
max-width: 1160px; margin: 0 auto;
display: flex; gap: 20px; padding: 20px 20px 40px; flex: 1;
}
/* ── Sidebar ── */
.sidebar { width: 188px; flex-shrink: 0; }
.sb-label {
font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase;
font-family: var(--mono); color: var(--dim); padding: 0 8px; margin-bottom: 6px;
}
.cat-btn {
display: flex; align-items: center; gap: 8px; width: 100%;
padding: 7px 8px; border-radius: 8px;
cursor: pointer; border: none; background: transparent;
color: var(--muted); font-size: 13px; font-family: var(--font);
transition: all .15s; text-align: left; margin-bottom: 2px;
}
.cat-btn:hover { background: var(--surface); color: var(--text); }
.cat-btn.on { background: var(--accent-glow); color: var(--accent-bright); }
.cat-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.cat-n { margin-left: auto; font-family: var(--mono); font-size: 10px; color: var(--dim); }
/* ── Main ── */
.main { flex: 1; min-width: 0; }
.main-hdr {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 14px;
}
.main-title { font-size: 13px; color: var(--muted); }
.main-title b { color: var(--text); }
/* ── Grid ── */
.grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(268px, 1fr)); gap: 10px;
}
/* ── Card ── */
.card {
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--r); padding: 14px; cursor: pointer;
transition: transform .18s, box-shadow .18s, background .15s, border-color .15s;
position: relative; overflow: hidden;
}
.card:hover {
background: var(--surface2); border-color: var(--border2);
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(0,0,0,.4), 0 0 0 1px var(--border2);
}
.card-hdr { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 9px; }
.card-ico {
width: 34px; height: 34px; border-radius: 8px;
display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0;
}
.card-meta { flex: 1; min-width: 0; }
.card-name { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.card-by { font-size: 10px; font-family: var(--mono); color: var(--dim); margin-top: 1px; }
.card-desc {
font-size: 12px; color: var(--muted); line-height: 1.55; margin-bottom: 10px;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.card-foot { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.tag {
font-size: 10px; font-family: var(--mono); padding: 2px 6px;
border-radius: 4px; background: rgba(255,255,255,.03); border: 1px solid var(--border);
color: var(--dim);
}
.cat-chip {
margin-left: auto; font-size: 10px; font-family: var(--mono);
letter-spacing: .04em; text-transform: uppercase; padding: 2px 7px; border-radius: 4px;
}
/* ── Empty ── */
.empty { grid-column: 1/-1; text-align: center; padding: 64px 20px; color: var(--muted); }
.empty-ico { font-size: 36px; margin-bottom: 10px; }
.empty h3 { font-size: 15px; margin-bottom: 6px; color: var(--text); }
.empty p { font-size: 12px; }
/* ── Panel overlay ── */
.overlay {
position: fixed; inset: 0; background: rgba(0,0,0,.55);
backdrop-filter: blur(6px); z-index: 100;
opacity: 0; pointer-events: none; transition: opacity .2s;
}
.overlay.on { opacity: 1; pointer-events: auto; }
/* ── Panel ── */
.panel {
position: fixed; right: 0; top: 0; bottom: 0; width: 460px; max-width: 100vw;
background: #0B0B1B; border-left: 1px solid var(--border);
z-index: 101; overflow-y: auto; display: flex; flex-direction: column;
transform: translateX(100%); transition: transform .25s cubic-bezier(.16,1,.3,1);
}
.panel.on { transform: translateX(0); }
.panel-hdr {
padding: 18px 20px 14px; border-bottom: 1px solid var(--border);
display: flex; align-items: flex-start; gap: 14px;
position: sticky; top: 0; background: #0B0B1B; z-index: 1;
}
.panel-ico {
width: 44px; height: 44px; border-radius: 11px;
display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0;
}
.panel-title { flex: 1; }
.p-name { font-size: 17px; font-weight: 700; margin-bottom: 3px; }
.p-by { font-size: 11px; font-family: var(--mono); color: var(--muted); }
.panel-x {
width: 28px; height: 28px; border-radius: 6px;
background: var(--surface); border: 1px solid var(--border);
color: var(--muted); cursor: pointer; display: flex; align-items: center;
justify-content: center; font-size: 13px; flex-shrink: 0; transition: all .15s;
}
.panel-x:hover { color: var(--text); background: var(--surface2); }
.panel-body { padding: 18px 20px; flex: 1; }
.psec { margin-bottom: 18px; }
.psec-lbl {
font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
font-family: var(--mono); color: var(--dim); margin-bottom: 7px;
}
.p-desc { font-size: 13px; color: var(--muted); line-height: 1.6; }
.triggers { display: flex; flex-wrap: wrap; gap: 5px; }
.trigger {
font-size: 11px; font-family: var(--mono); color: var(--cyan);
background: rgba(45,212,232,.07); border: 1px solid rgba(45,212,232,.14);
border-radius: 5px; padding: 4px 9px;
}
.skill-list { display: flex; flex-direction: column; gap: 6px; }
.skill-item {
background: var(--surface); border: 1px solid var(--border);
border-radius: 8px; padding: 8px 11px;
}
.s-name { font-size: 12px; font-weight: 600; margin-bottom: 2px; }
.s-desc { font-size: 11px; color: var(--muted); line-height: 1.4; }
.panel-foot { padding: 14px 20px; border-top: 1px solid var(--border); }
.install-btn {
width: 100%; padding: 11px; background: var(--accent);
border: none; border-radius: 8px; color: #fff;
font-size: 13px; font-weight: 600; font-family: var(--font);
cursor: pointer; transition: all .15s; display: flex; align-items: center;
justify-content: center; gap: 7px;
}
.install-btn:hover {
background: var(--accent-bright); transform: translateY(-1px);
box-shadow: 0 4px 18px rgba(123,92,245,.4);
}
@keyframes fadeUp {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
.card { animation: fadeUp .28s ease both; }
.card:nth-child(2) { animation-delay: 40ms; }
.card:nth-child(3) { animation-delay: 70ms; }
.card:nth-child(4) { animation-delay: 100ms; }
.card:nth-child(5) { animation-delay: 130ms; }
.card:nth-child(6) { animation-delay: 155ms; }
.card:nth-child(7) { animation-delay: 180ms; }
.card:nth-child(8) { animation-delay: 200ms; }
.card:nth-child(9) { animation-delay: 220ms; }
.card:nth-child(10){ animation-delay: 240ms; }
.card:nth-child(11){ animation-delay: 255ms; }
.card:nth-child(12){ animation-delay: 270ms; }
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-inner">
<div class="logo">
<div class="logo-gem">⬡</div>
<span class="logo-text">Plugin Marketplace</span>
<span class="logo-pill">IJV</span>
</div>
<div class="search-wrap">
<svg class="search-ico" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
<input id="search" type="text" placeholder="Search plugins, skills, or trigger phrases…">
</div>
<div class="hdr-count"><b id="total">0</b> plugins</div>
</div>
</header>
<div class="layout">
<aside class="sidebar">
<div class="sb-label">Categories</div>
<div id="cats"></div>
</aside>
<main class="main">
<div class="main-hdr">
<div class="main-title" id="main-title">All plugins</div>
</div>
<div class="grid" id="grid"></div>
</main>
</div>
<div class="overlay" id="overlay"></div>
<div class="panel" id="panel">
<div class="panel-hdr">
<div class="panel-ico" id="p-ico"></div>
<div class="panel-title">
<div class="p-name" id="p-name"></div>
<div class="p-by" id="p-by"></div>
</div>
<button class="panel-x" id="panel-x">✕</button>
</div>
<div class="panel-body">
<div class="psec">
<div class="psec-lbl">Description</div>
<div class="p-desc" id="p-desc"></div>
</div>
<div class="psec" id="p-triggers-sec">
<div class="psec-lbl">Trigger phrases</div>
<div class="triggers" id="p-triggers"></div>
</div>
<div class="psec" id="p-skills-sec">
<div class="psec-lbl">Skills included</div>
<div class="skill-list" id="p-skills"></div>
</div>
</div>
<div class="panel-foot">
<button class="install-btn" id="install-btn">⬇ Copy plugin path</button>
</div>
</div>
<script>
const CATS = [
{ id:'all', label:'All Plugins', color:'#7777A0', dot:'#444468' },
{ id:'context', label:'Context & Memory', color:'#2DD4E8', dot:'#2DD4E8' },
{ id:'dev', label:'Development', color:'#7B5CF5', dot:'#7B5CF5' },
{ id:'workflow', label:'Workflow', color:'#3DD68C', dot:'#3DD68C' },
{ id:'writing', label:'Writing & Docs', color:'#F59E0B', dot:'#F59E0B' },
{ id:'management', label:'Plugin Management', color:'#EC4899', dot:'#EC4899' },
];
const PLUGINS = [
{
id:'ijv-skills', name:'IJV Skills', author:'ijv-skills', icon:'🏗️', cat:'context',
desc:'A complete project context suite: prime your AI-coding environment, track design specs, close sessions with full handoff docs, and convert ideas into verifiable /goal commands for autonomous loops.',
triggers:['prime','close session','design spec','goal prompt','/ijv-prime','/ijv-close','/ijv-goal-prompt'],
skills:[
{n:'ijv-prime',d:'Initialize and maintain AI-coding context for Claude Code and Codex'},
{n:'ijv-close',d:'End-of-session documentation sync — layout-aware, updates handoff and changelogs'},
{n:'ijv-design-spec',d:'Create and maintain a project UI design specification from the live UI'},
{n:'ijv-goal-prompt',d:'Convert raw ideas into /goal commands for autonomous agent loops'},
],
},
{
id:'ponytail', name:'Ponytail', author:'ponytail', icon:'🐎', cat:'dev',
desc:'Forces the laziest working solution. Senior-dev energy: questions whether the task should exist at all, reaches for stdlib before custom code, one line before fifty. Intensity modes: lite, full, ultra.',
triggers:['ponytail','be lazy','lazy mode','simplest solution','yagni','do less','shortest path','/ponytail'],
skills:[
{n:'ponytail',d:'Minimal coding mode — produce the laziest solution that actually works'},
{n:'ponytail-review',d:'Code review focused exclusively on over-engineering — what to delete'},
{n:'ponytail-audit',d:'Whole-repo audit for complexity: ranked list of what to cut'},
{n:'ponytail-debt',d:'Harvest all ponytail: deferred comments into a tracked ledger'},
{n:'ponytail-gain',d:'Show ponytail\'s measured impact as a compact scoreboard'},
{n:'ponytail-help',d:'Quick-reference card for all ponytail modes and commands'},
],
},
{
id:'superpowers', name:'Superpowers', author:'superpowers', icon:'⚡', cat:'dev',
desc:'A complete professional development methodology suite: TDD, brainstorming before building, systematic debugging, code review (requesting and receiving), parallel agent dispatch, and worktree isolation.',
triggers:['brainstorm','debug','test driven','code review','parallel agents','git worktree','verification'],
skills:[
{n:'brainstorming',d:'Explore user intent and design requirements before any implementation'},
{n:'test-driven-development',d:'Write and run tests before writing implementation code'},
{n:'systematic-debugging',d:'Structured root-cause analysis for any bug or test failure'},
{n:'requesting-code-review',d:'Structured review workflow when completing major features'},
{n:'receiving-code-review',d:'Technical rigor when responding to review feedback — no blind compliance'},
{n:'dispatching-parallel-agents',d:'Coordinate 2+ independent tasks running concurrently'},
{n:'using-git-worktrees',d:'Isolated feature workspaces via native tools or git worktree fallback'},
{n:'writing-plans',d:'Turn specs and requirements into multi-step implementation plans'},
{n:'executing-plans',d:'Execute plans in a separate session with structured review checkpoints'},
],
},
{
id:'superpowers-dev', name:'Superpowers Dev', author:'superpowers-dev', icon:'🛠️', cat:'dev',
desc:'Claude Code–specific variant of Superpowers. Identical structured methodologies (TDD, debugging, planning, review, worktrees) tuned for coding agent sessions with file system access.',
triggers:['write plan','execute plan','finish branch','verification before completion','subagent'],
skills:[
{n:'brainstorming',d:'Design exploration tuned for Claude Code sessions'},
{n:'writing-plans',d:'Multi-step implementation plans with testable checkpoints'},
{n:'executing-plans',d:'Plan execution with review gates in a separate agent session'},
{n:'finishing-a-development-branch',d:'Structured merge, PR, or cleanup options after implementation'},
{n:'verification-before-completion',d:'Evidence before assertions — always verify before claiming done'},
{n:'using-git-worktrees',d:'Isolated workspace setup for feature work'},
{n:'subagent-driven-development',d:'Parallel task execution within the current session'},
],
},
{
id:'superpowers-lab', name:'Superpowers Lab', author:'superpowers-lab', icon:'🧪', cat:'dev',
desc:'Experimental development utilities: headless Windows VM in Docker, tmux for interactive CLIs, Slack messaging from the command line, MCP on-demand via CLI, and semantic duplicate function detection.',
triggers:['windows vm','tmux interactive','slack message','mcp cli','duplicate functions','finding-duplicate'],
skills:[
{n:'windows-vm',d:'Create, manage, or SSH into a headless Windows 11 VM in Docker'},
{n:'using-tmux-for-interactive-commands',d:'Control interactive CLI tools (vim, REPL, etc.) via tmux'},
{n:'slack-messaging',d:'Send and read Slack messages from the command line'},
{n:'mcp-cli',d:'Use MCP servers on-demand without polluting context with pre-loaded integrations'},
{n:'finding-duplicate-functions',d:'Audit codebase for semantic function duplication in LLM-generated code'},
],
},
{
id:'episodic-memory', name:'Episodic Memory', author:'episodic-memory', icon:'🧠', cat:'context',
desc:'Structured conversation recall. Must be invoked before saying "I don\'t know" — searches past conversations to recover what was actually said, supplementing other memory systems that only hold partial records.',
triggers:['do you remember','recall','past conversation','what did we discuss','what did we decide'],
skills:[
{n:'remembering-conversations',d:'Search and recall past conversation context before guessing or claiming ignorance'},
],
},
{
id:'cowork-plugin-management', name:'Plugin Management', author:'cowork-plugin-management', icon:'🧩', cat:'management',
desc:'Create and customize Cowork plugins through guided conversation. Phase-by-phase workflow: discovery, component planning, design, implementation, and packaging — outputs a ready-to-install .plugin file.',
triggers:['create plugin','build plugin','customize plugin','new plugin','configure plugin','scaffold plugin'],
skills:[
{n:'create-cowork-plugin',d:'Build a new Cowork plugin from scratch via guided five-phase workflow'},
{n:'cowork-plugin-customizer',d:'Customize an existing plugin for a specific organization\'s tools and workflows'},
],
},
{
id:'elements-of-style', name:'Elements of Style', author:'elements-of-style', icon:'✍️', cat:'writing',
desc:"Apply Strunk's timeless writing rules to any prose humans will read — docs, commit messages, error messages, reports, or UI copy. Produces clearer, stronger, more professional writing in any context.",
triggers:['writing clearly','improve prose','writing rules','strunk white','clearer writing','concise writing'],
skills:[
{n:'writing-clearly-and-concisely',d:"Apply Strunk & White's rules to any prose: active voice, strong verbs, no filler"},
],
},
{
id:'lacuna', name:'Lacuna', author:'lacuna', icon:'🔭', cat:'workflow',
desc:'Structured gap analysis for any domain. Maps a field as a concept space, finds what\'s structurally absent, names the force keeping the gap empty, sorts opportunity from dead end, and proposes the fill at full conviction.',
triggers:['find the lacuna','gap analysis','lacuna analysis','what is structurally missing','what is nobody doing','/lacuna'],
skills:[
{n:'lacuna',d:'Rigorous gap analysis: map a field, find the void, propose the fill with a confidence flag'},
],
},
{
id:'grill-me', name:'Grill Me', author:'grill-me', icon:'🔥', cat:'workflow',
desc:'Stress-test your plans by having Claude interrogate them relentlessly. Works through each branch of the decision tree one by one until shared understanding is reached — before you commit.',
triggers:['grill me','stress-test my plan','interrogate my design','push back on this','/grill-me'],
skills:[
{n:'grill-me',d:'Relentless plan interrogation: surfaces every weak assumption before commitment'},
],
},
{
id:'fueling-design-basis', name:'Fueling Design Basis', author:'fueling-design-basis', icon:'⛽', cat:'workflow',
desc:'Establish the design basis for fuel-handling projects: extract governing requirements, research applicable codes (CFR, OSHA, NFPA, API, EI, ASME), and produce a traceable, structured Design Basis document.',
triggers:['design basis','basis of design','BOD','fuel farm requirements','fueling project scope'],
skills:[
{n:'fueling-design-basis',d:'Scoping and requirements for airport fuel farms, storage sites, and engine test-cell fuel systems'},
],
},
{
id:'innovation-one-pager', name:'Innovation One Pager', author:'innovation-one-pager', icon:'💡', cat:'writing',
desc:'Generate approval-ready innovation write-ups as branded Word documents. Fills a 7-section template with an honest value case targeting 8× return on build cost within 12 months.',
triggers:['one pager','innovation request','pitch this idea','idea write-up','innovation roundtable','approval request'],
skills:[
{n:'innovation-one-pager',d:'Turn an AI idea into a polished, approval-ready Burns & McDonnell innovation write-up'},
],
},
];
let activeCat = 'all';
let query = '';
let activePlugin = null;
const gridEl = document.getElementById('grid');
const overlayEl = document.getElementById('overlay');
const panelEl = document.getElementById('panel');
const catsEl = document.getElementById('cats');
const totalEl = document.getElementById('total');
const titleEl = document.getElementById('main-title');
function catColor(id) { return (CATS.find(c=>c.id===id)||{}).color||'#7777A0'; }
function catLabel(id) { return (CATS.find(c=>c.id===id)||{}).label||''; }
function filtered() {
const q = query.toLowerCase().trim();
return PLUGINS.filter(p => {
const mc = activeCat==='all' || p.cat===activeCat;
const mq = !q ||
p.name.toLowerCase().includes(q) ||
p.desc.toLowerCase().includes(q) ||
p.author.toLowerCase().includes(q) ||
(p.triggers||[]).some(t=>t.toLowerCase().includes(q)) ||
(p.skills||[]).some(s=>s.n.toLowerCase().includes(q)||s.d.toLowerCase().includes(q));
return mc && mq;
});
}
function renderCats() {
catsEl.innerHTML='';
CATS.forEach(c=>{
const count = c.id==='all' ? PLUGINS.length : PLUGINS.filter(p=>p.cat===c.id).length;
if(!count && c.id!=='all') return;
const btn = document.createElement('button');
btn.className = 'cat-btn' + (activeCat===c.id?' on':'');
btn.innerHTML = `<span class="cat-dot" style="background:${c.dot}"></span>${c.label}<span class="cat-n">${count}</span>`;
btn.onclick = () => { activeCat=c.id; render(); };
catsEl.appendChild(btn);
});
}
function renderGrid() {
const list = filtered();
totalEl.textContent = PLUGINS.length;
if(!list.length) {
gridEl.innerHTML = `<div class="empty"><div class="empty-ico">🔍</div><h3>No plugins found</h3><p>Try a different search term or category</p></div>`;
titleEl.innerHTML = '<b>0</b> results';
return;
}
const q = query.trim();
titleEl.innerHTML = q
? `<b>${list.length}</b> result${list.length!==1?'s':''} for "${q}"`
: `<b>${list.length}</b> ${activeCat==='all'?'plugins':catLabel(activeCat)+' plugins'}`;
gridEl.innerHTML = list.map(p => {
const c = catColor(p.cat);
return `<div class="card" onclick="openPanel('${p.id}')">
<div class="card-hdr">
<div class="card-ico" style="background:${c}18">${p.icon}</div>
<div class="card-meta">
<div class="card-name">${p.name}</div>
<div class="card-by">${p.author}</div>
</div>
</div>
<div class="card-desc">${p.desc}</div>
<div class="card-foot">
<span class="tag">${p.skills.length} skill${p.skills.length!==1?'s':''}</span>
<span class="cat-chip" style="background:${c}14;color:${c};border:1px solid ${c}28">${catLabel(p.cat)}</span>
</div>
</div>`;
}).join('');
}
function render() { renderCats(); renderGrid(); }
function openPanel(id) {
const p = PLUGINS.find(x=>x.id===id);
if(!p) return;
activePlugin = p;
const c = catColor(p.cat);
document.getElementById('p-ico').style.background = `${c}18`;
document.getElementById('p-ico').textContent = p.icon;
document.getElementById('p-name').textContent = p.name;
document.getElementById('p-by').textContent = p.author;
document.getElementById('p-desc').textContent = p.desc;
const tSec = document.getElementById('p-triggers-sec');
const tEl = document.getElementById('p-triggers');
if(p.triggers&&p.triggers.length){
tSec.style.display='';
tEl.innerHTML = p.triggers.map(t=>`<span class="trigger">${t}</span>`).join('');
} else tSec.style.display='none';
const sSec = document.getElementById('p-skills-sec');
const sEl = document.getElementById('p-skills');
if(p.skills&&p.skills.length){
sSec.style.display='';
sEl.innerHTML = p.skills.map(s=>`<div class="skill-item"><div class="s-name">${s.n}</div><div class="s-desc">${s.d}</div></div>`).join('');
} else sSec.style.display='none';
panelEl.classList.add('on');
overlayEl.classList.add('on');
}
function closePanel() {
panelEl.classList.remove('on');
overlayEl.classList.remove('on');
activePlugin=null;
}
document.getElementById('panel-x').onclick = closePanel;
overlayEl.onclick = closePanel;
document.getElementById('install-btn').onclick = () => {
if(!activePlugin) return;
const path = `/mnt/skills/plugins/${activePlugin.author}`;
try { navigator.clipboard.writeText(path); } catch(e){}
const btn = document.getElementById('install-btn');
btn.textContent = '✓ Path copied!';
setTimeout(()=>{ btn.textContent='⬇ Copy plugin path'; }, 2000);
};
document.getElementById('search').addEventListener('input', e => { query=e.target.value; render(); });
render();
</script>
</body>
</html>
```
[REQUESTED CHANGES]
(no specific request — apply your best judgment)
--- HOW TO RESPOND (READ FIRST) ---
Before writing any code, follow this exact process:
1. **ANALYZE** the widget source code provided above and identify:
a. Which Vibes SDK features it already uses (vibes.save, vibes.load, vibes.shared.join, etc.)
b. Which SDK features would genuinely benefit THIS specific widget — tailored to what it does, not a dump of everything available.
2. **PRESENT A NUMBERED LIST** covering:
- SDK features currently active in this widget
- New SDK features that would concretely improve this widget (be specific: why this widget, what it enables)
3. **WAIT** — do not write any code yet. Reply with your analysis and numbered list, then stop and ask the user which numbered items they want.
4. **IMPLEMENT ONLY** the items the user confirms, plus any explicit change they requested. Do not add unrequested features.
**IMPORTANT — Shared state room names:**
If you add vibes.shared.join(), do NOT use a hardcoded string literal as the room name (e.g. vibes.shared.join("lobby")) unless the user explicitly wants ALL viewers to share one single global state. A hardcoded room name means every person who visits this widget reads and writes the same shared state — it is a global room. For per-user or per-session isolation, derive the room name from a variable (e.g. a user ID, session token, or random value). When in doubt, use vibes.save/vibes.load for per-user persistence instead.
--- VIBES SDK CONTEXT ---
## Vibes SDK Reference
You are building an HTML widget for It Just Vibes (itjustvibes.com). The Vibes SDK is auto-injected — do NOT add a script tag. Just use `window.vibes` (or just `vibes`).
### Setup
Wrap your startup code in `vibes.onReady`:
```js
vibes.onReady(async () => {
const saved = await vibes.load("myKey");
// your widget logic here
});
```
### State (Per-User Persistence)
Every user gets their own isolated state per widget. All methods return Promises.
| Method | Description |
|--------|-------------|
| `await vibes.save(key, value)` | Save JSON-serializable data |
| `await vibes.load(key)` | Load saved data (returns `null` if not found) |
| `await vibes.delete(key)` | Delete a saved key |
| `await vibes.listKeys()` | Get array of all saved key names |
**Key rules:**
- Keys are strings, max 64 characters, alphanumeric + dashes/underscores
- Values must be JSON-serializable (objects, arrays, strings, numbers, booleans)
- Max 100KB per value, 500KB per widget per user, 5MB per user total
- Max 100 keys per widget per user
### Fetch Proxy
Use direct `fetch()` for APIs with permissive CORS headers (`Access-Control-Allow-Origin: *`). Use `vibes.fetch` for APIs without CORS headers (the proxy handles cross-origin requests):
```js
const resp = await vibes.fetch("https://api.example.com/data", {
method: "GET", // GET, POST, PUT, DELETE
headers: {}, // optional headers
body: null, // optional body (string)
timeout: null // optional timeout in ms
});
const data = await resp.json(); // or resp.text()
console.log(resp.status, resp.ok);
```
### Multiplayer (Shared State)
Real-time shared state across all users viewing the same widget.
```js
// Join a room (call once at startup)
await vibes.shared.join("lobby", { persistent: true });
// Set shared state (broadcasts to all users)
await vibes.shared.set("score", { player1: 10, player2: 7 });
// Read shared state (synchronous, returns last known value)
const score = vibes.shared.get("score");
// Listen for changes to a specific key
vibes.shared.onChange("score", (newValue) => {
console.log("Score updated:", newValue);
});
// Listen for any shared state change
vibes.shared.onAny((key, value) => {
console.log(key, "changed to", value);
});
// Get number of connected users
const count = await vibes.shared.getUserCount();
// Leave room
vibes.shared.leave();
// Clear all shared state for this room
await vibes.shared.clear();
```
**Shared state options:**
- `{ persistent: true }` — state survives page reloads (stored server-side)
- Default room name is "__default__" if omitted
### Agent-Accessible State (vibes.ai.*)
State written with the standard `vibes.save` / `vibes.load` methods is private to each user and is **NOT readable by AI agents**. To share state with an AI agent (via the MCP connector), use the `vibes.ai` sub-namespace:
| Method | Description |
|--------|-------------|
| `await vibes.ai.setState(key, value)` | Write agent-readable state. Key is stored internally as `ai/<key>`. |
| `await vibes.ai.getState(key)` | Read agent-readable state. Returns `null` if key absent. |
| `await vibes.ai.listKeys()` | List all agent-readable keys (without the `ai/` prefix). |
**Important rules:**
- Regular `vibes.save()` / `vibes.load()` is **NOT agent-accessible** — use `vibes.ai.*` for state you want agents to read.
- The widget **owner** must enable agent access in **Manage → Agent tab** before any agent can read or write `vibes.ai.*` state.
- Keys are auto-prefixed to `ai/` internally; you supply just the short key (e.g. `'context'`).
```js
vibes.onReady(async () => {
// Write state an AI agent can later read
await vibes.ai.setState('context', { currentLevel: 3, score: 1500 });
// Read it back (same auto-prefix applies)
const ctx = await vibes.ai.getState('context');
// List all agent-accessible keys for this widget
const keys = await vibes.ai.listKeys(); // e.g. ['context']
});
```
### Rules
1. **Do NOT use localStorage, sessionStorage, or window.storage** — they are blocked or undefined in the sandbox. Use `vibes.save`/`vibes.load` instead.
2. **Do NOT add a script tag to import the SDK** — it is auto-injected.
3. **Wrap startup code in `vibes.onReady()`** — the SDK may not be ready immediately.
4. **Await all SDK calls** — every method (except `vibes.shared.get`) returns a Promise.
5. **External JS libraries are allowed and encouraged** — load them via a `<script src>` tag from a reputable CDN (jsDelivr, unpkg, or cdnjs) with a pinned version, or inline the library source. The only exception: do NOT add a script tag for the Vibes SDK itself — it is auto-injected (see Rule 2).
6. **Keep total code under 100KB** — that is the default widget size limit (your account limit may be higher).
### Rate Limits
| Operation | Limit |
|-----------|-------|
| Writes (save + delete combined) | 30/min |
| Reads (load) | 60/min |
| List keys | 30/min |
| Fetch proxy | Rate limited per widget |
### Error Handling
All SDK methods can reject. Wrap in try/catch:
```js
try {
await vibes.save("key", value);
} catch (err) {
console.error("Save failed:", err.message);
}
```
Fetch proxy errors include `err.code`: `"RATE_LIMITED"`, `"BLOCKED"`, `"FETCH_ERROR"`.
### Data Export
Export rows of data as CSV or Excel directly from your widget.
```js
// Register dataset for the platform's "Export data" button
// AND optionally trigger an immediate download
vibes.exportData(rows, { filename: 'results.csv' })
// rows: Array of arrays (each inner array is one row; first row = headers)
// options.filename: sets the download filename and format (csv or xlsx)
// options.directDownload: false to only register without downloading (default: true)
```
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `filename` | string | `'data.csv'` | Download filename; extension determines format (`.csv` or `.xlsx`) |
| `directDownload` | boolean | `true` | Trigger immediate browser download in addition to registering the dataset |
**Notes:**
- First call registers the dataset so the widget chrome shows an "Export data" button
- Use `.csv` extension for CSV, `.xlsx` for Excel
- CSV injection safety is automatic (formula-starting cells prefixed with `'`)
- Data stays in the browser — no server round-trip
--- FORK & RESUBMIT INSTRUCTION ---
Return the complete, self-contained HTML file with all changes applied.
It should be ready to paste into itjustvibes.com/submit to create a new fork.
Include this comment at the top of the output:
/* Forked from: https://itjustvibes.com/Owner/ijv-plugin-marketplace */