/* ============================================================ gandora — a generative station. seed → DJ (Venice chat) → ACE-Step (Venice /audio/queue) → player. Every cover is drawn from the song's own settings: hue from key, rings from BPM, density from energy, symmetry from the seed. ============================================================ */ export const DIRECT = "https://api.venice.ai/api/v1"; // with a personal key export const SHARED = "https://inference.yolopreneur.lol/v1"; // without one: the factory's worker, quota'd per person export const $ = (s) => document.querySelector(s); export const LS = { key: "gandora.key", dj: "gandora.dj", cost: "gandora.cost" }; export const state = { key: localStorage.getItem(LS.key) || "", dj: localStorage.getItem(LS.dj) || "claude-haiku-5-5", seed: "", station: "", stationId: "", sinceAd: 0, taste: { like: [], avoid: [], notes: [] }, // what the listener has told the station history: [], // tracks played this session (specs) current: null, nextJob: null, playing: false, recent: [], pos: -1, // ids of tracks played this session, and where the player stands in them audio: Object.assign(new Audio(), { volume: Math.min(1, Math.max(0, parseFloat(localStorage.getItem("gandora.vol") || "1"))) }), cost: parseFloat(localStorage.getItem(LS.cost) || "0"), }; state.audio.preload = "auto"; /* hooks: the seams something loaded after the page (brain.js) plugs into. Each is null or a function; the page calls them at the named moments. Assign, don't override the page's functions. */ export const hooks = { djPrompt: null, // (prompt) => prompt the DJ's prompt, to add to (brain.js adds the taste text) jobMade: null, // (spec) a sheet about to be rendered (brain.js writes spec.guess) stationOpened: null, // (seed, opts) after openStation finished reacted: null, // (emoji, text) after a reaction was taken in }; /* ---------- small utils ---------- */ export const toast = (m, ms = 2400) => { const t = $("#toast"); t.textContent = m; t.classList.add("on"); clearTimeout(toast.h); toast.h = setTimeout(() => t.classList.remove("on"), ms); }; export const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); export const asDataUrl = (blob) => new Promise((ok, no) => { const r = new FileReader(); r.onload = () => ok(r.result); r.onerror = () => no(r.error); r.readAsDataURL(blob); }); export const ids = { seq: 0 }; // job ids; brain.js reaches it as window.seq export const mmss = (s) => `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, "0")}`;