0
GAMES#0048ceb0
SEEFOOD
@GigaChadΒ·deposited 2mo agoΒ·updated 1mo agoΒ·111 views
GAMES#0048ceb0
SEEFOOD
GI
@GigaChad
111Views
0Comments
0Forks
0Saves
SHARE Β· REMIX
SEEFOOD β a JSX games widget by @GigaChad.
CONTROLS
#useless#fun
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: "SEEFOOD" by @GigaChad]
Source: https://itjustvibes.com/GigaChad/seefood
Type: React/JSX
--- SOURCE CODE ---
```jsx
import { useState, useRef, useCallback, useEffect } from "react";
const PHASE = {
LOADING: "loading",
WELCOME: "welcome",
LIVE: "live",
SCANNING: "scanning",
HOTDOG: "hotdog",
NOTHOTDOG: "nothotdog",
ERROR: "error",
};
const HOTDOG_CLASS = 934;
function loadScript(src) {
return new Promise((resolve, reject) => {
const s = document.createElement("script");
s.src = src;
s.onload = resolve;
s.onerror = reject;
document.head.appendChild(s);
});
}
export default function SeeFood() {
const [phase, setPhase] = useState(PHASE.LOADING);
const [facing, setFacing] = useState("environment");
const [errMsg, setErrMsg] = useState("");
const [snapUrl, setSnapUrl] = useState(null);
const [loadMsg, setLoadMsg] = useState("Starting up...");
const [confidence, setConfidence] = useState(null);
const videoRef = useRef(null);
const canvasRef = useRef(null);
const streamRef = useRef(null);
const modelRef = useRef(null);
const font = `'Bebas Neue','Impact','Arial Black',sans-serif`;
const body = `'DM Sans','Helvetica Neue',sans-serif`;
useEffect(() => {
let dead = false;
(async () => {
try {
setLoadMsg("Loading TensorFlow.js...");
await loadScript("https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@4.22.0/dist/tf.min.js");
if (dead) return;
setLoadMsg("Loading MobileNet model...");
await loadScript("https://cdn.jsdelivr.net/npm/@tensorflow-models/mobilenet@2.1.1/dist/mobilenet.min.js");
if (dead) return;
setLoadMsg("Initializing neural network...");
const tf = window.tf;
await tf.ready();
if (dead) return;
setLoadMsg("Downloading hotdog classifier...");
const model = await window.mobilenet.load({ version: 1, alpha: 0.25 });
if (dead) return;
modelRef.current = model;
setPhase(PHASE.WELCOME);
} catch (err) {
console.error("Load failed:", err);
if (!dead) {
setErrMsg("Couldn't load the AI model. Check your connection and reload.");
setPhase(PHASE.ERROR);
}
}
})();
return () => { dead = true; };
}, []);
const openCamera = useCallback(async (mode) => {
try {
if (streamRef.current) streamRef.current.getTracks().forEach((t) => t.stop());
const s = await navigator.mediaDevices.getUserMedia({
video: { facingMode: mode, width: { ideal: 1280 }, height: { ideal: 960 } },
audio: false,
});
streamRef.current = s;
if (videoRef.current) {
videoRef.current.srcObject = s;
await videoRef.current.play();
}
setPhase(PHASE.LIVE);
} catch {
setErrMsg("Camera access denied -- check permissions and try again.");
setPhase(PHASE.ERROR);
}
}, []);
useEffect(() => () => {
if (streamRef.current) streamRef.current.getTracks().forEach((t) => t.stop());
}, []);
const flip = () => {
const next = facing === "environment" ? "user" : "environment";
setFacing(next);
openCamera(next);
};
const snap = async () => {
if (!videoRef.current || !canvasRef.current || !modelRef.current) return;
const v = videoRef.current;
const c = canvasRef.current;
c.width = v.videoWidth;
c.height = v.videoHeight;
c.getContext("2d").drawImage(v, 0, 0);
setSnapUrl(c.toDataURL("image/jpeg", 0.75));
setPhase(PHASE.SCANNING);
try {
const model = modelRef.current;
// Create a temporary image element for MobileNet
const img = document.createElement("img");
img.width = 224;
img.height = 224;
img.src = c.toDataURL("image/jpeg", 0.75);
await new Promise((res) => { img.onload = res; });
const predictions = await model.classify(img);
// Check if any prediction relates to hotdog
let isHotdog = false;
let conf = 0;
for (const p of predictions) {
const name = p.className.toLowerCase();
if (name.includes("hotdog") || name.includes("hot dog") || name.includes("red hot")) {
isHotdog = true;
conf = p.probability;
break;
}
}
// Also check via infer for raw class indices
if (!isHotdog) {
const logits = model.infer(img);
const data = await logits.data();
logits.dispose();
// Softmax manually to get probabilities
let maxVal = -Infinity;
for (let i = 0; i < data.length; i++) {
if (data[i] > maxVal) maxVal = data[i];
}
let sum = 0;
const probs = new Float32Array(data.length);
for (let i = 0; i < data.length; i++) {
probs[i] = Math.exp(data[i] - maxVal);
sum += probs[i];
}
for (let i = 0; i < probs.length; i++) probs[i] /= sum;
const hotdogProb = probs[HOTDOG_CLASS];
if (hotdogProb > 0.05) {
isHotdog = true;
conf = hotdogProb;
}
}
setConfidence(conf);
setPhase(isHotdog ? PHASE.HOTDOG : PHASE.NOTHOTDOG);
} catch (e) {
console.error("Classification error:", e);
setErrMsg("Classification failed -- try again.");
setPhase(PHASE.ERROR);
}
};
const again = () => { setSnapUrl(null); setConfidence(null); setPhase(PHASE.LIVE); setErrMsg(""); };
const showResult = phase === PHASE.HOTDOG || phase === PHASE.NOTHOTDOG;
const isHot = phase === PHASE.HOTDOG;
return (
<div style={{ position: "fixed", inset: 0, background: "#0a0a0a", overflow: "hidden", fontFamily: body }}>
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet" />
<style>{`@keyframes sweep{0%{top:-4px}100%{top:100%}} @keyframes pulseScan{0%,100%{opacity:.5}50%{opacity:1}} @keyframes slamResult{0%{transform:scale(4) rotate(-10deg);opacity:0}50%{transform:scale(.85) rotate(3deg);opacity:1}75%{transform:scale(1.08) rotate(-1deg)}100%{transform:scale(1) rotate(0)}} @keyframes driftUp{0%{transform:translateY(30px);opacity:0}100%{transform:translateY(0);opacity:1}} @keyframes emojiPop{0%{transform:scale(0) rotate(-30deg);opacity:0}60%{transform:scale(1.3) rotate(8deg);opacity:1}100%{transform:scale(1) rotate(0)}} @keyframes borderPulse{0%,100%{opacity:.35}50%{opacity:.9}} @keyframes floatUp{0%{transform:translateY(0) scale(1);opacity:.7}100%{transform:translateY(-110vh) scale(.3);opacity:0}} @keyframes ripple{0%{transform:scale(0);opacity:.5}100%{transform:scale(6);opacity:0}} @keyframes subtleBob{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}} @keyframes glowPulse{0%,100%{box-shadow:0 0 20px rgba(255,107,53,.2)}50%{box-shadow:0 0 40px rgba(255,107,53,.45)}} @keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}`}</style>
<video ref={videoRef} autoPlay playsInline muted style={{ position:"absolute",inset:0,width:"100%",height:"100%",objectFit:"cover",zIndex:1,transform:facing==="user"?"scaleX(-1)":"none",opacity:phase===PHASE.LOADING||phase===PHASE.WELCOME?0:1 }} />
<canvas ref={canvasRef} style={{ display:"none" }} />
{snapUrl && showResult && (
<img src={snapUrl} alt="" style={{ position:"absolute",inset:0,width:"100%",height:"100%",objectFit:"cover",zIndex:2,filter:"blur(8px) brightness(.45)",transform:facing==="user"?"scaleX(-1)":"none" }} />
)}
{/* LOADING */}
{phase === PHASE.LOADING && (
<div style={{ position:"absolute",inset:0,zIndex:60,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",background:"radial-gradient(ellipse at 50% 30%,#1a1a1a,#0a0a0a 70%)" }}>
<div style={{ fontSize:72,marginBottom:20,animation:"emojiPop .6s cubic-bezier(.17,.67,.35,1.3) forwards" }}>π</div>
<h1 style={{ fontFamily:font,fontSize:48,color:"#fff",letterSpacing:4,margin:"0 0 24px",lineHeight:1 }}>SEEFOOD</h1>
<div style={{ width:36,height:36,border:"3px solid rgba(255,255,255,.1)",borderTopColor:"#ff6b35",borderRadius:"50%",animation:"spin .8s linear infinite",marginBottom:20 }} />
<p style={{ color:"rgba(255,255,255,.4)",fontSize:13,letterSpacing:2,textAlign:"center",padding:"0 40px" }}>{loadMsg}</p>
</div>
)}
{/* WELCOME */}
{phase === PHASE.WELCOME && (
<div style={{ position:"absolute",inset:0,zIndex:50,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",background:"radial-gradient(ellipse at 50% 40%,#1e1e1e,#0a0a0a 70%)" }}>
<div style={{ fontSize:96,marginBottom:8,animation:"emojiPop .6s cubic-bezier(.17,.67,.35,1.3) forwards" }}>π</div>
<h1 style={{ fontFamily:font,fontSize:56,color:"#fff",letterSpacing:4,margin:0,lineHeight:1 }}>SEEFOOD</h1>
<p style={{ color:"rgba(255,255,255,.45)",fontSize:14,letterSpacing:3,marginTop:6,fontWeight:500 }}>HOT DOG IDENTIFICATION</p>
<div style={{ marginTop:16,padding:"6px 16px",borderRadius:20,background:"rgba(0,200,100,.15)",border:"1px solid rgba(0,200,100,.3)",display:"flex",alignItems:"center",gap:6 }}>
<div style={{ width:6,height:6,borderRadius:"50%",background:"#00c864" }} />
<span style={{ color:"rgba(0,200,100,.8)",fontSize:11,fontWeight:700,letterSpacing:2 }}>ON-DEVICE AI β’ NO API KEY</span>
</div>
<div style={{ width:40,height:3,background:"linear-gradient(90deg,#ff4136,#ff851b)",borderRadius:2,margin:"24px 0 32px" }} />
<button onClick={() => openCamera(facing)} style={{ background:"linear-gradient(135deg,#ff4136,#ff6b35)",border:"none",borderRadius:60,padding:"18px 52px",color:"#fff",fontFamily:font,fontSize:22,letterSpacing:4,cursor:"pointer",boxShadow:"0 8px 40px rgba(255,65,54,.4)",animation:"glowPulse 2s ease-in-out infinite" }}>
OPEN CAMERA
</button>
<p style={{ color:"rgba(255,255,255,.2)",fontSize:11,marginTop:28,letterSpacing:1,textAlign:"center",padding:"0 40px" }}>RUNS 100% IN YOUR BROWSER</p>
</div>
)}
{/* LIVE */}
{phase === PHASE.LIVE && (
<>
<div style={{ position:"absolute",top:0,left:0,right:0,zIndex:10,display:"flex",alignItems:"center",justifyContent:"space-between",padding:"52px 20px 10px" }}>
<div style={{ display:"flex",alignItems:"center",gap:8 }}>
<span style={{ fontSize:24 }}>π</span>
<span style={{ fontFamily:font,color:"#fff",fontSize:22,letterSpacing:2,textShadow:"0 2px 16px rgba(0,0,0,.7)" }}>SEEFOOD</span>
</div>
<button onClick={flip} style={{ width:44,height:44,borderRadius:"50%",background:"rgba(0,0,0,.35)",backdropFilter:"blur(12px)",border:"1px solid rgba(255,255,255,.15)",color:"#fff",fontSize:20,cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center" }}>β»</button>
</div>
<div style={{ position:"absolute",inset:0,zIndex:5,display:"flex",alignItems:"center",justifyContent:"center",pointerEvents:"none" }}>
<div style={{ width:200,height:200,position:"relative",border:"2px solid rgba(255,255,255,.12)",borderRadius:24 }}>
{[[0,0],[1,0],[0,1],[1,1]].map(([x,y],i) => (
<div key={i} style={{ position:"absolute",...(y?{bottom:-1}:{top:-1}),...(x?{right:-1}:{left:-1}),width:22,height:22,borderTop:y?"none":"3px solid rgba(255,255,255,.55)",borderBottom:y?"3px solid rgba(255,255,255,.55)":"none",borderLeft:x?"none":"3px solid rgba(255,255,255,.55)",borderRight:x?"3px solid rgba(255,255,255,.55)":"none",borderRadius:`${!x&&!y?8:0}px ${x&&!y?8:0}px ${x&&y?8:0}px ${!x&&y?8:0}px` }} />
))}
</div>
</div>
<div style={{ position:"absolute",top:100,left:"50%",transform:"translateX(-50%)",zIndex:10,padding:"4px 12px",borderRadius:12,background:"rgba(0,0,0,.5)",backdropFilter:"blur(8px)",display:"flex",alignItems:"center",gap:5 }}>
<div style={{ width:5,height:5,borderRadius:"50%",background:"#00c864" }} />
<span style={{ color:"rgba(255,255,255,.5)",fontSize:10,fontWeight:600,letterSpacing:1.5 }}>ON-DEVICE</span>
</div>
<div style={{ position:"absolute",bottom:0,left:0,right:0,zIndex:10,display:"flex",flexDirection:"column",alignItems:"center",paddingBottom:48 }}>
<button onClick={snap} style={{ width:78,height:78,borderRadius:"50%",background:"linear-gradient(135deg,#ff4136,#ff6b35)",border:"5px solid rgba(255,255,255,.85)",boxShadow:"0 0 0 5px rgba(255,65,54,.25),0 6px 30px rgba(0,0,0,.4)",cursor:"pointer",position:"relative",transition:"transform .12s" }}
onMouseDown={e=>e.currentTarget.style.transform="scale(.88)"}
onMouseUp={e=>e.currentTarget.style.transform="scale(1)"}
onTouchStart={e=>e.currentTarget.style.transform="scale(.88)"}
onTouchEnd={e=>e.currentTarget.style.transform="scale(1)"}>
<div style={{ position:"absolute",inset:5,borderRadius:"50%",border:"2px solid rgba(255,255,255,.35)" }} />
</button>
<span style={{ fontFamily:font,color:"rgba(255,255,255,.6)",fontSize:14,letterSpacing:5,marginTop:14,textShadow:"0 1px 6px rgba(0,0,0,.5)" }}>IDENTIFY</span>
</div>
</>
)}
{/* SCANNING */}
{phase === PHASE.SCANNING && (
<div style={{ position:"absolute",inset:0,zIndex:30 }}>
<div style={{ position:"absolute",inset:0,background:"rgba(0,0,0,.4)" }} />
<div style={{ position:"absolute",left:0,right:0,height:4,background:"linear-gradient(90deg,transparent 0%,#00ff88 20%,#00ffdd 50%,#00ff88 80%,transparent 100%)",boxShadow:"0 0 24px 4px #00ff88,0 0 80px 8px rgba(0,255,136,.3)",animation:"sweep 1.1s ease-in-out infinite" }} />
<div style={{ position:"absolute",inset:0,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center" }}>
<div style={{ fontFamily:font,color:"#00ff88",fontSize:28,letterSpacing:8,textShadow:"0 0 30px #00ff88,0 0 60px rgba(0,255,136,.4)",animation:"pulseScan .9s ease-in-out infinite" }}>ANALYZING</div>
<div style={{ display:"flex",gap:8,marginTop:16 }}>
{[0,1,2].map(i=><div key={i} style={{ width:8,height:8,borderRadius:"50%",background:"#00ff88",animation:`pulseScan .9s ${i*.2}s ease-in-out infinite` }} />)}
</div>
</div>
</div>
)}
{/* RESULT */}
{showResult && (
<div onClick={again} style={{ position:"absolute",inset:0,zIndex:40,cursor:"pointer",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",background:isHot?"linear-gradient(160deg,rgba(16,190,50,.92),rgba(0,140,30,.92))":"linear-gradient(160deg,rgba(235,30,40,.92),rgba(160,10,20,.92))" }}>
<div style={{ position:"absolute",inset:10,borderRadius:24,border:`3px solid ${isHot?"rgba(160,255,200,.5)":"rgba(255,160,160,.5)"}`,animation:"borderPulse 1s ease-in-out infinite",pointerEvents:"none" }} />
{Array.from({length:8}).map((_,i)=><div key={i} style={{ position:"absolute",bottom:-20,left:`${10+Math.random()*80}%`,fontSize:isHot?28:18,animation:`floatUp ${3+Math.random()*3}s ${Math.random()*2}s linear infinite`,pointerEvents:"none" }}>{isHot?"π":"β"}</div>)}
<div style={{ position:"absolute",width:100,height:100,borderRadius:"50%",border:"3px solid rgba(255,255,255,.25)",animation:"ripple 1.2s ease-out forwards",pointerEvents:"none" }} />
<div style={{ fontSize:90,animation:"emojiPop .5s cubic-bezier(.17,.67,.35,1.3) forwards",marginBottom:4 }}>{isHot?"π":"π«"}</div>
<div style={{ fontFamily:font,fontSize:64,color:"#fff",textAlign:"center",letterSpacing:4,lineHeight:1,textShadow:"0 4px 40px rgba(0,0,0,.35)",animation:"slamResult .45s cubic-bezier(.17,.67,.35,1.3) forwards" }}>
{isHot?"HOT DOG":<>NOT<br/>HOT DOG</>}
</div>
{confidence > 0 && (
<div style={{ marginTop:16,padding:"5px 14px",borderRadius:12,background:"rgba(0,0,0,.3)",color:"rgba(255,255,255,.6)",fontSize:12,fontWeight:600,letterSpacing:2,animation:"driftUp .4s .3s both" }}>
{(confidence*100).toFixed(1)}% CONFIDENCE
</div>
)}
<div style={{ fontFamily:body,color:"rgba(255,255,255,.7)",fontSize:13,letterSpacing:3,fontWeight:500,marginTop:24,animation:"driftUp .5s .35s both" }}>TAP TO SCAN AGAIN</div>
</div>
)}
{/* ERROR */}
{phase === PHASE.ERROR && (
<div onClick={()=>{setErrMsg("");if(modelRef.current)setPhase(PHASE.LIVE);else window.location.reload();}} style={{ position:"absolute",inset:0,zIndex:50,cursor:"pointer",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",background:"rgba(10,10,10,.94)" }}>
<div style={{ fontSize:56,marginBottom:16 }}>β οΈ</div>
<div style={{ fontFamily:font,color:"#ff6b6b",fontSize:24,letterSpacing:3,marginBottom:12 }}>ERROR</div>
<p style={{ color:"rgba(255,255,255,.55)",fontSize:15,textAlign:"center",padding:"0 36px",maxWidth:340,lineHeight:1.5 }}>{errMsg}</p>
<div style={{ color:"rgba(255,255,255,.3)",fontSize:12,letterSpacing:3,marginTop:28,animation:"subtleBob 2s ease-in-out infinite" }}>TAP TO RETRY</div>
</div>
)}
</div>
);
}
```
[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 JSX 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/GigaChad/seefood */