0
TOOLS#a3740101
Daily Bloom
@Owner·deposited 1mo ago·updated 1mo ago·34 views
TOOLS#a3740101
Daily Bloom
OW
@Owner
34Views
0Comments
0Forks
0Saves
SHARE · REMIX
Daily Bloom — a HTML tools widget by @Owner.
CONTROLS
flowersdailyinspirationquotescalm
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: "Daily Bloom" by @Owner]
Source: https://itjustvibes.com/Owner/daily-bloom
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>Daily Bloom</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,600;1,9..144,400;1,9..144,500&family=Spline+Sans:wght@400;500;600&display=swap" rel="stylesheet" />
<style>
:root{
--paper:#FCFAF5;
--bg:#F4F1EA;
--tint:#E7E2D4;
--accent:#9a8f78;
--ink:#3a3527;
}
*{ box-sizing:border-box; }
html,body{ height:100%; margin:0; }
body{
min-height:100%;
display:flex;
align-items:center;
justify-content:center;
padding:28px 18px;
font-family:"Spline Sans", system-ui, sans-serif;
color:var(--ink);
background:
radial-gradient(120% 90% at 50% 8%, var(--tint) 0%, var(--bg) 58%, var(--bg) 100%);
transition: background 900ms ease, color 900ms ease;
}
.plate{
width:min(560px, 100%);
background:var(--paper);
border-radius:10px;
padding:30px 34px 24px;
position:relative;
box-shadow:
0 1px 0 rgba(255,255,255,.6) inset,
0 18px 50px -22px rgba(40,34,20,.45),
0 2px 10px -4px rgba(40,34,20,.18);
transition: box-shadow 900ms ease;
}
.plate::before{
content:"";
position:absolute; inset:9px;
border:1px solid color-mix(in srgb, var(--accent) 38%, transparent);
border-radius:5px;
pointer-events:none;
transition:border-color 900ms ease;
}
.topline{
display:flex;
justify-content:space-between;
align-items:baseline;
font-size:11.5px;
letter-spacing:.18em;
text-transform:uppercase;
color:color-mix(in srgb, var(--ink) 62%, transparent);
font-weight:500;
}
.topline .no{ color:var(--accent); font-weight:600; }
.stage{
display:flex;
align-items:flex-end;
justify-content:center;
height:262px;
margin:6px 0 2px;
}
.flower-wrap{
width:min(260px, 70vw);
height:auto;
filter: drop-shadow(0 14px 14px rgba(40,34,20,.14));
animation: float 9s ease-in-out infinite alternate;
transform-origin:center;
}
.flower-wrap svg{ display:block; width:100%; height:auto; overflow:visible; }
.petal{
transform-box: view-box;
transform-origin: 0px 0px;
animation: bloomIn 1s cubic-bezier(.18,.85,.25,1) backwards;
}
.core{
transform-box: view-box;
transform-origin: 0px 0px;
animation: coreIn .7s ease backwards;
animation-delay:.5s;
}
@keyframes bloomIn{
from{ transform: scale(.08) rotate(-14deg); opacity:0; }
60%{ opacity:1; }
to{ transform:none; opacity:1; }
}
@keyframes coreIn{ from{ transform:scale(.2); opacity:0; } to{ transform:none; opacity:1; } }
@keyframes float{
from{ transform: translateY(0) rotate(-1.6deg); }
to{ transform: translateY(-9px) rotate(1.6deg); }
}
.name{
font-family:"Fraunces", Georgia, serif;
font-optical-sizing:auto;
font-weight:600;
font-size:30px;
line-height:1.05;
margin:8px 0 2px;
text-align:center;
color:var(--ink);
}
.latin{
font-family:"Fraunces", Georgia, serif;
font-style:italic;
font-weight:400;
font-size:14.5px;
text-align:center;
color:color-mix(in srgb, var(--ink) 58%, transparent);
margin:0 0 4px;
}
.note{
text-align:center;
font-size:13px;
color:color-mix(in srgb, var(--ink) 64%, transparent);
max-width:40ch;
margin:4px auto 0;
line-height:1.5;
}
.rule{
display:flex; align-items:center; justify-content:center;
gap:10px; margin:20px auto 16px; max-width:260px;
}
.rule .line{ height:1px; flex:1; background:color-mix(in srgb, var(--accent) 45%, transparent); }
.rule .dot{ width:5px; height:5px; border-radius:50%; background:var(--accent); transition:background 900ms ease; }
.quote{
font-family:"Fraunces", Georgia, serif;
font-style:italic;
font-weight:400;
font-size:21px;
line-height:1.4;
text-align:center;
color:var(--ink);
margin:0 auto;
max-width:34ch;
transition: opacity .45s ease;
}
.who{
text-align:center;
margin:14px 0 0;
font-size:12px;
letter-spacing:.12em;
text-transform:uppercase;
font-weight:500;
color:var(--accent);
transition:color 900ms ease;
}
.foot{
display:flex;
justify-content:space-between;
align-items:center;
margin-top:24px;
padding-top:14px;
border-top:1px solid color-mix(in srgb, var(--ink) 10%, transparent);
}
.foot small{
font-size:11px;
letter-spacing:.04em;
color:color-mix(in srgb, var(--ink) 50%, transparent);
}
.another{
appearance:none; border:0; background:transparent; cursor:pointer;
font-family:"Spline Sans", sans-serif;
font-size:12px; letter-spacing:.1em; text-transform:uppercase; font-weight:600;
color:var(--accent);
display:inline-flex; align-items:center; gap:7px;
padding:6px 4px; border-radius:6px;
transition: color .25s ease, opacity .25s ease;
}
.another:hover{ opacity:.7; }
.another:focus-visible{ outline:2px solid var(--accent); outline-offset:3px; }
.another svg{ width:14px; height:14px; }
@media (max-width:480px){
.plate{ padding:24px 20px 18px; }
.stage{ height:226px; }
.name{ font-size:26px; }
.quote{ font-size:19px; }
}
@media (prefers-reduced-motion: reduce){
.flower-wrap{ animation:none; }
.petal,.core{ animation:none; }
}
</style>
</head>
<body>
<main class="plate" role="main" aria-label="Today's flower and quote">
<div class="topline">
<span id="date">—</span>
<span class="no" id="specimen">No. —</span>
</div>
<div class="stage">
<div class="flower-wrap" id="flower" aria-hidden="true"></div>
</div>
<h1 class="name" id="name">Loading…</h1>
<p class="latin" id="latin"></p>
<p class="note" id="flnote"></p>
<div class="rule" aria-hidden="true">
<span class="line"></span><span class="dot"></span><span class="line"></span>
</div>
<blockquote class="quote" id="quote"></blockquote>
<p class="who" id="who"></p>
<div class="foot">
<small>A fresh bloom every day</small>
<button class="another" id="another" type="button" aria-label="Show a different flower and quote">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/></svg>
Another
</button>
</div>
</main>
<script>
const FLOWERS = [
{ name:"Sunflower", latin:"Helianthus annuus", note:"Turns its face to follow the sun across the sky.",
rings:[{count:22,len:96,wid:24,shape:"pointed",rot:0,light:"#FFD75A",dark:"#F2A100"}],
center:{r:40,fill:"#5A3A1A",dot:"#3A2410",dots:36},
theme:{paper:"#FCF8EC",bg:"#F4ECD2",tint:"#FBE6A2",accent:"#D88400",ink:"#4A3A12"} },
{ name:"Cosmos", latin:"Cosmos bipinnatus", note:"Loose, airy petals that drift on the slightest breeze.",
rings:[{count:8,len:92,wid:40,shape:"round",rot:0,light:"#FFDDEA",dark:"#EF6FA2"}],
center:{r:22,fill:"#F4C740",dot:"#C99500",dots:10},
theme:{paper:"#FCF4F7",bg:"#F4E4EC",tint:"#F8CBDD",accent:"#DD5B8C",ink:"#552742"} },
{ name:"Cornflower", latin:"Centaurea cyanus", note:"A wild blue once tucked into harvest fields.",
rings:[{count:13,len:90,wid:22,shape:"pointed",rot:0,light:"#7AB0FF",dark:"#2E50C8"}],
center:{r:18,fill:"#283A86",dot:"#1B2A66",dots:8},
theme:{paper:"#F6F8FE",bg:"#E6ECFA",tint:"#C2D4F6",accent:"#3155C8",ink:"#22315F"} },
{ name:"Poppy", latin:"Papaver rhoeas", note:"Tissue-thin petals, vivid as a struck match.",
rings:[{count:5,len:94,wid:62,shape:"round",rot:18,light:"#FF6F5C",dark:"#D62828"}],
center:{r:24,fill:"#241009",dot:"#0F0604",dots:14},
theme:{paper:"#FCF2F0",bg:"#F5E0DC",tint:"#F6B6AD",accent:"#D62828",ink:"#4A1610"} },
{ name:"Oxeye Daisy", latin:"Leucanthemum vulgare", note:"Simple, sunny, and impossibly cheerful.",
rings:[{count:24,len:92,wid:16,shape:"pointed",rot:0,light:"#FFFFFF",dark:"#ECEADD"}],
center:{r:30,fill:"#F2B705",dot:"#C98A00",dots:22},
theme:{paper:"#FAFBF5",bg:"#ECEEDF",tint:"#E2E7CC",accent:"#E0A800",ink:"#3F4632"} },
{ name:"Lotus", latin:"Nelumbo nucifera", note:"Rises clean and bright straight out of the mud.",
rings:[
{count:8,len:96,wid:34,shape:"pointed",rot:0,light:"#FFE4ED",dark:"#F58FB4"},
{count:7,len:66,wid:28,shape:"pointed",rot:25,light:"#FFF0F5",dark:"#F4A9C6"}],
center:{r:16,fill:"#F4D35E",dot:"#C99A1E",dots:7},
theme:{paper:"#FCF4F8",bg:"#F3E2EC",tint:"#F6CFE0",accent:"#E783A9",ink:"#522A3D"} },
{ name:"Forget-me-not", latin:"Myosotis sylvatica", note:"Tiny, true-blue, and easy to love.",
rings:[{count:5,len:78,wid:48,shape:"round",rot:0,light:"#CFE0FF",dark:"#5C97E8"}],
center:{r:20,fill:"#FFE08A",dot:"#E0A800",dots:8},
theme:{paper:"#F7FAFE",bg:"#E7EFFB",tint:"#CBDEF7",accent:"#5C8FE0",ink:"#2A405F"} },
{ name:"Marigold", latin:"Tagetes erecta", note:"Densely packed petals that glow like embers.",
rings:[
{count:14,len:92,wid:22,shape:"pointed",rot:0,light:"#FFC24D",dark:"#E8650A"},
{count:13,len:70,wid:20,shape:"pointed",rot:13,light:"#FFD37A",dark:"#EE7A12"},
{count:11,len:48,wid:18,shape:"pointed",rot:8,light:"#FFE0A0",dark:"#F09020"}],
center:{r:14,fill:"#C9560A",dot:"#8A3A06",dots:6},
theme:{paper:"#FCF6EC",bg:"#F4E6D4",tint:"#F8CE97",accent:"#E8650A",ink:"#5A2E0C"} },
{ name:"Anemone", latin:"Anemone coronaria", note:"Bold petals around a deep, velvety eye.",
rings:[{count:7,len:92,wid:46,shape:"round",rot:0,light:"#CDB0FF",dark:"#7B4BE0"}],
center:{r:32,fill:"#1E1230",dot:"#0E0820",dots:30},
theme:{paper:"#F8F4FD",bg:"#EDE5FA",tint:"#DCC9F7",accent:"#7B4BE0",ink:"#2E1C4A"} },
{ name:"Cherry Blossom", latin:"Prunus serrulata", note:"A brief, gentle pink that signals spring.",
rings:[{count:5,len:88,wid:50,shape:"notched",rot:18,light:"#FFEDF3",dark:"#F7B8CE"}],
center:{r:14,fill:"#E08AB0",dot:"#C25C8A",dots:9},
theme:{paper:"#FCF6F9",bg:"#F5E6EE",tint:"#F7D9E5",accent:"#EE8FB4",ink:"#5A3245"} },
{ name:"Dahlia", latin:"Dahlia pinnata", note:"Layer upon layer, geometry in full bloom.",
rings:[
{count:12,len:94,wid:24,shape:"pointed",rot:0,light:"#FF95C6",dark:"#C71F76"},
{count:11,len:72,wid:22,shape:"pointed",rot:15,light:"#FFA9D2",dark:"#D2348A"},
{count:9,len:50,wid:20,shape:"pointed",rot:9,light:"#FFC1E0",dark:"#E04F9E"}],
center:{r:12,fill:"#8A1454",dot:"#5E0E3A",dots:6},
theme:{paper:"#FCF2F8",bg:"#F4E2EE",tint:"#F4C0DC",accent:"#C71F76",ink:"#4A0F30"} },
{ name:"Aster", latin:"Symphyotrichum", note:"A starry burst of slender lavender rays.",
rings:[{count:28,len:90,wid:13,shape:"pointed",rot:0,light:"#DEC9FF",dark:"#9B6FE0"}],
center:{r:20,fill:"#F2C84B",dot:"#C99A1E",dots:14},
theme:{paper:"#F8F5FD",bg:"#EEE7FA",tint:"#DDD0F5",accent:"#9B6FE0",ink:"#322052"} },
{ name:"Buttercup", latin:"Ranunculus acris", note:"Five glossy petals that catch the light.",
rings:[{count:5,len:88,wid:56,shape:"round",rot:18,light:"#FFEA85",dark:"#F4B400"}],
center:{r:18,fill:"#E89A00",dot:"#B57600",dots:9},
theme:{paper:"#FCF9EC",bg:"#F4EDD2",tint:"#F6E59A",accent:"#E5A200",ink:"#5A4410"} },
{ name:"Periwinkle", latin:"Vinca minor", note:"A calm violet-blue with a pale, bright eye.",
rings:[{count:5,len:90,wid:40,shape:"pointed",rot:34,light:"#B6C4FF",dark:"#5C5CE0"}],
center:{r:14,fill:"#FFFFFF",dot:"#C9C0FF",dots:7},
theme:{paper:"#F7F8FE",bg:"#E8EAFB",tint:"#CBD0F7",accent:"#5C5CE0",ink:"#2A2A5F"} }
];
const QUOTES = [
["The journey of a thousand miles begins with one step.","Lao Tzu"],
["What we think, we become.","Buddha"],
["Well done is better than well said.","Benjamin Franklin"],
["Fall seven times, stand up eight.","Japanese proverb"],
["To live is the rarest thing in the world.","Oscar Wilde"],
["He who has a why can bear almost any how.","Friedrich Nietzsche"],
["Whatever you are, be a good one.","Abraham Lincoln"],
["We suffer more in imagination than in reality.","Seneca"],
["The wound is the place where the light enters you.","Rumi"],
["Knowing yourself is the beginning of all wisdom.","Aristotle"],
["Quality is not an act, it is a habit.","Aristotle"],
["Do what you can, with what you have, where you are.","Theodore Roosevelt"],
["To begin is half the work.","Ausonius"],
["Energy and persistence conquer all things.","Benjamin Franklin"],
["Life is really simple, but we insist on making it complicated.","Confucius"],
["Our greatest glory is in rising every time we fall.","Confucius"],
["Wherever you go, go with all your heart.","Confucius"],
["The man who moves a mountain begins by carrying small stones.","Confucius"],
["It does not matter how slowly you go, so long as you do not stop.","Confucius"],
["The happiness of your life depends on the quality of your thoughts.","Marcus Aurelius"],
["Waste no more time arguing what a good person should be. Be one.","Marcus Aurelius"],
["You have power over your mind — not outside events.","Marcus Aurelius"],
["Very little is needed to make a happy life.","Marcus Aurelius"],
["Difficulties strengthen the mind, as labor does the body.","Seneca"],
["Luck is what happens when preparation meets opportunity.","Seneca"],
["While we wait for life, life passes.","Seneca"],
["What you do speaks so loudly I cannot hear what you say.","Ralph Waldo Emerson"],
["Always do what you are afraid to do.","Ralph Waldo Emerson"],
["The earth laughs in flowers.","Ralph Waldo Emerson"],
["Adopt the pace of nature: her secret is patience.","Ralph Waldo Emerson"],
["Hitch your wagon to a star.","Ralph Waldo Emerson"],
["Go confidently in the direction of your dreams.","Henry David Thoreau"],
["It's not what you look at that matters, it's what you see.","Henry David Thoreau"],
["Live the life you have imagined.","Henry David Thoreau"],
["Keep your face always toward the sunshine.","Walt Whitman"],
["We are all in the gutter, but some of us are looking at the stars.","Oscar Wilde"],
["Be yourself; everyone else is already taken.","Oscar Wilde"],
["Do one thing every day that scares you.","Eleanor Roosevelt"],
["No one can make you feel inferior without your consent.","Eleanor Roosevelt"],
["The secret of getting ahead is getting started.","Mark Twain"],
["Courage is resistance to fear, mastery of fear.","Mark Twain"],
["Believe you can and you're halfway there.","Theodore Roosevelt"],
["Comparison is the thief of joy.","Theodore Roosevelt"],
["Optimism is the faith that leads to achievement.","Helen Keller"],
["Where flowers bloom, so does hope.","Lady Bird Johnson"],
["A flower does not compete with the flower beside it. It just blooms.","Zen proverb"],
["Bloom where you are planted.","Proverb"]
];
function rot(p, deg){
const a = deg * Math.PI/180, c=Math.cos(a), s=Math.sin(a);
return [p[0]*c - p[1]*s, p[0]*s + p[1]*c];
}
function petalCmds(shape, L, w){
if(shape==="pointed"){
return [["M",[0,0]],["C",[w*0.9,-0.2*L],[w*0.32,-0.92*L],[0,-L]],
["C",[-w*0.32,-0.92*L],[-w*0.9,-0.2*L],[0,0]],["Z"]];
}
if(shape==="notched"){
return [["M",[0,0]],
["C",[w,-0.18*L],[w,-0.72*L],[w*0.42,-0.96*L]],
["C",[w*0.2,-1.02*L],[w*0.06,-0.9*L],[0,-0.82*L]],
["C",[-w*0.06,-0.9*L],[-w*0.2,-1.02*L],[-w*0.42,-0.96*L]],
["C",[-w,-0.72*L],[-w,-0.18*L],[0,0]],["Z"]];
}
return [["M",[0,0]],["C",[w,-0.15*L],[w,-0.85*L],[0,-L]],
["C",[-w,-0.85*L],[-w,-0.15*L],[0,0]],["Z"]];
}
function petalPath(shape, L, w, deg){
const cmds = petalCmds(shape, L, w);
return cmds.map(c=>{
if(c[0]==="Z") return "Z";
const pts = c.slice(1).map(p=>{ const r=rot(p,deg); return r[0].toFixed(2)+","+r[1].toFixed(2); });
return c[0]+pts.join(" ");
}).join(" ");
}
function centerDots(cx, r, n, color){
let out="";
if(n<=0) return out;
out += `<circle cx="0" cy="0" r="${(r*0.46).toFixed(1)}" fill="${color}" opacity="0.45"/>`;
for(let i=0;i<n;i++){
const ang = i * 137.5 * Math.PI/180;
const rad = r*0.82*Math.sqrt(i/n);
const x=(Math.cos(ang)*rad).toFixed(2), y=(Math.sin(ang)*rad).toFixed(2);
const dr=(1.3 + r*0.05*(1 - i/n)).toFixed(2);
out += `<circle cx="${x}" cy="${y}" r="${dr}" fill="${color}"/>`;
}
return out;
}
function renderFlower(f){
let defs="", body="", delay=0;
f.rings.forEach((ring, ri)=>{
const gid = `g${ri}`;
defs += `<radialGradient id="${gid}" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="${ring.len}">
<stop offset="0%" stop-color="${ring.light}"/>
<stop offset="100%" stop-color="${ring.dark}"/></radialGradient>`;
for(let i=0;i<ring.count;i++){
const deg = (i*360/ring.count) + (ring.rot||0);
const d = petalPath(ring.shape, ring.len, ring.wid, deg);
body += `<path class="petal" d="${d}" fill="url(#${gid})"
stroke="rgba(0,0,0,0.05)" stroke-width="0.6"
style="animation-delay:${delay.toFixed(2)}s"/>`;
delay += 0.022;
}
});
const c=f.center;
body += `<g class="core"><circle cx="0" cy="0" r="${c.r}" fill="${c.fill}"/>${centerDots(0,c.r,c.dots,c.dot)}</g>`;
return `<svg viewBox="-135 -135 270 270" role="img" aria-label="${f.name} illustration">
<defs>${defs}</defs>${body}</svg>`;
}
function todayParts(){
const now = new Date();
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const dayNum = Math.floor(midnight.getTime()/86400000);
const startYear = new Date(now.getFullYear(),0,0);
const doy = Math.floor((midnight - startYear)/86400000);
const dateStr = now.toLocaleDateString(undefined,{weekday:"long",month:"long",day:"numeric"});
return { dayNum, doy, dateStr };
}
const F = FLOWERS.length, Q = QUOTES.length;
let cur = { flower:0, quote:0 };
async function sget(k){ try{ if(!window.storage) return null; const r=await window.storage.get(k,false); return r?r.value:null; }catch(e){ return null; } }
async function sset(k,v){ try{ if(window.storage) await window.storage.set(k,v,false); }catch(e){} }
function paint(){
const f = FLOWERS[cur.flower], q = QUOTES[cur.quote];
const t = f.theme;
const root = document.documentElement.style;
root.setProperty("--paper", t.paper);
root.setProperty("--bg", t.bg);
root.setProperty("--tint", t.tint);
root.setProperty("--accent", t.accent);
root.setProperty("--ink", t.ink);
document.getElementById("flower").innerHTML = renderFlower(f);
document.getElementById("name").textContent = f.name;
document.getElementById("latin").textContent = f.latin;
document.getElementById("flnote").textContent = f.note;
document.getElementById("quote").textContent = "\u201C" + q[0] + "\u201D";
document.getElementById("who").textContent = q[1];
}
async function init(){
const { dayNum, doy, dateStr } = todayParts();
document.getElementById("date").textContent = dateStr;
document.getElementById("specimen").textContent = "No. " + String(doy).padStart(3,"0");
cur.flower = ((dayNum % F) + F) % F;
cur.quote = ((dayNum*8 % Q) + Q) % Q;
const override = await sget("override");
if(override && override.day === dayNum){
cur.flower = override.flower;
cur.quote = override.quote;
}
paint();
}
async function another(){
const { dayNum } = todayParts();
cur.flower = (cur.flower + 1 + Math.floor(Math.random()*(F-1))) % F;
cur.quote = (cur.quote + 1 + Math.floor(Math.random()*(Q-1))) % Q;
paint();
await sset("override", { day: dayNum, flower: cur.flower, quote: cur.quote });
}
document.getElementById("another").addEventListener("click", another);
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/Owner/daily-bloom */