/* * fee[dB]ack v0.3.0 — FeedBarcade (#v3-feedbarcade), v3-native minigames hub. * * A vanilla-JS reskin of the bundled `minigames` plugin's screen into the fb-* * design (constitution P-II). It REUSES the minigames backend + the * window.feedBackMinigames SDK — it does not fork scoring/run logic and adds * no second XP curve. XP stays unified through the core store (P15): the header * reads /api/profile/progress (the same data the topbar profile badge shows), * while per-game bests + cross-game unlocks come from the minigames /profile. * * UI placement is a DEFERRED capability domain (design/05): we use the legacy * plugin loader + /api/plugins/minigames/* routes, NOT capability dispatch. * Launch delegates to feedBackMinigames.start(gameId), which mounts the * plugin's own #mg-stage overlay (eagerly injected at boot) — so this screen * never needs to visit the legacy #plugin-minigames hub. */ (function () { 'use strict'; const SCREEN_ID = 'v3-feedbarcade'; const sm = window.feedBack; const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ( { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); const enc = encodeURIComponent; let _progress = null; // /api/profile/progress → header level/xp/streak let _mgProfile = null; // /api/plugins/minigames/profile → per_game + unlocks let _registry = null; // /api/plugins/minigames/registry → installed minigames // Stage-visibility tracking for the refresh-after-run observer. let _observer = null; let _stageWasVisible = false; let _refreshTimer = null; function sdk() { return window.feedBackMinigames || null; } function registeredList() { const s = sdk(); try { return (s && typeof s.listRegistered === 'function') ? s.listRegistered() : []; } catch (e) { return []; } } // ── API ────────────────────────────────────────────────────────────────-- async function jget(url) { try { const r = await fetch(url); return r.ok ? r.json() : null; } catch (e) { return null; } } async function fetchProgress() { _progress = await jget('/api/profile/progress'); } async function fetchMgProfile() { const s = sdk(); if (s && typeof s.getProfile === 'function') { try { _mgProfile = await s.getProfile(); return; } catch (e) { /* fall through */ } } _mgProfile = await jget('/api/plugins/minigames/profile'); } async function fetchRegistry() { _registry = await jget('/api/plugins/minigames/registry'); } async function load() { await Promise.all([fetchProgress(), fetchMgProfile(), fetchRegistry()]); } // ── Toast (non-blocking, self-contained) ────────────────────────────────── function toast(msg) { let host = document.getElementById('v3-fb-toast'); if (!host) { host = document.createElement('div'); host.id = 'v3-fb-toast'; host.className = 'fixed bottom-6 left-1/2 -translate-x-1/2 z-[120] bg-fb-card text-fb-text ' + 'border border-fb-border/60 rounded-lg px-4 py-2 text-sm shadow-xl'; document.body.appendChild(host); } host.textContent = msg; host.classList.remove('hidden'); clearTimeout(host._t); host._t = setTimeout(() => host.classList.add('hidden'), 3000); } // ── Render: header ──────────────────────────────────────────────────────── function headerHTML() { const p = _progress || { current_streak: 0, best_streak: 0 }; // Progression (spec 010): rounds earn Decibels (dB) and advance // minigame challenges/quests — the old XP level meter is gone. const prog = (window.v3Progression && window.v3Progression.get()) || null; const rank = prog ? prog.mastery_rank : 0; const wallet = (prog && prog.wallet) || { balance: 0 }; return '
Standalone games that share your guitar input — rounds earn Decibels (dB) and count toward challenges and quests.
' + '