0
USELESS#ed1c9d0b
Surprise! π A Pink OtterBox is Spinning Your Way
@OwnerΒ·deposited 3d agoΒ·updated 3d agoΒ·17 views
USELESS#ed1c9d0b
Surprise! π A Pink OtterBox is Spinning Your Way
OW
@Owner
17Views
0Comments
0Forks
0Saves
SHARE Β· REMIX
Surprise! π A Pink OtterBox is Spinning Your Way β a HTML useless widget by @Owner.
CONTROLS
animationrandomformclock
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: "Surprise! π A Pink OtterBox is Spinning Your Way" by @Owner]
Source: https://itjustvibes.com/Owner/surprise-a-pink-otterbox-is-spinning-your-way
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>Surprise! π A Pink OtterBox is Spinning Your Way</title>
<style>
:root{
--pink:#ff5c9e;
--pink-deep:#e83e86;
--pink-soft:#ffd3e6;
--ink:#2a1420;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
background:
radial-gradient(1200px 600px at 50% -10%,#fff0f7 0%,#ffe3f0 40%,#ffd0e6 100%);
color:var(--ink);
min-height:100%;
overflow-x:hidden;
display:flex;
flex-direction:column;
align-items:center;
text-align:center;
}
/* floating confetti hearts */
.sky{position:fixed;inset:0;pointer-events:none;overflow:hidden;z-index:0}
.sky span{
position:absolute;top:-40px;font-size:20px;opacity:.7;
animation:fall linear infinite;
}
@keyframes fall{
0%{transform:translateY(-40px) rotate(0)}
100%{transform:translateY(110vh) rotate(360deg)}
}
header{position:relative;z-index:2;padding:48px 20px 8px}
.kicker{
letter-spacing:.35em;text-transform:uppercase;font-size:12px;
color:var(--pink-deep);font-weight:800;
}
h1{
font-size:clamp(28px,6vw,54px);
line-height:1.05;margin:10px 0 6px;
background:linear-gradient(90deg,var(--pink-deep),var(--pink));
-webkit-background-clip:text;background-clip:text;color:transparent;
}
.sub{font-size:clamp(15px,2.4vw,19px);color:#7a4159;max-width:640px;margin:0 auto}
/* stage + spinning zooming iPad */
.stage{
position:relative;z-index:1;
width:min(90vw,520px);height:min(90vw,520px);max-height:56vh;
margin:10px auto 0;
display:flex;align-items:center;justify-content:center;
perspective:1400px;
}
.halo{
position:absolute;width:70%;height:70%;border-radius:50%;
background:radial-gradient(circle,rgba(255,92,158,.45),transparent 65%);
filter:blur(12px);
animation:pulse 2.4s ease-in-out infinite;
}
@keyframes pulse{0%,100%{transform:scale(.9);opacity:.6}50%{transform:scale(1.15);opacity:1}}
.zoom{
animation:zoom 4.5s ease-in-out infinite;
transform-style:preserve-3d;
}
@keyframes zoom{
0%,100%{transform:scale(.72)}
50%{transform:scale(1.05)}
}
.spin{
animation:spin 3.2s linear infinite;
transform-style:preserve-3d;
}
@keyframes spin{
0%{transform:rotateY(0deg)}
100%{transform:rotateY(360deg)}
}
/* the tablet built in CSS so nothing can break */
.ipad{
position:relative;width:230px;height:310px;
transform-style:preserve-3d;
}
.face{
position:absolute;inset:0;border-radius:26px;
backface-visibility:hidden;
box-shadow:0 30px 50px rgba(232,62,134,.35);
}
/* FRONT: pink rugged bezel + screen */
.front{
background:linear-gradient(145deg,var(--pink) 0%,var(--pink-deep) 100%);
padding:16px;
}
.bezel-ridges{
position:absolute;inset:0;border-radius:26px;
background:
repeating-linear-gradient(45deg,rgba(255,255,255,.10) 0 6px,transparent 6px 14px);
mix-blend-mode:overlay;
}
.screen{
position:absolute;inset:16px;border-radius:14px;
background:linear-gradient(160deg,#1b1b22,#33333f);
overflow:hidden;display:flex;align-items:center;justify-content:center;
}
.screen::after{
content:"π";font-size:64px;
filter:drop-shadow(0 4px 8px rgba(0,0,0,.4));
animation:bob 2.6s ease-in-out infinite;
}
@keyframes bob{0%,100%{transform:translateY(-6px)}50%{transform:translateY(6px)}}
.cam{position:absolute;top:50%;left:8px;transform:translateY(-50%);
width:7px;height:7px;border-radius:50%;background:#111;box-shadow:0 0 0 2px rgba(0,0,0,.25)}
/* BACK: kickstand + pencil slot */
.back{
transform:rotateY(180deg);
background:linear-gradient(145deg,var(--pink-deep),#c22e70);
}
.back .logo{
position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
width:54px;height:54px;border-radius:50%;
background:rgba(255,255,255,.9);
display:flex;align-items:center;justify-content:center;font-size:26px;
box-shadow:inset 0 -4px 8px rgba(0,0,0,.15);
}
.kickstand{
position:absolute;left:24px;right:24px;bottom:40px;height:70px;
border-radius:12px;
background:linear-gradient(180deg,rgba(255,255,255,.35),rgba(255,255,255,.12));
border:2px solid rgba(255,255,255,.55);
box-shadow:0 8px 14px rgba(0,0,0,.18);
}
.kickstand::after{
content:"BACKSTAND";position:absolute;inset:0;display:flex;
align-items:center;justify-content:center;font-size:10px;font-weight:800;
letter-spacing:.2em;color:rgba(255,255,255,.85);
}
.pencil{
position:absolute;top:22px;right:-6px;width:12px;height:150px;
border-radius:8px;
background:linear-gradient(180deg,#fff,#ffd9ea);
box-shadow:0 4px 10px rgba(0,0,0,.25);
border:2px solid rgba(255,255,255,.7);
}
.pencil::before{
content:"βοΈ";position:absolute;top:-24px;left:50%;transform:translateX(-50%);
font-size:16px;
}
/* feature chips */
.features{
position:relative;z-index:2;
display:flex;flex-wrap:wrap;gap:10px;justify-content:center;
margin:26px auto 6px;max-width:560px;padding:0 16px;
}
.chip{
background:#fff;border:2px solid var(--pink-soft);
color:var(--pink-deep);font-weight:700;font-size:14px;
padding:9px 15px;border-radius:999px;
box-shadow:0 6px 14px rgba(232,62,134,.12);
}
.card{
position:relative;z-index:2;background:#fff;border-radius:22px;
padding:22px 24px;margin:20px 16px 8px;max-width:520px;
box-shadow:0 18px 40px rgba(232,62,134,.18);
text-align:left;
}
.card h2{font-size:18px;color:var(--pink-deep);margin-bottom:8px}
.card p{font-size:15px;color:#6b3a51;line-height:1.5}
.fit{display:inline-block;margin-top:10px;font-size:13px;color:#9a6a80}
.actions{position:relative;z-index:2;display:flex;gap:12px;flex-wrap:wrap;
justify-content:center;margin:14px 0 40px}
button{
font:inherit;font-weight:800;cursor:pointer;border:none;border-radius:999px;
padding:14px 24px;transition:transform .15s ease, box-shadow .15s ease;
}
.primary{background:linear-gradient(90deg,var(--pink-deep),var(--pink));color:#fff;
box-shadow:0 10px 22px rgba(232,62,134,.4)}
.ghost{background:#fff;color:var(--pink-deep);border:2px solid var(--pink-soft)}
button:hover{transform:translateY(-2px)}
button:active{transform:translateY(0)}
.toast{
position:fixed;left:50%;bottom:26px;transform:translateX(-50%) translateY(120px);
background:var(--ink);color:#fff;padding:12px 20px;border-radius:999px;
font-size:14px;z-index:9;transition:transform .35s ease;box-shadow:0 8px 20px rgba(0,0,0,.3)}
.toast.show{transform:translateX(-50%) translateY(0)}
footer{position:relative;z-index:2;font-size:12px;color:#b07f92;padding:0 20px 30px}
@media (prefers-reduced-motion: reduce){
.spin,.zoom,.halo,.sky span,.screen::after{animation:none}
.zoom{transform:scale(.95)}
}
</style>
</head>
<body>
<div class="sky" id="sky"></div>
<header>
<div class="kicker">A gift is spinning your way</div>
<h1>It's a Pink OtterBox!</h1>
<p class="sub">For the shiny new <strong>iPad A16</strong> β rugged, ridiculously pink, and built to survive real life. Watch it spin. π</p>
</header>
<div class="stage">
<div class="halo"></div>
<div class="zoom">
<div class="spin">
<div class="ipad">
<div class="face front">
<div class="bezel-ridges"></div>
<div class="cam"></div>
<div class="screen"></div>
</div>
<div class="face back">
<div class="logo">π¦¦</div>
<div class="kickstand"></div>
<div class="pencil"></div>
</div>
</div>
</div>
</div>
</div>
<div class="features">
<span class="chip">πͺ Fold-out backstand</span>
<span class="chip">π‘οΈ Detachable front cover</span>
<span class="chip">βοΈ Apple Pencil slot</span>
<span class="chip">π§ Drop-proof shell</span>
</div>
<div class="card">
<h2>OtterBox β Pink, for iPad A16</h2>
<p>Multi-layer rugged protection with a built-in kickstand for hands-free viewing, a front screen cover that pops off and doubles as a stand, and a spot to stash the Apple Pencil. Basically a bodyguard for a very cute tablet.</p>
<span class="fit">Fits: iPad (A16) & iPad 10th Gen Β· Color: Pink</span>
</div>
<div class="actions">
<button class="primary" onclick="celebrate()">π Reveal the surprise</button>
<button class="ghost" onclick="share()">π Send this to someone</button>
</div>
<footer>A mock gift-reveal page Β· Confirm the exact pink model & Apple Pencil slot before you buy π</footer>
<div class="toast" id="toast">Link copied β go make someone's day! π</div>
<script>
// confetti hearts
const sky = document.getElementById('sky');
const emojis = ['π','π','β¨','π©·','π','π'];
for(let i=0;i<26;i++){
const s=document.createElement('span');
s.textContent=emojis[Math.floor(Math.random()*emojis.length)];
s.style.left=Math.random()*100+'vw';
s.style.animationDuration=(6+Math.random()*8)+'s';
s.style.animationDelay=(-Math.random()*10)+'s';
s.style.fontSize=(14+Math.random()*22)+'px';
sky.appendChild(s);
}
function celebrate(){
for(let i=0;i<40;i++){
const s=document.createElement('span');
s.textContent=emojis[Math.floor(Math.random()*emojis.length)];
s.style.position='fixed';
s.style.left='50%';s.style.top='40%';
s.style.zIndex=8;s.style.fontSize=(16+Math.random()*20)+'px';
s.style.pointerEvents='none';
document.body.appendChild(s);
const ang=Math.random()*Math.PI*2, dist=120+Math.random()*260;
s.animate([
{transform:'translate(-50%,-50%) scale(.3)',opacity:1},
{transform:`translate(${Math.cos(ang)*dist}px,${Math.sin(ang)*dist}px) scale(1.2)`,opacity:0}
],{duration:1100+Math.random()*500,easing:'cubic-bezier(.2,.7,.3,1)'})
.onfinish=()=>s.remove();
}
}
function share(){
const t=document.getElementById('toast');
const data={title:'A Pink OtterBox for the iPad A16 π',text:'Look what I found β a spinning pink iPad case!',url:location.href};
if(navigator.share){navigator.share(data).catch(()=>{});}
else if(navigator.clipboard){navigator.clipboard.writeText(location.href).catch(()=>{});}
t.classList.add('show');
setTimeout(()=>t.classList.remove('show'),2200);
}
</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/surprise-a-pink-otterbox-is-spinning-your-way */