Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
HTML
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>pop dashboard — songs · instruments · tools</title><link rel="icon" href="https://aesthetic.computer/icon/128x128/prompt.png" type="image/png" /><link rel="stylesheet" href="https://aesthetic.computer/type/webfonts/berkeley-mono-variable.css" /><style> /* themes follow the OS automatically (prefers-color-scheme) — aesthetic.computer's own scheme: dark = purple, light = legal-pad yellow (from disks/prompt.mjs). "screen" surfaces (canvases, code-out) stay dark in both by design. */ :root { /* dark — AC purple (rgb 70,50,100) */ --bg:#463264; --panel:#3a2b52; --ink:#f2eef8; --mute:#b0a2c8; --line:#5f4d80; } @media (prefers-color-scheme: light) { :root { /* light — AC legal-pad yellow */ --bg:#fcf7c5; --panel:#fffdf0; --ink:#281e5a; --mute:#807a52; --line:#cdc488; } } * { box-sizing: border-box; } body { font-family: "Berkeley Mono Variable", "Berkeley Mono", Menlo, monospace; background: var(--bg); color: var(--ink); margin: 0; font-size: 13px; line-height: 1.5; } header { display: flex; align-items: center; gap: 12px; padding: 9px 15px; border-bottom: 1px solid var(--line); } h1 { font-size: 17px; letter-spacing: 0.14em; margin: 0; text-transform: lowercase; } h1::before { content: "♪ "; color: var(--mute); } header > #gate { margin-left: auto; } /* responsive masonry — columns scale with viewport width */ main { max-width: 1660px; margin: 0 auto; padding: 13px 13px 44px; column-width: 22rem; column-gap: 12px; } .demo-card { break-inside: avoid; display: inline-block; width: 100%; margin-bottom: 12px; border: 1px solid var(--line); border-left: 3px solid var(--accent,#555); background: var(--panel); padding: 9px 11px 11px; } .demo-card h2 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em; margin: 1px 0 0; color: var(--accent,#ccc); } .demo-card .cat { font-size: 7.5px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--mute); } .demo-card .blurb { color: var(--mute); margin: 3px 0 8px; font-size: 10.5px; line-height: 1.34; } .demo-body { display: flex; flex-direction: column; gap: 6px; } .demo-row { display: flex; flex-wrap: wrap; gap: 5px 10px; align-items: center; } canvas { width: 100%; height: 96px; display: block; border: 1px solid var(--line); background: #000; } .demo-btn { font: inherit; font-size: 10.5px; cursor: pointer; background: var(--accent,#333); color: #0d0c0b; border: none; padding: 4px 10px; letter-spacing: 0.04em; text-transform: uppercase; } .demo-btn:hover { filter: brightness(1.15); } .demo-btn:active { transform: translateY(1px); } .demo-btn[disabled] { opacity: 0.4; cursor: default; } .demo-ctl { display: inline-flex; align-items: center; gap: 4px; font-size: 10px; } .demo-lbl { color: var(--mute); } .demo-num { color: var(--ink); min-width: 26px; } .demo-ctl input[type=range] { width: 92px; accent-color: var(--accent,#888); } .demo-sel { font: inherit; font-size: 10px; padding: 1px 4px; background: var(--bg); color: var(--ink); border: 1px solid var(--line); } .demo-status { font-size: 10px; color: var(--mute); min-height: 1.3em; white-space: pre-wrap; } .demo-out { font-size: 10px; line-height: 1.36; background: #000; color: #b9d98a; border: 1px solid var(--line); padding: 6px 8px; max-height: 150px; overflow: auto; white-space: pre-wrap; } .demo-err { color: #e0593f; font-size: 10.5px; } textarea { font: inherit; font-size: 10px; width: 100%; color: var(--ink); background: var(--bg); border: 1px solid var(--line); padding: 5px; } footer { text-align: center; font-size: 8px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--mute); padding: 14px; border-top: 1px solid var(--line); } footer a { color: var(--mute); }
/* ── dashboard sections ─────────────────────────────────────────── */ .db-dashboard { max-width: 1660px; margin: 0 auto; padding: 14px 13px 0; } .db-load-err { max-width: 1660px; margin: 14px auto 0; padding: 8px 13px; color: #e0593f; font-size: 10.5px; border: 1px solid var(--line); border-left: 3px solid #e0593f; background: var(--panel); } .db-section { font-size: 8px; letter-spacing: 0.26em; text-transform: uppercase; color: var(--mute); margin: 0 0 8px; font-weight: normal; padding-bottom: 4px; border-bottom: 1px solid var(--line); } .db-section-wrap { margin-bottom: 20px; }
/* badge — shared by jukebox + instruments */ .db-badge { font-size: 7px; letter-spacing: 0.16em; text-transform: uppercase; color: #0d0c0b; background: var(--badge,#8c8576); padding: 1.5px 5px; flex-shrink: 0; }
/* songs — jukebox */ .db-jukebox-now { position: sticky; top: 0; z-index: 6; display: flex; align-items: center; gap: 11px; background: #000; border: 1px solid var(--line); border-left: 3px solid #b5331f; padding: 6px 10px; margin-bottom: 7px; } .db-now-tag { font-size: 7px; letter-spacing: 0.22em; text-transform: uppercase; color: #8c8576; flex-shrink: 0; } .db-now-label { font-size: 11px; white-space: nowrap; flex-shrink: 0; text-transform: uppercase; letter-spacing: 0.05em; color: #ece6d6; } .db-jukebox-now audio { height: 30px; flex: 1; min-width: 150px; } .db-jukebox-list { border: 1px solid var(--line); } .db-jukebox-row { display: flex; align-items: baseline; gap: 9px; padding: 4px 9px; cursor: pointer; font-size: 11px; border-left: 3px solid var(--accent,#555); border-bottom: 1px solid var(--line); } .db-jukebox-row:last-child { border-bottom: none; } .db-jukebox-row:hover { background: var(--panel); } .db-jukebox-row.playing { background: var(--panel); } .db-jukebox-row.playing .db-jb-title { color: var(--accent); } .db-jukebox-row.noaudio { cursor: default; opacity: 0.4; } .db-jb-glyph { color: var(--accent,#888); width: 0.9em; flex-shrink: 0; } .db-jb-title { text-transform: uppercase; letter-spacing: 0.05em; min-width: 7rem; flex-shrink: 0; } .db-jb-meta { color: var(--mute); font-size: 8.5px; letter-spacing: 0.09em; text-transform: uppercase; white-space: nowrap; flex-shrink: 0; } .db-jb-blurb { color: var(--mute); font-size: 9.5px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .db-jb-no { color: var(--mute); font-size: 7.5px; text-transform: uppercase; letter-spacing: 0.14em; flex-shrink: 0; }
/* instruments — responsive grid */ .db-inst-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr)); gap: 9px; } .db-inst-card { background: var(--panel); border: 1px solid var(--line); border-left: 3px solid var(--accent,#555); padding: 8px 11px 10px; } .db-inst-label { font-size: 12px; letter-spacing: 0.05em; text-transform: uppercase; color: var(--accent,#ccc); margin-bottom: 3px; } .db-blurb { color: var(--mute); margin: 0 0 6px; font-size: 10.5px; line-height: 1.34; } .db-pending { font-size: 9.5px; color: var(--mute); margin: 4px 0 0; font-style: italic; } .db-audio-wrap audio { width: 100%; height: 28px; display: block; margin-top: 5px; } .db-chips { display: flex; flex-wrap: wrap; gap: 4px; margin: 5px 0 6px; } .db-chip { font-size: 8px; letter-spacing: 0.12em; text-transform: uppercase; border: 1px solid var(--line); color: var(--mute); padding: 1px 5px; white-space: nowrap; } .db-caption { font-size: 8px; color: var(--mute); margin: 5px 0 0; letter-spacing: 0.06em; }
/* tool demos band */ .db-tool-heading { max-width: 1660px; margin: 0 auto; padding: 0 13px 8px; font-size: 8px; letter-spacing: 0.26em; text-transform: uppercase; color: var(--mute); border-bottom: 1px solid var(--line); }
/* ── picker rows (shared by song + sound sections) ─────────────── */ .db-picker-row { display: flex; align-items: center; gap: 7px; margin-bottom: 8px; flex-wrap: wrap; } .db-picker-lbl { font-size: 7.5px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--mute); flex-shrink: 0; } .db-picker-sep { color: var(--line); flex-shrink: 0; } .db-song-sel { min-width: 18rem; max-width: 100%; } .db-cat-sel { min-width: 10rem; } .db-sound-sel { min-width: 12rem; }
/* ── song detail panel ─────────────────────────────────────────── */ .db-song-detail { border: 1px solid var(--line); border-left: 3px solid #b5331f; background: var(--panel); padding: 8px 11px 9px; min-height: 3.5rem; } .db-song-meta-row { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; } .db-song-blurb { color: var(--mute); font-size: 10.5px; line-height: 1.34; margin: 3px 0 0; } .db-song-distrib { font-size: 8.5px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--mute); margin: 4px 0 0; }
/* ── sound detail panel ────────────────────────────────────────── */ .db-sound-panel { border: 1px solid var(--line); border-left: 3px solid var(--accent, #555); background: var(--panel); padding: 8px 11px 10px; min-height: 4rem; } .db-sound-label { font-size: 12px; letter-spacing: 0.05em; text-transform: uppercase; color: var(--accent, #ccc); margin-bottom: 3px; } .db-sound-meta { font-size: 8.5px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--mute); margin: 3px 0 0; }</style></head><body> <header> <h1>pop</h1> <button id="gate" class="demo-btn" style="--accent:#b5331f">▶ enable audio</button> </header> <div id="db-tool-heading" class="db-tool-heading">tool demos</div> <main id="demos"></main> <footer> aesthetic.computer · /pop · <a href="https://github.com/whistlegraph/aesthetic-computer/tree/main/pop">source</a> </footer>
<script type="module">import { resume, stopAll } from "./lib/demo-kit.mjs";import { mountDashboard } from "./demos/dashboard.mjs";
// Each demo: { title, blurb, accent, cat, mount(rootEl) }. Modules// export a `demos` object keyed by demo id.const CATS = { fx: { label: "fx", accent: "#c0631a" }, analysis: { label: "analysis", accent: "#1d7a8a" }, score: { label: "score", accent: "#5a6b1f" }, forms: { label: "forms", accent: "#8a5223" },};
// Render order + which category each belongs to (for the chip + color).const ORDER = [ ["ringmod", "fx"], ["vocoder", "fx"], ["envelope-follower", "analysis"], ["pitch-track", "analysis"], ["audio-gate", "analysis"], ["audio-to-rhythm", "score"], ["fib-meter", "forms"], ["note-subdiv", "score"], ["species-counterpoint","forms"],];
const MODULES = [ "./demos/fx-demos.mjs", "./demos/analysis-demos.mjs", "./demos/structure-demos.mjs",];
const gate = document.getElementById("gate");gate.addEventListener("click", async () => { await resume(); gate.textContent = "♪ audio enabled"; gate.disabled = true;});
// Mount dashboard sections (songs + instruments) before the tool demos heading.const toolHeading = document.getElementById("db-tool-heading");mountDashboard(toolHeading).catch((err) => { console.error("dashboard mount failed:", err);});
const root = document.getElementById("demos");const registry = {};for (const path of MODULES) { try { const mod = await import(path); Object.assign(registry, mod.demos || {}); } catch (err) { console.error("demo module failed:", path, err); root.append(card(path, "module", "#b5331f", `failed to load ${path} — ${err.message}`, () => {})); }}
function card(title, cat, accent, blurb, mount) { const c = document.createElement("section"); c.className = "demo-card"; c.style.setProperty("--accent", accent); c.innerHTML = `<div class="cat">${cat}</div><h2>${title}</h2>` + `<div class="blurb">${blurb}</div>`; const body = document.createElement("div"); body.className = "demo-body"; c.append(body); try { mount(body); } catch (err) { body.append(Object.assign(document.createElement("div"), { className: "demo-err", textContent: "demo error: " + err.message })); console.error(title, err); } return c;}
for (const [id, catKey] of ORDER) { const d = registry[id]; const cat = CATS[catKey]; if (!d) { root.append(card(id, cat.label, "#b5331f", "demo not registered — module missing or not exporting this id", () => {})); continue; } root.append(card(d.title || id, cat.label, d.accent || cat.accent, d.blurb || "", d.mount));}
// Stop sound when the tab is hidden.document.addEventListener("visibilitychange", () => { if (document.hidden) stopAll();});</script></body></html>