0
TOOLS#84621560
Widget Source Pad
@princess-peach·deposited 2w ago·updated 2w ago·34 views
TOOLS#84621560
Widget Source Pad
PR
@princess-peach
34Views
0Comments
0Forks
0Saves
SHARE · REMIX
Widget Source Pad — a HTML tools widget by @princess-peach.
CONTROLS
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: "Widget Source Pad" by @princess-peach]
Source: https://itjustvibes.com/princess-peach/widget-source-pad
Type: HTML
--- SOURCE CODE ---
```html
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Widget Source Pad</title><style>body{margin:0;background:#0b0f14;color:#f7efdf;font-family:system-ui,Segoe UI,sans-serif}.w{max-width:980px;margin:auto;padding:14px}.card{background:#121923;border:1px solid #ffffff22;border-radius:22px;padding:16px;margin:0 0 12px;box-shadow:0 16px 50px #0008}h1{font-size:clamp(34px,8vw,60px);line-height:.9;letter-spacing:-.06em;margin:6px 0 10px}.mut{color:#aeb8c8;line-height:1.45}.row{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px}.tabs,.tools{display:flex;gap:8px;flex-wrap:wrap}button{border:1px solid #ffffff22;background:#172130;color:#f7efdf;border-radius:999px;padding:10px 13px;font-weight:800}.go,.on{background:linear-gradient(135deg,#86f7bf,#a8baff);color:#07100d;border:0}input,textarea{width:100%;background:#0b1017;color:#f7efdf;border:1px solid #2b3646;border-radius:14px;padding:10px}textarea{min-height:310px;font:12px/1.45 ui-monospace,Menlo,Consolas,monospace}label{display:block;color:#aeb8c8;font-size:12px;font-weight:800;margin:0 0 6px}.stat{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.k{background:#0b1017;border:1px solid #2b3646;border-radius:16px;padding:10px}.ok{color:#7cf0a2}.bad{color:#ff7a90}.note{border:1px solid #ffd16655;background:#3b2e123d;border-radius:16px;padding:10px;color:#ffe5a0}.toast{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);background:#f5faef;color:#07100d;border-radius:99px;padding:10px 14px;font-weight:900;opacity:0;transition:.2s}.toast.show{opacity:1;bottom:28px}@media(max-width:650px){.row,.stat{grid-template-columns:1fr}}.card{max-height:max-content;overflow:hidden}.card .tools{margin-top:10px}</style></head><body><div class="w"><div class="card"><div class="mut">It Just Vibes utility</div><h1>Widget Source Pad</h1><p class="mut">Save a widget document in persistent pieces, restore it later, then copy or export the assembled text.</p><div class="tabs"><button data-tab="paste" class="on">Paste</button><button data-tab="pieces">Pieces</button><button data-tab="restore">Restore</button></div></div><div id="app"></div></div><div id="toast" class="toast"></div><script>(()=>{const M='wsp_meta',P='wsp_piece_',MAX=60,te=new TextEncoder();let S={tab:'paste',name:'Untitled widget',size:28000,total:0,text:''},C={},Out='',Hash='';function V(){if(!window.vibes)window.vibes={onReady:f=>document.readyState==='loading'?document.addEventListener('DOMContentLoaded',f):f(),save:async()=>{},load:async()=>null,delete:async()=>{},listKeys:async()=>[]};return window.vibes}function $(x){return document.getElementById(x)}function esc(x){return String(x==null?'':x).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]))}function bytes(x){return te.encode(x||'').length}function fmt(n){return n<1024?n+' B':(n/1024).toFixed(1)+' KB'}function say(x){let t=$('toast');t.textContent=x;t.classList.add('show');clearTimeout(say.t);say.t=setTimeout(()=>t.classList.remove('show'),1400)}async function hash(x){try{let b=await crypto.subtle.digest('SHA-256',te.encode(x||''));Hash=[...new Uint8Array(b)].map(y=>y.toString(16).padStart(2,'0')).join('').slice(0,24)}catch(e){Hash='unavailable'}}function split(x,n){let a=[];for(let i=0;i<x.length;i+=n)a.push(x.slice(i,i+n));return a}function join(){if(!S.total)return S.text||'';let a=[];for(let i=1;i<=S.total;i++){let k=String(i).padStart(2,'0');if(C[k]==null)return null;a.push(C[k])}return a.join('')}function copy(x){if(navigator.clipboard)navigator.clipboard.writeText(x).then(()=>say('Copied'));else{let a=document.createElement('textarea');a.value=x;document.body.appendChild(a);a.select();document.execCommand('copy');a.remove();say('Copied')}}function download(x){let a=document.createElement('a'),u=URL.createObjectURL(new Blob([x],{type:'text/html'}));a.href=u;a.download=(S.name||'widget').toLowerCase().replace(/[^a-z0-9]+/g,'-')+'.html';a.click();setTimeout(()=>URL.revokeObjectURL(u),400)}async function save(){await V().save(M,S).catch(()=>{})}function status(){let j=join(),b=bytes(j||S.text);return `<div class="stat"><div class="k"><b>${fmt(b)}</b><div class="mut">Current size</div></div><div class="k"><b>${Object.keys(C).length}/${S.total||0}</b><div class="mut">Pieces</div></div><div class="k"><b class="${j==null?'bad':'ok'}">${j==null?'Missing':'Ready'}</b><div class="mut">Status</div></div></div><p class="mut">SHA: ${Hash||'--'}</p>`}function paste(){return `<div class="card"><div class="tools"><button class="go" data-a="store">Save as pieces</button><button data-a="copyText">Copy textarea</button></div>${status()}<div class="note">This stores and assembles text only. It does not run pasted content.</div><div class="row" style="margin-top:10px"><div><label>Name</label><input id="name" value="${esc(S.name)}"></div><div><label>Piece size</label><input id="size" type="number" value="${S.size}"></div><div><label>Saved pieces</label><input disabled value="${Object.keys(C).length}"></div></div><label style="margin-top:10px">Widget document</label><textarea id="text" spellcheck="false">${esc(S.text)}</textarea></div>`}function pieces(){let list='';for(let i=1;i<=Math.max(S.total,Object.keys(C).length);i++){let k=String(i).padStart(2,'0');list+=`<div class="k"><b>#${k}</b> <span class="${C[k]?'ok':'bad'}">${C[k]?'saved '+fmt(bytes(C[k])):'missing'}</span> <button data-del="${k}">Delete</button></div>`}return `<div class="card"><div class="tools"><button class="go" data-a="savePiece">Save piece</button><button class="go" data-a="rebuild">Rebuild</button><button class="go" data-a="clear">Clear</button></div>${status()}<div class="row"><div><label>Piece #</label><input id="no" type="number" value="1"></div><div><label>Total</label><input id="total" type="number" value="${S.total||1}"></div><div><label>Saved</label><input disabled value="${Object.keys(C).length}"></div></div><label style="margin-top:10px">Piece text</label><textarea id="piece"></textarea><h3>Manifest</h3>${list||'<p class="mut">No pieces saved yet.</p>'}</div>`}function restore(){let vibes=/window\.vibes|\bvibes\./.test(Out);return `<div class="card"><div class="tools"><button class="go" data-a="copyOut">Copy rebuilt</button><button class="go" data-a="download">Export HTML</button></div>${status()}<label>Rebuilt document</label><textarea id="out">${esc(Out)}</textarea><ul><li class="${/^\s*<!doctype|<html[\s>]/i.test(Out)?'ok':'bad'}">Full HTML document check</li><li class="${vibes?'ok':'bad'}">Vibes SDK check</li></ul></div>`}function render(){let j=join();if(j!=null)Out=j;$('app').innerHTML=S.tab==='pieces'?pieces():S.tab==='restore'?restore():paste();document.querySelectorAll('[data-tab]').forEach(b=>{b.classList.toggle('on',b.dataset.tab===S.tab);b.onclick=()=>{S.tab=b.dataset.tab;save();render()}});bind()}function bind(){['name','size','text'].forEach(id=>{let x=$(id);if(x)x.oninput=x.onchange=()=>{S[id]=id==='size'?+x.value:x.value;if(id==='text')hash(S.text).then(render);save()}});document.querySelectorAll('[data-a]').forEach(b=>b.onclick=()=>act(b.dataset.a));document.querySelectorAll('[data-del]').forEach(b=>b.onclick=async()=>{delete C[b.dataset.del];await V().delete(P+b.dataset.del);render()})}async function act(a){if(a==='store'){S.text=$('text').value;let ps=split(S.text,+S.size||28000);if(ps.length>MAX){say('Too many pieces');return}C={};S.total=ps.length;for(let i=0;i<ps.length;i++){let k=String(i+1).padStart(2,'0');C[k]=ps[i];await V().save(P+k,ps[i])}await hash(S.text);await save();say('Saved '+ps.length);render()}if(a==='copyText')copy($('text').value);if(a==='savePiece'){let n=Math.max(1,Math.min(MAX,+$('no').value||1)),k=String(n).padStart(2,'0');S.total=Math.max(+$('total').value||n,n);C[k]=$('piece').value||'';await V().save(P+k,C[k]);await save();render()}if(a==='rebuild'){let j=join();if(j==null){say('Missing pieces');return}Out=j;await hash(Out);S.tab='restore';render()}if(a==='clear'){if(!confirm('Clear saved pieces?'))return;for(let i=1;i<=MAX;i++)await V().delete(P+String(i).padStart(2,'0')).catch(()=>{});C={};S.total=0;save();render()}if(a==='copyOut')copy(Out);if(a==='download')download(Out)}async function init(){try{let m=await V().load(M);if(m&&typeof m==='object')S=Object.assign(S,m);for(let i=1;i<=MAX;i++){let k=String(i).padStart(2,'0'),v=await V().load(P+k).catch(()=>null);if(typeof v==='string')C[k]=v}Out=join()||S.text||'';await hash(Out)}catch(e){}render()}V().onReady(init)})();</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/princess-peach/widget-source-pad */