0
GAMES#18dca369
Pokédex Trivia
@Owner·deposited 0mo ago·updated 3w ago·45 views
GAMES#18dca369
Pokédex Trivia
OW
@Owner
45Views
0Comments
0Forks
0Saves
SHARE · REMIX
Pokédex Trivia — a HTML games widget by @Owner.
CONTROLS
pokemontriviaquizleaderboardgame
No comments yet. Be the first!
✦ Remix with AI
SDK in this widget
vibes.onReadyvibes.savevibes.loadvibes.shared.setvibes.shared.getvibes.shared.onChange
- Global shared room: Room "leaderboard" is hardcoded — every viewer shares the same state.
Generated prompt
You are helping me modify a vibe-coded widget from itjustvibes.com.
[VIBE CODE: "Pokédex Trivia" by @Owner]
Source: https://itjustvibes.com/Owner/pokdex-trivia
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>Pokédex Trivia</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=Press+Start+2P&family=Nunito:wght@600;700;800;900&display=swap" rel="stylesheet">
<style>
:root{
--shell:#d4252b; --shell-d:#a51218; --shell-l:#ec3b41;
--lens:#3aa0ff; --lens-d:#1c6fd0;
--lcd:#cfe8b8; --lcd-d:#9bc77f; --lcd-edge:#2b3a1f;
--ink:#1c2516; --gold:#ffcb05; --blue:#2a75bb;
--bezel:#262b22;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{
font-family:'Nunito',system-ui,sans-serif;
background:
radial-gradient(circle at 30% 20%, #3a4a6b 0%, transparent 55%),
radial-gradient(circle at 80% 90%, #41204a 0%, transparent 55%),
#16181f;
min-height:100vh;display:flex;align-items:center;justify-content:center;
padding:14px;color:var(--ink);
}
.pokedex{
width:100%;max-width:460px;background:linear-gradient(160deg,var(--shell-l),var(--shell) 40%,var(--shell-d));
border-radius:18px 18px 22px 22px;padding:16px 16px 18px;
box-shadow:0 18px 40px rgba(0,0,0,.55), inset 0 2px 0 rgba(255,255,255,.25), inset 0 -6px 14px rgba(0,0,0,.3);
position:relative;border:2px solid var(--shell-d);
}
/* ---- top lens row ---- */
.top{display:flex;align-items:center;gap:12px;margin-bottom:14px;padding:2px 4px}
.lens{
width:54px;height:54px;border-radius:50%;flex:0 0 auto;
background:radial-gradient(circle at 32% 30%, #bfe3ff 0%, var(--lens) 38%, var(--lens-d) 100%);
border:4px solid #f4f4f4;box-shadow:0 0 0 3px var(--shell-d), inset 0 0 10px rgba(0,0,0,.4);
position:relative;
}
.lens::after{content:"";position:absolute;top:9px;left:11px;width:16px;height:13px;border-radius:50%;
background:rgba(255,255,255,.65);transform:rotate(-20deg)}
.leds{display:flex;gap:8px}
.led{width:13px;height:13px;border-radius:50%;border:2px solid rgba(0,0,0,.25)}
.led.r{background:#ff5a5a;animation:blink 1.4s infinite}
.led.y{background:var(--gold);animation:blink 1.4s .25s infinite}
.led.g{background:#67e06b;animation:blink 1.4s .5s infinite}
@keyframes blink{0%,100%{filter:brightness(.55)}50%{filter:brightness(1.4)}}
.brand{margin-left:auto;font-family:'Press Start 2P',monospace;font-size:9px;color:#fff;
text-shadow:1px 1px 0 var(--shell-d);letter-spacing:1px;text-align:right;line-height:1.5}
/* ---- screen ---- */
.screen-frame{
background:var(--bezel);border-radius:12px;padding:14px 12px;
box-shadow:inset 0 3px 8px rgba(0,0,0,.7);position:relative;
}
.screen-frame::before{content:"";position:absolute;top:6px;left:14px;width:7px;height:7px;border-radius:50%;
background:#ff5a5a;box-shadow:14px 0 0 #67e06b}
.screen{
background:linear-gradient(180deg,var(--lcd),var(--lcd-d));
border:3px solid var(--lcd-edge);border-radius:7px;margin-top:14px;
min-height:380px;padding:16px 15px;position:relative;overflow:hidden;
box-shadow:inset 0 0 0 2px rgba(255,255,255,.25);
}
.screen::after{content:"";position:absolute;inset:0;pointer-events:none;
background:repeating-linear-gradient(rgba(0,0,0,.04) 0 1px, transparent 1px 3px);mix-blend-mode:multiply}
.scr{position:relative;z-index:1}
/* ---- type/labels ---- */
.eyebrow{font-family:'Press Start 2P',monospace;font-size:8px;letter-spacing:1px;color:#4a5e38;text-transform:uppercase}
h1.title{font-family:'Press Start 2P',monospace;font-size:17px;line-height:1.5;color:var(--ink);
margin:6px 0 2px;text-shadow:1px 1px 0 rgba(255,255,255,.4)}
.sub{font-weight:700;font-size:13px;color:#3c4a2e;margin-bottom:12px}
/* ---- buttons ---- */
button{font-family:inherit;cursor:pointer;border:none}
.opt,.primary,.ghost,.chip{font-weight:800}
.primary{display:block;width:100%;background:var(--blue);color:#fff;font-size:15px;
padding:13px;border-radius:9px;box-shadow:0 4px 0 #1b4f82;transition:.08s;margin-top:4px}
.primary:active{transform:translateY(3px);box-shadow:0 1px 0 #1b4f82}
.primary.gold{background:var(--gold);color:#5b4500;box-shadow:0 4px 0 #b88f00}
.primary.gold:active{box-shadow:0 1px 0 #b88f00}
.ghost{background:rgba(255,255,255,.45);color:#33421f;font-size:13px;padding:9px;border-radius:8px;
border:2px solid rgba(43,58,31,.3);width:100%;margin-top:8px}
.row{display:flex;gap:8px}
.row .primary,.row .ghost{margin-top:0}
/* ---- start screen bits ---- */
.field{margin:10px 0}
label.lab{display:block;font-size:11px;font-weight:800;color:#43522f;margin-bottom:5px;text-transform:uppercase;letter-spacing:.5px}
input.name{width:100%;padding:11px;border-radius:8px;border:3px solid var(--lcd-edge);
background:#f3fbe9;font-family:inherit;font-weight:800;font-size:15px;color:var(--ink)}
input.name:focus{outline:none;border-color:var(--blue)}
.lenpick{display:flex;gap:7px}
.lenpick button{flex:1;padding:10px 0;border-radius:8px;font-weight:800;font-size:13px;
background:rgba(255,255,255,.4);border:2px solid rgba(43,58,31,.3);color:#3a481f}
.lenpick button.on{background:var(--blue);color:#fff;border-color:var(--blue)}
.stats{display:flex;gap:8px;margin:12px 0}
.stat{flex:1;background:rgba(255,255,255,.4);border-radius:8px;padding:8px 6px;text-align:center;border:1px solid rgba(43,58,31,.18)}
.stat b{display:block;font-size:18px;color:var(--ink);font-weight:900}
.stat span{font-size:9px;text-transform:uppercase;letter-spacing:.5px;color:#54643d;font-weight:800}
/* ---- HUD ---- */
.hud{display:flex;justify-content:space-between;align-items:center;font-weight:800;font-size:12px;color:#3c4a2e;margin-bottom:8px}
.hud .cat{font-family:'Press Start 2P',monospace;font-size:8px;background:var(--ink);color:var(--lcd);
padding:5px 7px;border-radius:5px}
.bar{height:7px;background:rgba(43,58,31,.2);border-radius:4px;overflow:hidden;margin-bottom:12px}
.bar i{display:block;height:100%;background:linear-gradient(90deg,var(--gold),#ff9b3d);transition:width .3s}
.q{font-weight:900;font-size:17px;line-height:1.35;color:var(--ink);margin-bottom:12px;min-height:46px}
.opt{display:block;width:100%;text-align:left;background:#f3fbe9;color:var(--ink);font-size:14px;
padding:12px 13px;border-radius:9px;border:3px solid var(--lcd-edge);margin-bottom:9px;transition:.1s;font-weight:700}
.opt:active{transform:scale(.99)}
.opt.sel{background:#dceaff;border-color:var(--blue)}
.opt.right{background:#bff0c4;border-color:#1f9a3a;color:#13561f}
.opt.wrong{background:#ffc9c9;border-color:#c62828;color:#7a1414}
.opt.dim{opacity:.55}
.opt .mk{float:right;font-weight:900;font-family:'Press Start 2P',monospace;font-size:11px;margin-left:8px}
.tf{display:flex;gap:10px}
.tf .opt{flex:1;text-align:center;font-size:16px;font-family:'Press Start 2P',monospace;padding:16px 8px}
/* ---- order question ---- */
.pool,.seq{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:10px;min-height:42px}
.seq{background:rgba(255,255,255,.35);border:2px dashed rgba(43,58,31,.35);border-radius:9px;padding:8px}
.seq:empty::before{content:"Tap items below in order";color:#6a784f;font-weight:700;font-size:12px}
.chip{background:#f3fbe9;border:3px solid var(--lcd-edge);border-radius:8px;padding:8px 11px;font-size:13px;color:var(--ink)}
.chip.in{background:var(--blue);color:#fff;border-color:var(--blue)}
.chip .n{font-family:'Press Start 2P',monospace;font-size:9px;margin-right:6px;opacity:.8}
/* ---- feedback ---- */
.fb{font-weight:900;font-size:14px;padding:10px 12px;border-radius:9px;margin:4px 0 10px;line-height:1.35}
.fb.ok{background:#bff0c4;color:#13561f}
.fb.no{background:#ffd2d2;color:#7a1414}
.fb small{display:block;font-weight:700;font-size:12px;margin-top:3px;opacity:.85}
/* ---- end screen ---- */
.score-big{font-family:'Press Start 2P',monospace;font-size:34px;color:var(--ink);text-align:center;margin:6px 0}
.score-big small{font-size:14px;color:#54643d}
.verdict{text-align:center;font-weight:900;font-size:16px;margin-bottom:4px}
.seedline{text-align:center;font-family:'Press Start 2P',monospace;font-size:8px;color:#5a6a40;margin:8px 0 14px}
/* ---- leaderboard ---- */
.lb{margin-top:6px}
.lb h3{font-family:'Press Start 2P',monospace;font-size:9px;color:#3c4a2e;margin-bottom:8px;display:flex;justify-content:space-between;align-items:center}
.lb .live{font-size:8px;color:#1f7a2e}
.lrow{display:flex;align-items:center;gap:8px;background:rgba(255,255,255,.4);border-radius:7px;
padding:7px 9px;margin-bottom:5px;font-weight:800;font-size:13px}
.lrow.me{background:#dceaff;outline:2px solid var(--blue)}
.lrow .rk{font-family:'Press Start 2P',monospace;font-size:9px;width:22px;color:#5a6a40}
.lrow .nm{flex:1;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lrow .sc{color:var(--blue);font-weight:900}
.lrow .ac{font-size:10px;color:#5a6a40;width:38px;text-align:right}
.empty{font-size:12px;color:#5a6a40;font-weight:700;text-align:center;padding:10px}
/* ---- controls strip ---- */
.controls{display:flex;align-items:center;justify-content:space-between;margin-top:14px;padding:0 6px}
.dpad{width:46px;height:46px;position:relative;opacity:.85}
.dpad i{position:absolute;background:#2b2f26;border-radius:3px}
.dpad i.h{width:46px;height:15px;top:15px}
.dpad i.v{height:46px;width:15px;left:15px}
.pills{display:flex;gap:7px}
.pill{width:34px;height:11px;border-radius:6px;background:rgba(0,0,0,.28)}
.ab{display:flex;gap:9px}
.ab i{width:26px;height:26px;border-radius:50%;background:#2b2f26;box-shadow:inset 0 -3px 4px rgba(0,0,0,.4)}
.ab i.a{background:#b8232b}.ab i.b{background:#7a1118}
.tiny{font-size:10px;color:#54643d;font-weight:700;text-align:center;margin-top:10px}
@media(max-width:380px){.screen{min-height:340px}h1.title{font-size:14px}.q{font-size:15px}}
@media(prefers-reduced-motion:reduce){.led{animation:none;filter:brightness(1.1)}.primary{transition:none}}
</style>
</head>
<body>
<div class="pokedex">
<div class="top">
<div class="lens"></div>
<div class="leds"><span class="led r"></span><span class="led y"></span><span class="led g"></span></div>
<div class="brand">POKéDEX<br>TRIVIA</div>
</div>
<div class="screen-frame">
<div class="screen"><div class="scr" id="screen"></div></div>
</div>
<div class="controls">
<div class="dpad"><i class="h"></i><i class="v"></i></div>
<div class="pills"><div class="pill"></div><div class="pill"></div></div>
<div class="ab"><i class="b"></i><i class="a"></i></div>
</div>
</div>
<script>
/* ===================== QUESTION BANK ===================== */
/* types: mc(single), tf(true/false), multi(select all), order(arrange) */
const BANK = [
// --- GAMES (multiple choice) ---
{c:"Games",t:"mc",q:"Which Pokémon is #001 in the National Pokédex?",o:["Bulbasaur","Charmander","Pikachu","Squirtle"],a:0},
{c:"Games",t:"mc",q:"Who is the Pokémon Professor of the Kanto region?",o:["Professor Oak","Professor Elm","Professor Birch","Professor Rowan"],a:0},
{c:"Games",t:"mc",q:"Which item is guaranteed to catch any wild Pokémon?",o:["Master Ball","Ultra Ball","Great Ball","Premier Ball"],a:0},
{c:"Games",t:"mc",q:"Which region is the setting of Pokémon Gold and Silver?",o:["Johto","Kanto","Hoenn","Sinnoh"],a:0},
{c:"Games",t:"mc",q:"What is Charizard's type combination?",o:["Fire / Flying","Fire / Dragon","Fire only","Fire / Ground"],a:0},
{c:"Games",t:"mc",q:"Which Pokémon evolves from Magikarp?",o:["Gyarados","Milotic","Seaking","Feebas"],a:0},
{c:"Games",t:"mc",q:"You must beat the Elite Four before facing whom?",o:["The Champion","The Gym Leaders","Team Rocket's boss","Professor Oak"],a:0},
{c:"Games",t:"mc",q:"Which legendary Pokémon appears on the box of Pokémon Red?",o:["Charizard","Mewtwo","Articuno","Zapdos"],a:0},
{c:"Games",t:"mc",q:"Which type is super effective against Water-type Pokémon?",o:["Electric","Fire","Ice","Poison"],a:0},
{c:"Games",t:"mc",q:"Which device records data on every Pokémon species?",o:["Pokédex","Pokégear","Town Map","PC Box"],a:0},
{c:"Games",t:"mc",q:"What is the maximum level in the main-series games?",o:["100","99","255","50"],a:0},
{c:"Games",t:"mc",q:"Which region is featured in Pokémon Sun and Moon?",o:["Alola","Kalos","Unova","Galar"],a:0},
{c:"Games",t:"mc",q:"Which Pokémon is known as the 'Genetic Pokémon', created in a lab?",o:["Mewtwo","Mew","Ditto","Porygon"],a:0},
{c:"Games",t:"mc",q:"Which region is featured in Pokémon Scarlet and Violet?",o:["Paldea","Galar","Sinnoh","Hoenn"],a:0},
{c:"Games",t:"mc",q:"Pokémon Diamond and Pearl take place in which region?",o:["Sinnoh","Johto","Hoenn","Unova"],a:0},
{c:"Games",t:"mc",q:"Which stat generally decides who attacks first in battle?",o:["Speed","Attack","Defense","Special"],a:0},
{c:"Games",t:"mc",q:"How many Gym Badges are needed in Kanto before the League?",o:["8","6","10","4"],a:0},
{c:"Games",t:"mc",q:"As of recent games, how many Pokémon can Eevee evolve into?",o:["8","5","3","7"],a:0},
{c:"Games",t:"mc",q:"Which stone evolves Pikachu into Raichu?",o:["Thunder Stone","Fire Stone","Water Stone","Moon Stone"],a:0},
{c:"Games",t:"mc",q:"Which Eeveelution is a Water-type?",o:["Vaporeon","Jolteon","Flareon","Umbreon"],a:0},
{c:"Games",t:"mc",q:"Umbreon, an evolution of Eevee, is which type?",o:["Dark","Ghost","Psychic","Poison"],a:0},
{c:"Games",t:"mc",q:"Generation 2 raised the total number of Pokémon to how many?",o:["251","200","386","151"],a:0},
{c:"Games",t:"mc",q:"In which generation's games was Mega Evolution introduced?",o:["Gen 6 (X & Y)","Gen 3","Gen 4","Gen 5"],a:0},
{c:"Games",t:"mc",q:"Z-Moves were introduced in the games set in which region?",o:["Alola","Kalos","Galar","Sinnoh"],a:0},
{c:"Games",t:"mc",q:"Dynamax and Gigantamax are mechanics from which games?",o:["Sword & Shield","Sun & Moon","X & Y","Black & White"],a:0},
{c:"Games",t:"mc",q:"Where do trainers buy Poké Balls and Potions?",o:["Poké Mart","Pokémon Center","Day Care","Game Corner"],a:0},
{c:"Games",t:"mc",q:"Where can a hurt Pokémon be healed for free?",o:["Pokémon Center","Poké Mart","Gym","Safari Zone"],a:0},
{c:"Games",t:"mc",q:"A 'shiny' Pokémon is notable for what?",o:["Rare alternate coloration","A higher level cap","Never fainting","Infinite items"],a:0},
{c:"Games",t:"mc",q:"What happens when a Pokémon's HP reaches zero?",o:["It faints","It evolves","It flees","It levels up"],a:0},
{c:"Games",t:"mc",q:"Giovanni, the Team Rocket boss, leads which city's Gym?",o:["Viridian City","Cerulean City","Saffron City","Vermilion City"],a:0},
{c:"Games",t:"mc",q:"Which of these was NOT one of the three starters in Red & Blue?",o:["Pikachu","Bulbasaur","Charmander","Squirtle"],a:0},
{c:"Games",t:"mc",q:"Which type is completely immune to Ground-type moves?",o:["Flying","Water","Grass","Fire"],a:0},
{c:"Games",t:"mc",q:"Normal-type Pokémon are weak to which single type?",o:["Fighting","Ghost","Dark","Psychic"],a:0},
{c:"Games",t:"mc",q:"Which Pokémon is #150 in the National Pokédex?",o:["Mewtwo","Mew","Dragonite","Zapdos"],a:0},
{c:"Games",t:"mc",q:"What does the move Transform let Ditto do?",o:["Copy the foe's form","Heal fully","Always go first","Double its size"],a:0},
// --- ANIME / MOVIES ---
{c:"Anime",t:"mc",q:"Who is the main human protagonist of the original Pokémon anime?",o:["Ash Ketchum","Gary Oak","Red","Brock"],a:0},
{c:"Anime",t:"mc",q:"Which town is Ash Ketchum from?",o:["Pallet Town","Viridian City","Pewter City","Cerulean City"],a:0},
{c:"Anime",t:"mc",q:"Which talking Pokémon belongs to Team Rocket's main trio?",o:["Meowth","Wobbuffet","Persian","Pikachu"],a:0},
{c:"Anime",t:"mc",q:"What are the names of Team Rocket's two main human members?",o:["Jessie & James","Cassidy & Butch","Mondo & Domino","Attila & Hun"],a:0},
{c:"Anime",t:"mc",q:"In Pokémon: The First Movie, who is the central antagonist?",o:["Mewtwo","Mew","Giovanni","Lugia"],a:0},
{c:"Anime",t:"mc",q:"Which Pokémon flies across the sky in the very first anime episode?",o:["Ho-Oh","Lugia","Articuno","Rayquaza"],a:0},
{c:"Anime",t:"mc",q:"Brock, Ash's companion, runs a Gym specializing in which type?",o:["Rock","Water","Grass","Fire"],a:0},
{c:"Anime",t:"mc",q:"Misty, Ash's companion, is a Gym Leader of which type?",o:["Water","Ice","Electric","Rock"],a:0},
{c:"Anime",t:"mc",q:"Who voices the title character in the 2019 film Detective Pikachu?",o:["Ryan Reynolds","Danny DeVito","Chris Pratt","Will Smith"],a:0},
{c:"Anime",t:"mc",q:"Ash's earliest rival in the anime is named what?",o:["Gary Oak","Paul","Trip","Bianca"],a:0},
{c:"Anime",t:"mc",q:"Which phrase opens Team Rocket's famous motto?",o:["Prepare for trouble","To protect the world","Surrender now","Blast off again"],a:0},
{c:"Anime",t:"mc",q:"In which region did Ash win his FIRST official League championship?",o:["Alola","Kanto","Sinnoh","Unova"],a:0},
{c:"Anime",t:"mc",q:"Ash finally became World Champion by defeating which trainer?",o:["Leon","Cynthia","Lance","Gary"],a:0},
{c:"Anime",t:"mc",q:"Who are the new lead protagonists of the soft-reboot anime, Pokémon Horizons?",o:["Liko & Roy","Ash & Goh","Red & Blue","Brock & Misty"],a:0},
{c:"Anime",t:"mc",q:"Which Pokémon did Ash receive as his very first partner?",o:["Pikachu","Charmander","Bulbasaur","Squirtle"],a:0},
{c:"Anime",t:"mc",q:"Dawn was Ash's traveling companion in which region?",o:["Sinnoh","Hoenn","Kalos","Unova"],a:0},
// --- TRADING CARD GAME ---
{c:"TCG",t:"mc",q:"In the Pokémon TCG, which cards power your Pokémon's attacks?",o:["Energy cards","Trainer cards","Stadium cards","Item cards"],a:0},
{c:"TCG",t:"mc",q:"Which company first published the Pokémon TCG in English (1999)?",o:["Wizards of the Coast","Nintendo","Hasbro","Konami"],a:0},
{c:"TCG",t:"mc",q:"A holographic Base Set card of which Pokémon is the most iconic chase card?",o:["Charizard","Pikachu","Mewtwo","Blastoise"],a:0},
{c:"TCG",t:"mc",q:"What is the name of the very first English Pokémon TCG set?",o:["Base Set","Jungle","Fossil","Team Rocket"],a:0},
{c:"TCG",t:"mc",q:"A Pokémon VMAX card evolves from which kind of card?",o:["Pokémon V","Pokémon GX","Pokémon ex","Pokémon BREAK"],a:0},
{c:"TCG",t:"mc",q:"What is the standard way to win a game of the Pokémon TCG?",o:["Take all your Prize cards","Reduce HP to zero","Draw your whole deck","Collect 8 badges"],a:0},
{c:"TCG",t:"mc",q:"Which Pokémon cards can be played straight onto your Bench from hand?",o:["Basic Pokémon","Stage 1","Stage 2","Mega"],a:0},
{c:"TCG",t:"mc",q:"Many TCG attacks resolve their effects by doing what?",o:["Flipping a coin","Rolling dice","Drawing tarot","Spinning a wheel"],a:0},
{c:"TCG",t:"mc",q:"The most expensive Pokémon card ever sold, the 'Illustrator', features which Pokémon?",o:["Pikachu","Charizard","Mew","Lugia"],a:0},
// --- LORE / GENERAL ---
{c:"Lore",t:"mc",q:"How many Pokémon types exist (Generation 6 onward)?",o:["18","17","15","20"],a:0},
{c:"Lore",t:"mc",q:"Which type was added in Gen 6 partly to balance Dragon-types?",o:["Fairy","Dark","Steel","Ghost"],a:0},
{c:"Lore",t:"mc",q:"Which two types were introduced in Generation 2?",o:["Dark & Steel","Fairy & Dark","Steel & Ghost","Dragon & Fairy"],a:0},
{c:"Lore",t:"mc",q:"What is Pikachu's National Pokédex number?",o:["025","001","052","150"],a:0},
{c:"Lore",t:"mc",q:"Articuno, Zapdos and Moltres are collectively known as the...",o:["Legendary Birds","Legendary Beasts","Weather Trio","Lake Guardians"],a:0},
{c:"Lore",t:"mc",q:"Raikou, Entei and Suicune are collectively called the...",o:["Legendary Beasts","Legendary Birds","Swords of Justice","Eon Duo"],a:0},
{c:"Lore",t:"mc",q:"Which Pokémon is said to contain the DNA of all Pokémon?",o:["Mew","Mewtwo","Ditto","Arceus"],a:0},
{c:"Lore",t:"mc",q:"Snorlax is best known for which behavior?",o:["Sleeping & blocking paths","Flying fast","Digging tunnels","Swimming deep"],a:0},
{c:"Lore",t:"mc",q:"Lucario is which dual-type?",o:["Fighting / Steel","Fighting / Dark","Steel / Psychic","Fighting / Fire"],a:0},
{c:"Lore",t:"mc",q:"Bulbasaur is which dual-type?",o:["Grass / Poison","Grass / Bug","Grass only","Grass / Ground"],a:0},
{c:"Lore",t:"mc",q:"Dragon-type moves are super effective against which type?",o:["Dragon","Fairy","Steel","Normal"],a:0},
// --- TRUE / FALSE ---
{c:"Lore",t:"tf",q:"Charizard is a Dragon-type Pokémon.",a:false},
{c:"Lore",t:"tf",q:"Gyarados evolves from Magikarp.",a:true},
{c:"Lore",t:"tf",q:"There are 151 Pokémon in the original Kanto Pokédex.",a:true},
{c:"Lore",t:"tf",q:"Psychic-type moves are super effective against Dark-types.",a:false},
{c:"Lore",t:"tf",q:"The Master Ball can sometimes fail to catch a Pokémon.",a:false},
{c:"Lore",t:"tf",q:"Eevee has more possible evolutions than any other single Pokémon.",a:true},
{c:"Anime",t:"tf",q:"Ash's Pikachu has always refused to evolve into Raichu.",a:true},
{c:"Lore",t:"tf",q:"The Steel type existed back in Generation 1.",a:false},
{c:"Lore",t:"tf",q:"Snorlax is a Normal-type Pokémon.",a:true},
{c:"Lore",t:"tf",q:"Jigglypuff became part Fairy-type as of Generation 6.",a:true},
{c:"Lore",t:"tf",q:"Ground-type moves can hit Flying-type Pokémon normally.",a:false},
{c:"Lore",t:"tf",q:"Ditto can breed with almost any other Pokémon.",a:true},
{c:"Lore",t:"tf",q:"Ho-Oh appears on the cover of Pokémon Gold.",a:true},
{c:"Anime",t:"tf",q:"Wobbuffet belongs to Jessie of Team Rocket in the anime.",a:true},
{c:"Lore",t:"tf",q:"Ghost-type moves have no effect on Normal-type Pokémon.",a:true},
{c:"Games",t:"tf",q:"In the games, most Pokémon evolve by leveling up.",a:true},
{c:"TCG",t:"tf",q:"Energy cards are required to use most attacks in the Pokémon TCG.",a:true},
{c:"Lore",t:"tf",q:"Sylveon, introduced in Gen 6, is a Fairy-type.",a:true},
// --- MULTI-SELECT (choose all that apply) ---
{c:"Games",t:"multi",q:"Which of these are Generation 1 starter Pokémon?",o:["Bulbasaur","Squirtle","Chikorita","Charmander","Totodile"],a:[0,1,3]},
{c:"Lore",t:"multi",q:"Which of these are Eevee evolutions (Eeveelutions)?",o:["Vaporeon","Umbreon","Vulpix","Jolteon","Sylveon"],a:[0,1,3,4]},
{c:"Lore",t:"multi",q:"Which of these are Legendary Birds of Kanto?",o:["Articuno","Zapdos","Lugia","Moltres","Entei"],a:[0,1,3]},
{c:"Games",t:"multi",q:"Which of these are Water-type starter Pokémon?",o:["Squirtle","Totodile","Mudkip","Bulbasaur","Chimchar"],a:[0,1,2]},
{c:"Anime",t:"multi",q:"Which of these were part of Ash's classic Kanto team?",o:["Pikachu","Charizard","Meowth","Bulbasaur","Squirtle"],a:[0,1,3,4]},
{c:"Lore",t:"multi",q:"Which of the following are real Pokémon types?",o:["Fairy","Dragon","Light","Steel","Cosmic"],a:[0,1,3]},
{c:"Lore",t:"multi",q:"Which of these are Legendary Pokémon (not common species)?",o:["Mewtwo","Lugia","Pidgey","Rayquaza","Bidoof"],a:[0,1,3]},
{c:"Games",t:"multi",q:"Which of these are Fire-type starter Pokémon?",o:["Charmander","Cyndaquil","Torchic","Chikorita","Mudkip"],a:[0,1,2]},
{c:"Games",t:"multi",q:"Which types are super effective against Grass-type Pokémon?",o:["Fire","Ice","Flying","Water","Ground"],a:[0,1,2]},
{c:"Games",t:"multi",q:"Which of these are battle 'power-up' mechanics from the games?",o:["Mega Evolution","Z-Move","Dynamax","Potion","Poké Ball"],a:[0,1,2]},
// --- ORDER (arrange correctly) ---
{c:"Games",t:"order",q:"Arrange Charmander's evolution line (first to last):",o:["Charmander","Charmeleon","Charizard"]},
{c:"Games",t:"order",q:"Arrange Bulbasaur's evolution line (first to last):",o:["Bulbasaur","Ivysaur","Venusaur"]},
{c:"Games",t:"order",q:"Arrange Squirtle's evolution line (first to last):",o:["Squirtle","Wartortle","Blastoise"]},
{c:"Lore",t:"order",q:"Arrange Caterpie's evolution line (first to last):",o:["Caterpie","Metapod","Butterfree"]},
{c:"Lore",t:"order",q:"Arrange Weedle's evolution line (first to last):",o:["Weedle","Kakuna","Beedrill"]},
{c:"Lore",t:"order",q:"Arrange Pikachu's full evolution line (baby to final):",o:["Pichu","Pikachu","Raichu"]},
{c:"Games",t:"order",q:"Order these by Pokédex number (lowest first):",o:["Bulbasaur","Charmander","Squirtle","Pikachu","Mewtwo"]},
{c:"Games",t:"order",q:"Order these regions by debut (earliest first):",o:["Kanto","Johto","Hoenn","Sinnoh"]},
{c:"Games",t:"order",q:"Order these regions by debut (earliest first):",o:["Kalos","Alola","Galar","Paldea"]},
{c:"Games",t:"order",q:"Order these games by release (earliest first):",o:["Red & Blue","Gold & Silver","Ruby & Sapphire","Diamond & Pearl"]},
{c:"Games",t:"order",q:"Order these mechanics by the generation they debuted (earliest first):",o:["Mega Evolution","Z-Moves","Dynamax"]},
{c:"Lore",t:"order",q:"Order these Eeveelutions by generation introduced (earliest first):",o:["Vaporeon","Espeon","Leafeon","Sylveon"]}
];
/* ===================== SEEDED RNG ===================== */
function mulberry32(a){return function(){a|=0;a=a+0x6D2B79F5|0;let t=Math.imul(a^a>>>15,1|a);t=t+Math.imul(t^t>>>7,61|t)^t;return((t^t>>>14)>>>0)/4294967296}}
function shuffle(arr,rng){const a=arr.slice();for(let i=a.length-1;i>0;i--){const j=Math.floor(rng()*(i+1));[a[i],a[j]]=[a[j],a[i]]}return a}
/* ===================== STATE ===================== */
const screen=document.getElementById('screen');
let stats={high:0,played:0,totalCorrect:0,totalQ:0,bestStreak:0,usedSeeds:[]};
let playerName="Trainer";
let roundLen=10;
let game=null; // {seed,qs,idx,score,correct,streak}
let lbReady=false, userCount=1;
/* ===================== SDK HELPERS ===================== */
async function vload(k){try{return await vibes.load(k)}catch(e){return null}}
async function vsave(k,v){try{await vibes.save(k,v)}catch(e){console.warn('save',k,e.message)}}
async function loadAll(){
const s=await vload('stats'); if(s&&typeof s==='object') stats=Object.assign(stats,s);
if(!Array.isArray(stats.usedSeeds)) stats.usedSeeds=[];
const n=await vload('name'); if(typeof n==='string'&&n.trim()) playerName=n;
const rl=await vload('roundLen'); if(rl) roundLen=rl;
}
/* unique seed: never reuse one this player has already played */
function freshSeed(){
const used=new Set(stats.usedSeeds);
let s, guard=0;
do{ s=(Math.floor(Math.random()*0xFFFFFFFF))>>>0; guard++; }
while(used.has(s)&&guard<9999);
return s;
}
/* ===================== LEADERBOARD (shared state) ===================== */
async function initLeaderboard(){
try{
await vibes.shared.join('leaderboard',{persistent:true});
vibes.shared.onChange('top',()=>{ if(curScreen==='start'||curScreen==='end') renderLeaderboard(); });
try{ userCount=await vibes.shared.getUserCount(); }catch(e){}
lbReady=true;
}catch(e){ lbReady=false; }
}
function getTop(){ try{ return vibes.shared.get('top')||[]; }catch(e){ return []; } }
async function submitScore(entry){
if(!lbReady) return;
try{
const top=getTop().slice();
top.push(entry);
top.sort((a,b)=> b.s-a.s || b.a-a.a || a.t-b.t);
await vibes.shared.set('top', top.slice(0,15));
}catch(e){ console.warn('lb',e.message); }
}
/* ===================== AGENT-ACCESSIBLE STATE ===================== */
async function pushAgentState(){
try{
await vibes.ai.setState('context',{
player:playerName, highScore:stats.high, gamesPlayed:stats.played,
lifetimeAccuracy: stats.totalQ? Math.round(stats.totalCorrect/stats.totalQ*100):0,
bestStreak:stats.bestStreak, lastSeed: game?game.seed:null, updated:Date.now()
});
}catch(e){ /* owner may not have enabled agent access */ }
}
/* ===================== BUILD A GAME FROM A SEED ===================== */
function buildGame(seed){
const rng=mulberry32(seed);
const picks=shuffle(BANK.map((_,i)=>i),rng).slice(0,Math.min(roundLen,BANK.length));
const qs=picks.map(i=>{
const base=BANK[i];
if(base.t==='mc'){
const opts=shuffle(base.o.map((txt,idx)=>({txt,ok:idx===base.a})),rng);
return {c:base.c,t:'mc',q:base.q,opts};
}
if(base.t==='multi'){
const opts=shuffle(base.o.map((txt,idx)=>({txt,ok:base.a.includes(idx)})),rng);
return {c:base.c,t:'multi',q:base.q,opts};
}
if(base.t==='order'){
let disp; do{ disp=shuffle(base.o,rng); }while(disp.join('|')===base.o.join('|')&&base.o.length>1);
return {c:base.c,t:'order',q:base.q,correct:base.o.slice(),disp};
}
return {c:base.c,t:'tf',q:base.q,ans:base.a}; // tf
});
return {seed,qs,idx:0,score:0,correct:0,streak:0};
}
/* ===================== SCREENS ===================== */
let curScreen='start';
const esc=s=>String(s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
function show(html){ screen.innerHTML=html; }
function startScreen(){
curScreen='start';
const acc=stats.totalQ?Math.round(stats.totalCorrect/stats.totalQ*100):0;
show(`
<div class="eyebrow">Pokémon Knowledge Test</div>
<h1 class="title">Pokédex Trivia</h1>
<div class="sub">Games · Anime · Cards · Lore — every round is a unique seed.</div>
<div class="stats">
<div class="stat"><b>${stats.high}</b><span>High Score</span></div>
<div class="stat"><b>${stats.played}</b><span>Games</span></div>
<div class="stat"><b>${acc}%</b><span>Accuracy</span></div>
</div>
<div class="field">
<label class="lab" for="nm">Trainer name</label>
<input id="nm" class="name" maxlength="16" value="${esc(playerName)}" placeholder="Trainer">
</div>
<div class="field">
<label class="lab">Questions per round</label>
<div class="lenpick" id="len">
<button data-l="5"${roundLen===5?' class="on"':''}>5</button>
<button data-l="10"${roundLen===10?' class="on"':''}>10</button>
<button data-l="15"${roundLen===15?' class="on"':''}>15</button>
<button data-l="20"${roundLen===20?' class="on"':''}>20</button>
</div>
</div>
<button class="primary gold" id="play">▶ Start a unique round</button>
<div id="lbWrap" class="lb"></div>
`);
document.getElementById('nm').addEventListener('change',async e=>{
playerName=(e.target.value||'').trim().slice(0,16)||'Trainer'; await vsave('name',playerName);
});
document.getElementById('len').addEventListener('click',async e=>{
const b=e.target.closest('button'); if(!b)return;
roundLen=+b.dataset.l; await vsave('roundLen',roundLen);
[...e.currentTarget.children].forEach(x=>x.classList.toggle('on',x===b));
});
document.getElementById('play').addEventListener('click',startRound);
renderLeaderboard();
}
function renderLeaderboard(){
const wrap=document.getElementById('lbWrap'); if(!wrap)return;
const top=getTop();
const rows=top.length? top.slice(0,10).map((e,i)=>`
<div class="lrow${e.n===playerName?' me':''}">
<span class="rk">${i+1}</span>
<span class="nm">${esc(e.n)}</span>
<span class="sc">${e.s}</span>
<span class="ac">${e.a}%</span>
</div>`).join('')
: `<div class="empty">${lbReady?'No scores yet — be the first!':'Leaderboard offline'}</div>`;
wrap.innerHTML=`<h3>Global Leaderboard <span class="live">${lbReady?('● '+userCount+' online'):''}</span></h3>${rows}`;
}
function startRound(){
const seed=freshSeed();
game=buildGame(seed);
if(!stats.usedSeeds.includes(seed)){ stats.usedSeeds.push(seed); if(stats.usedSeeds.length>400) stats.usedSeeds=stats.usedSeeds.slice(-400); }
renderQuestion();
}
function renderQuestion(){
curScreen='game';
const g=game, q=g.qs[g.idx], n=g.qs.length;
let body='';
if(q.t==='mc'||q.t==='multi'){
body=q.opts.map((o,i)=>`<button class="opt" data-i="${i}">${esc(o.txt)}<span class="mk"></span></button>`).join('');
if(q.t==='multi') body+=`<button class="primary" id="submitMulti" style="margin-top:6px">Submit answer</button>`;
} else if(q.t==='tf'){
body=`<div class="tf"><button class="opt" data-v="1">TRUE</button><button class="opt" data-v="0">FALSE</button></div>`;
} else { // order
body=`<div class="seq" id="seq"></div>
<div class="pool" id="pool">${q.disp.map((x,i)=>`<button class="chip" data-i="${i}">${esc(x)}</button>`).join('')}</div>
<button class="primary" id="submitOrder">Submit order</button>`;
}
show(`
<div class="hud"><span>Q ${g.idx+1}/${n}</span><span class="cat">${esc(q.c)}</span><span>Score ${g.score}</span></div>
<div class="bar"><i style="width:${g.idx/n*100}%"></i></div>
<div class="q">${esc(q.q)}${q.t==='multi'?' <span style="font-size:12px;color:#5a6a40">(select all)</span>':''}</div>
<div id="body">${body}</div>
`);
if(q.t==='mc') screen.querySelectorAll('.opt').forEach(b=>b.addEventListener('click',()=>answerMC(b)));
if(q.t==='tf') screen.querySelectorAll('.opt').forEach(b=>b.addEventListener('click',()=>answerTF(b)));
if(q.t==='multi'){
screen.querySelectorAll('.opt').forEach(b=>b.addEventListener('click',()=>b.classList.toggle('sel')));
document.getElementById('submitMulti').addEventListener('click',answerMulti);
}
if(q.t==='order') initOrder(q);
}
/* ---- answer handlers ---- */
function lockOpts(){ screen.querySelectorAll('.opt,.chip,.primary').forEach(b=>b.disabled=true); }
function nextOrEnd(){ game.idx++; if(game.idx>=game.qs.length) endRound(); else renderQuestion(); }
function feedback(ok,msg){
const div=document.createElement('div');
div.className='fb '+(ok?'ok':'no');
div.innerHTML=(ok?'✓ Correct!':'✗ Not quite.')+(msg?`<small>${msg}</small>`:'');
document.getElementById('body').appendChild(div);
const btn=document.createElement('button');
btn.className='primary'; btn.style.marginTop='10px';
btn.textContent=(game.idx+1>=game.qs.length)?'See results ▶':'Next question ▶';
btn.addEventListener('click',nextOrEnd);
document.getElementById('body').appendChild(btn);
}
function score(ok){
if(ok){ game.correct++; game.streak++; game.score+=10+Math.max(0,(game.streak-1)*2); if(game.streak>stats.bestStreak)stats.bestStreak=game.streak; }
else game.streak=0;
}
function answerMC(btn){
const q=game.qs[game.idx]; lockOpts();
const ok=q.opts[+btn.dataset.i].ok;
screen.querySelectorAll('.opt').forEach((b,i)=>{
if(q.opts[i].ok){b.classList.add('right');b.querySelector('.mk').textContent='✓';}
else if(b===btn){b.classList.add('wrong');b.querySelector('.mk').textContent='✗';}
else b.classList.add('dim');
});
score(ok); feedback(ok, ok?'':'Correct answer is highlighted.');
}
function answerTF(btn){
const q=game.qs[game.idx]; lockOpts();
const chose=btn.dataset.v==='1', ok=chose===q.ans;
screen.querySelectorAll('.opt').forEach(b=>{
const v=b.dataset.v==='1';
if(v===q.ans)b.classList.add('right'); else if(b===btn)b.classList.add('wrong'); else b.classList.add('dim');
});
score(ok); feedback(ok, ok?'':('The statement is '+(q.ans?'TRUE.':'FALSE.')));
}
function answerMulti(){
const q=game.qs[game.idx]; lockOpts();
let ok=true;
screen.querySelectorAll('.opt').forEach((b,i)=>{
const picked=b.classList.contains('sel'), correct=q.opts[i].ok;
if(correct&&picked){b.classList.remove('sel');b.classList.add('right');b.querySelector('.mk').textContent='✓';}
else if(correct&&!picked){b.classList.add('right');b.querySelector('.mk').textContent='✓';ok=false;}
else if(!correct&&picked){b.classList.remove('sel');b.classList.add('wrong');b.querySelector('.mk').textContent='✗';ok=false;}
else b.classList.add('dim');
});
score(ok); feedback(ok, ok?'':'You need every correct option and no wrong ones.');
}
/* ---- order question ---- */
let orderState=[];
function initOrder(q){
orderState=[];
const seq=document.getElementById('seq'), pool=document.getElementById('pool');
function paint(){
seq.innerHTML=orderState.map((i,pos)=>`<button class="chip in" data-pos="${pos}"><span class="n">${pos+1}</span>${esc(q.disp[i])}</button>`).join('');
pool.querySelectorAll('.chip').forEach(c=>{c.style.display=orderState.includes(+c.dataset.i)?'none':'';});
seq.querySelectorAll('.chip').forEach(c=>c.addEventListener('click',()=>{orderState.splice(+c.dataset.pos,1);paint();}));
}
pool.querySelectorAll('.chip').forEach(c=>c.addEventListener('click',()=>{orderState.push(+c.dataset.i);paint();}));
document.getElementById('submitOrder').addEventListener('click',()=>{
if(orderState.length<q.disp.length) return;
lockOpts();
const chosen=orderState.map(i=>q.disp[i]);
const ok=chosen.join('|')===q.correct.join('|');
score(ok);
feedback(ok, ok?'':('Correct order: '+q.correct.join(' → ')));
});
}
/* ===================== END OF ROUND ===================== */
async function endRound(){
curScreen='end';
const g=game, n=g.qs.length, acc=Math.round(g.correct/n*100);
const isHigh=g.score>stats.high;
stats.played++; stats.totalCorrect+=g.correct; stats.totalQ+=n;
if(isHigh) stats.high=g.score;
show(`
<div class="eyebrow">Round complete · Seed locked</div>
<div class="verdict">${isHigh?'🏆 New High Score!':(acc>=80?'⚡ Great battling!':acc>=50?'Solid effort!':'Keep training!')}</div>
<div class="score-big">${g.score}<small> pts</small></div>
<div class="stats">
<div class="stat"><b>${g.correct}/${n}</b><span>Correct</span></div>
<div class="stat"><b>${acc}%</b><span>Accuracy</span></div>
<div class="stat"><b>${stats.bestStreak}</b><span>Best Streak</span></div>
</div>
<div class="seedline">UNIQUE SEED #${g.seed}</div>
<div class="row">
<button class="primary gold" id="again">▶ New unique round</button>
<button class="ghost" id="home" style="margin-top:0;flex:0 0 38%">Home</button>
</div>
<button class="ghost" id="exp">⬇ Export leaderboard (CSV)</button>
<div id="lbWrap" class="lb"></div>
`);
await vsave('stats',stats);
await submitScore({n:playerName,s:g.score,a:acc,t:Date.now()});
pushAgentState();
renderLeaderboard();
document.getElementById('again').addEventListener('click',startRound);
document.getElementById('home').addEventListener('click',startScreen);
document.getElementById('exp').addEventListener('click',exportLeaderboard);
}
function exportLeaderboard(){
try{
const top=getTop();
const rows=[['Rank','Trainer','Score','Accuracy %']];
top.forEach((e,i)=>rows.push([i+1,e.n,e.s,e.a]));
if(rows.length===1) rows.push(['—','No scores yet','','']);
vibes.exportData(rows,{filename:'pokedex-leaderboard.csv'});
}catch(e){ console.warn('export',e.message); }
}
/* ===================== BOOT ===================== */
vibes.onReady(async()=>{
await loadAll();
startScreen(); // render immediately with local data
await initLeaderboard();// then wire up shared state
renderLeaderboard();
pushAgentState();
});
</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/pokdex-trivia */