Restructure (2a): shared chrome on every page; merge Open=Info into one page per form factor

- Shared header/footer/chrome (src/header.html, src/footer.html, src/chrome.js)
  now on every page: editor (header above its app toolbar), player, teacher,
  stage, micro, showcase, embed. chrome.js defers to DOMContentLoaded so the
  footer version stamps regardless of placement. Player's fullscreen toggle
  relocated out of the header to a floating control.
- Open = Info: each form-factor page is self-contained — a more-detailed
  description (.about) + an expandable "Spec & BOM" (<details class="spec">,
  hidden in embed). info-*.html retired; build/deploy/README updated.

Next: teacher-style dimensioned front + top/side views + loading panels for
Stage, Micro and Showcase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Me Here 2026-05-28 09:31:39 -05:00
parent e048be390f
commit 1c74100065
18 changed files with 256 additions and 841 deletions

View file

@ -26,11 +26,14 @@ State (set lists, the practice log, theme and UI preferences) lives in `localSto
| `/teacher.html` | **PM1 Teacher** — studio / lesson console (colour TFT, arcade buttons, 1/4″ instrument passthrough with analog click injection) | | `/teacher.html` | **PM1 Teacher** — studio / lesson console (colour TFT, arcade buttons, 1/4″ instrument passthrough with analog click injection) |
| `/stage.html` | **PM1 Stage** — footpedal stompbox (two footswitches, expressionpedal in, RGB beat light, instrument passthrough) | | `/stage.html` | **PM1 Stage** — footpedal stompbox (two footswitches, expressionpedal in, RGB beat light, instrument passthrough) |
| `/micro.html` | **PMµ Micro** — inline practice bar (instrument in / out passthrough, clickable thumbroller, 14segment display) | | `/micro.html` | **PMµ Micro** — inline practice bar (instrument in / out passthrough, clickable thumbroller, 14segment display) |
| `/showcase.html` | **PMS Showcase** — pyramid display piece with an RGBlight pendulum + perlane subdivision/accent light rows | | `/showcase.html` | **PMS Showcase** — pyramid display piece; the pendulum is an RGB light bar combining every lane's subdivisions/accents |
| `/info-editor.html`, `/info-initial.html` | purpose pages (web app / concept — no BOM) |
| `/info-teacher.html`, `/info-stage.html`, `/info-micro.html`, `/info-showcase.html` | purpose **+ priced BOM** (buildable hardware only) |
| `/embed.html` · `/embed.js` | embed docs and the dropin loader | | `/embed.html` · `/embed.js` | embed docs and the dropin loader |
Each formfactor page is selfcontained ("Open" = "Info"): a moredetailed description,
the live device with front + top/side dimensioned views and loading instructions, and an
expandable **Spec & BOM** (priced, for the buildable hardware). The buildable units are the
Teacher, Stage, Micro and Showcase; the Editor (web app) and Initial (concept) have no BOM.
Each page carries the same VARASYS header (logo + tagline, nav, theme toggle). The editor Each page carries the same VARASYS header (logo + tagline, nav, theme toggle). The editor
also shows a subtle live **program string** of what's loaded — editable, with copy/paste — also shows a subtle live **program string** of what's loaded — editable, with copy/paste —
under the app (press `Enter` or paste to apply; see [the share language](#the-share-language)). under the app (press `Enter` or paste to apply; see [the share language](#the-share-language)).
@ -158,8 +161,9 @@ container and the loader script — it builds an `<iframe>` to the chromestri
- `data-width` / `data-height` — optional initial size (default `100%` × `300px`; - `data-width` / `data-height` — optional initial size (default `100%` × `300px`;
height then tracks the widget, which posts `{type:'varasys-h', h}` to the parent). height then tracks the widget, which posts `{type:'varasys-h', h}` to the parent).
Prefer your own iframe? `…/<variant>.html?embed=1#p=<patch>` works directly. Our own Prefer your own iframe? `…/<variant>.html?embed=1#p=<patch>` works directly. The
[`info-*.html`](info-teacher.html) pages dogfood this exact mechanism. See `/embed.html`. [Concepts landing](index.html) dogfoods this exact mechanism (every box is a live widget).
See `/embed.html`.
## Keyboard shortcuts ## Keyboard shortcuts
@ -211,7 +215,7 @@ in sync:
assets from `assets/`. assets from `assets/`.
`./build.sh` resolves every marker into a selfcontained page in `dist/` — the editor + `./build.sh` resolves every marker into a selfcontained page in `dist/` — the editor +
concepts + device mockups + the `info-*.html` pages — and copies `embed.js` through asis the Concepts landing + editor + device/form-factor pages — and copies `embed.js` through asis
(the editor inlines the CC0 samples; the device pages pass an empty `SAMPLES` for pure synth). (the editor inlines the CC0 samples; the device pages pass an empty `SAMPLES` for pure synth).
`dist/` is generated, gitignored — don't edit it by hand. `deploy.sh` runs the build first, `dist/` is generated, gitignored — don't edit it by hand. `deploy.sh` runs the build first,
so a deploy always serves freshly assembled pages. so a deploy always serves freshly assembled pages.
@ -235,7 +239,6 @@ Push the tag, then deploy.
| `editor.html` | the **PE1 editor** app (source, with `@BUILD:*` markers) | | `editor.html` | the **PE1 editor** app (source, with `@BUILD:*` markers) |
| `src/header.html` · `src/footer.html` · `src/chrome.js` | shared header / footer / theme chrome, inlined into every page | | `src/header.html` · `src/footer.html` · `src/chrome.js` | shared header / footer / theme chrome, inlined into every page |
| `player.html` · `teacher.html` · `stage.html` · `micro.html` · `showcase.html` | the device mockups (PM1 Initial / Teacher / Stage, PMµ Micro, PMS Showcase) | | `player.html` · `teacher.html` · `stage.html` · `micro.html` · `showcase.html` | the device mockups (PM1 Initial / Teacher / Stage, PMµ Micro, PMS Showcase) |
| `info-*.html` | performfactor info pages (purpose + priced BOM for buildable hardware) |
| `embed.html` · `embed.js` | embed docs and the dropin widget loader | | `embed.html` · `embed.js` | embed docs and the dropin widget loader |
| `src/` | shared partials inlined into every page: `engine.js`, `setlists.js`, `base.css` | | `src/` | shared partials inlined into every page: `engine.js`, `setlists.js`, `base.css` |
| `assets/` | base64 blobs inlined at build (samples, logos, favicon) | | `assets/` | base64 blobs inlined at build (samples, logos, favicon) |

View file

@ -32,8 +32,7 @@ def build(name):
return out.stat().st_size return out.stat().st_size
for name in ("index.html","editor.html","player.html","teacher.html","stage.html","micro.html","showcase.html", for name in ("index.html","editor.html","player.html","teacher.html","stage.html","micro.html","showcase.html",
"embed.html", "embed.html"):
"info-editor.html","info-initial.html","info-teacher.html","info-stage.html","info-micro.html","info-showcase.html"):
print("built %s (%dKB)" % (name, build(name) // 1024)) print("built %s (%dKB)" % (name, build(name) // 1024))
pathlib.Path("dist/embed.js").write_text(pathlib.Path("embed.js").read_text()) # loader, served as-is pathlib.Path("dist/embed.js").write_text(pathlib.Path("embed.js").read_text()) # loader, served as-is
print("copied embed.js") print("copied embed.js")

View file

@ -41,14 +41,14 @@ fi
# stamp the version into the built copy only (source stays clean) # stamp the version into the built copy only (source stays clean)
echo "deployed v$BUILD -> $DEST_DIR" echo "deployed v$BUILD -> $DEST_DIR"
for f in index.html editor.html player.html teacher.html stage.html micro.html showcase.html \ for f in index.html editor.html player.html teacher.html stage.html micro.html showcase.html \
embed.html \ embed.html; do
info-editor.html info-initial.html info-teacher.html info-stage.html info-micro.html info-showcase.html; do
sed "s|const APP_VERSION = \"[^\"]*\";|const APP_VERSION = \"$BUILD\";|" "$DIST_DIR/$f" > "$DEST_DIR/$f" sed "s|const APP_VERSION = \"[^\"]*\";|const APP_VERSION = \"$BUILD\";|" "$DIST_DIR/$f" > "$DEST_DIR/$f"
echo " $f ($(stat -c '%s' "$DEST_DIR/$f") bytes)" echo " $f ($(stat -c '%s' "$DEST_DIR/$f") bytes)"
done done
cp "$DIST_DIR/embed.js" "$DEST_DIR/embed.js"; echo " embed.js ($(stat -c '%s' "$DEST_DIR/embed.js") bytes)" cp "$DIST_DIR/embed.js" "$DEST_DIR/embed.js"; echo " embed.js ($(stat -c '%s' "$DEST_DIR/embed.js") bytes)"
rm -f "$DEST_DIR/player-asbuilt.html" # renamed to teacher.html rm -f "$DEST_DIR/player-asbuilt.html" # renamed to teacher.html
rm -f "$DEST_DIR/concepts.html" # Concepts is now the landing (/) rm -f "$DEST_DIR/concepts.html" # Concepts is now the landing (/)
rm -f "$DEST_DIR"/info-*.html # info pages merged into each form-factor page
# (stage.html / info-stage.html are deployed again — now the foot-pedal Stage stompbox) # (stage.html / info-stage.html are deployed again — now the foot-pedal Stage stompbox)
# If real audio samples are added later (see the plan's GM-sample note), # If real audio samples are added later (see the plan's GM-sample note),

View file

@ -237,21 +237,15 @@
</style> </style>
</head> </head>
<body> <body>
/*@BUILD:include:src/header.html@*/
<div id="app"> <div id="app">
<div class="device"> <div class="device">
<div class="row appheader" style="align-items:center; flex-wrap:wrap; gap:6px 14px; margin-bottom:8px"> <div class="row appheader" style="align-items:center; flex-wrap:wrap; gap:6px 14px; margin-bottom:8px">
<h1 style="margin:0">PE1 <span style="font-weight:400; opacity:.75">PolyMeter Editor</span> <span class="lane-meta" id="appVersion" title="build version">v0.0.1-dev</span></h1> <h1 style="margin:0">PE1 <span style="font-weight:400; opacity:.75">PolyMeter Editor</span></h1>
<div class="appheader-ctrls" style="display:flex; align-items:center; gap:10px"> <div class="appheader-ctrls" style="display:flex; align-items:center; gap:10px">
<a href="/" style="font-size:13px; color:var(--muted); text-decoration:none" title="Concept gallery — hardware & widget form factors">Concepts</a>
<a href="/info-editor.html" style="font-size:13px; color:var(--muted); text-decoration:none" title="About the PolyMeter Editor">Info</a>
<a href="/embed.html" style="font-size:13px; color:var(--muted); text-decoration:none" title="Embed a PolyMeter widget">Embed</a>
<button id="themeBtn" title="toggle light / dark theme"></button>
<button id="helpBtn" title="keyboard shortcuts (?)">?</button> <button id="helpBtn" title="keyboard shortcuts (?)">?</button>
<a class="brand" href="/"
title="VARASYS PolyMeter — home" style="display:inline-flex; align-items:center; flex:0 0 auto">
<img class="brand-logo brand-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" />
<img class="brand-logo brand-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS — Simplifying Complexity" />
</a>
</div> </div>
</div> </div>
<div class="kbd-legend" style="margin-bottom:12px"><span>Space play</span> <span>· T tap</span> <span>· ←→ tempo</span> <span>· ↑↓ cue</span> <span>· ⏎ commit</span> <span>· N/P step</span> <span>· A add</span> <span>· ? help</span></div> <div class="kbd-legend" style="margin-bottom:12px"><span>Space play</span> <span>· T tap</span> <span>· ←→ tempo</span> <span>· ↑↓ cue</span> <span>· ⏎ commit</span> <span>· N/P step</span> <span>· A add</span> <span>· ? help</span></div>
@ -373,6 +367,8 @@
</aside> </aside>
</div><!-- /#app --> </div><!-- /#app -->
/*@BUILD:include:src/footer.html@*/
<!-- Subtle live program string for what's loaded: editable, copy & paste. --> <!-- Subtle live program string for what's loaded: editable, copy & paste. -->
<div class="patchbar" id="patchBar" title="Program string for what's loaded — edit it and press Enter (or paste one) to apply"> <div class="patchbar" id="patchBar" title="Program string for what's loaded — edit it and press Enter (or paste one) to apply">
<label for="patchField">program</label> <label for="patchField">program</label>
@ -1194,18 +1190,7 @@ function syncStartBtn() {
else { startBtn.textContent = "▶ Start"; startBtn.classList.remove("on"); } else { startBtn.textContent = "▶ Start"; startBtn.classList.remove("on"); }
} }
function toggleShortcuts(show) { const o = $("shortcutsOverlay"); o.hidden = (show === undefined) ? !o.hidden : !show; } function toggleShortcuts(show) { const o = $("shortcutsOverlay"); o.hidden = (show === undefined) ? !o.hidden : !show; }
const THEMES = ["system", "light", "dark"]; // theme toggle + version stamp are handled by the shared chrome (src/chrome.js), wired below.
function effectiveTheme(pref) { return pref === "system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : pref; }
function themePref() { try { const p = localStorage.getItem("metronome.theme"); return (p === "light" || p === "dark" || p === "system") ? p : "system"; } catch (e) { return "system"; } }
function applyTheme(pref) {
try { localStorage.setItem("metronome.theme", pref); } catch (e) {}
document.documentElement.dataset.theme = effectiveTheme(pref);
$("themeBtn").textContent = pref === "system" ? "◐" : pref === "light" ? "☀" : "☾"; // plain glyphs (emoji ones failed to render in some browsers)
$("themeBtn").title = "Theme: " + pref + " (click to cycle: system → light → dark)";
}
$("themeBtn").addEventListener("click", () => applyTheme(THEMES[(THEMES.indexOf(themePref()) + 1) % THEMES.length]));
matchMedia("(prefers-color-scheme: light)").addEventListener("change", () => { if (themePref() === "system") applyTheme("system"); });
applyTheme(themePref());
$("startBtn").addEventListener("click", () => toggleTransport()); $("startBtn").addEventListener("click", () => toggleTransport());
let _taps = []; let _taps = [];
function tapTempo() { function tapTempo() {
@ -1343,8 +1328,8 @@ updateCtx();
refreshFeatureBoxes(); refreshFeatureBoxes();
$("continueMode").checked = continueMode; $("continueMode").checked = continueMode;
$("timersOn").checked = timersOn; $("timersOn").checked = timersOn;
$("appVersion").textContent = "v" + APP_VERSION;
requestAnimationFrame(drawLoop); requestAnimationFrame(drawLoop);
/*@BUILD:include:src/chrome.js@*/
</script> </script>
</body> </body>
</html> </html>

View file

@ -38,21 +38,7 @@
</head> </head>
<body> <body>
<header class="site-head"> /*@BUILD:include:src/header.html@*/
<div class="head-left">
<a class="brand" href="/" title="VARASYS — Simplifying Complexity">
<img class="brand-logo brand-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" />
<img class="brand-logo brand-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS — Simplifying Complexity" />
</a>
<span class="page-name"><b>PolyMeter</b> · Embed</span>
</div>
<nav class="site-nav">
<a href="/editor.html">Editor</a>
<a href="/">Concepts</a>
<span class="here">Embed</span>
<button id="themeBtn" class="tbtn" title="toggle light / dark theme"></button>
</nav>
</header>
<main> <main>
<h1>Embed a PolyMeter widget</h1> <h1>Embed a PolyMeter widget</h1>
@ -97,22 +83,12 @@
site chrome so only the widget shows. That's the same way our own Concept &amp; Info pages embed it.</p> site chrome so only the widget shows. That's the same way our own Concept &amp; Info pages embed it.</p>
</main> </main>
<div class="site-foot">VARASYS · Simplifying Complexity — <span id="appVersion">v0.0.1-dev</span></div>
<script> <script>
const APP_VERSION = "v0.0.1-dev"; const APP_VERSION = "v0.0.1-dev";
const $ = (id)=>document.getElementById(id); const $ = (id)=>document.getElementById(id);
try{ $("appVersion").textContent = "v"+APP_VERSION.replace(/^v/,""); }catch(e){} /*@BUILD:include:src/chrome.js@*/
const THEMES=["system","light","dark"];
function effectiveTheme(p){ return p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches?"light":"dark") : p; }
function themePref(){ try{ const p=localStorage.getItem("metronome.theme"); return (p==="light"||p==="dark"||p==="system")?p:"system"; }catch(e){ return "system"; } }
function applyTheme(p){ try{ localStorage.setItem("metronome.theme",p); }catch(e){}
document.documentElement.dataset.theme = effectiveTheme(p);
$("themeBtn").textContent = p==="system" ? "◐" : p==="light" ? "☀" : "☾"; $("themeBtn").title="Theme: "+p; }
$("themeBtn").onclick = ()=> applyTheme(THEMES[(THEMES.indexOf(themePref())+1)%THEMES.length]);
matchMedia("(prefers-color-scheme: light)").addEventListener("change", ()=>{ if(themePref()==="system") applyTheme("system"); });
applyTheme(themePref());
</script> </script>
<script src="/embed.js"></script> <script src="/embed.js"></script>
/*@BUILD:include:src/footer.html@*/
</body> </body>
</html> </html>

View file

@ -1,99 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PE1 PolyMeter Editor — info — VARASYS</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c;--bg2:#05070a;--txt:#c7d0db;--muted:#7f8b9a;--link:#6cb6ff;--panel-bg:#161b22;--panel-bd:#2a313c;--field-bg:#0e1116; }
:root[data-theme="light"]{ --bg1:#f5f8fc;--bg2:#dde4ec;--txt:#1e2630;--muted:#5c6776;--link:#1769c4;--panel-bg:#fff;--panel-bd:#d2dae4;--field-bg:#f1f4f8; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt); background:radial-gradient(circle at 50% -8%,var(--bg1),var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:760px; margin:26px auto 0; }
h1{ font-size:24px; margin:0 0 4px; } h2{ font-size:16px; margin:28px 0 8px; }
p{ color:var(--muted); font-size:14px; line-height:1.6; } p.lead{ max-width:62ch; color:var(--txt); }
.tags{ display:flex; gap:8px; flex-wrap:wrap; margin:8px 0 4px; }
.tag{ font-size:11px; color:var(--muted); border:1px solid var(--panel-bd); border-radius:999px; padding:2px 9px; }
.tag.app{ color:var(--cyan); border-color:rgba(10,179,247,.45); }
ul{ color:var(--muted); font-size:14px; line-height:1.7; padding-left:20px; } ul b{ color:var(--txt); }
.embed-wrap{ margin:16px 0 4px; }
.cap{ font-size:12px; color:var(--muted); }
.site-foot{ max-width:760px; margin:40px auto 0; font-size:12px; color:var(--muted); }
</style>
</head>
<body>
<header class="site-head">
<div class="head-left">
<a class="brand" href="/" title="VARASYS — Simplifying Complexity">
<img class="brand-logo brand-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" />
<img class="brand-logo brand-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS — Simplifying Complexity" />
</a>
<span class="page-name"><b>PE1</b> · PolyMeter Editor — info</span>
</div>
<nav class="site-nav">
<a href="/editor.html">Open ↗</a>
<a href="/">Concepts</a>
<a href="/embed.html">Embed</a>
<button id="themeBtn" class="tbtn" title="toggle light / dark theme"></button>
</nav>
</header>
<main>
<h1>PE1 — PolyMeter Editor</h1>
<div class="tags"><span class="tag app">Web app</span><span class="tag">Free · no install</span><span class="tag">Open source</span></div>
<p class="lead">The full polymeter workbench, and the reference design for the whole PolyMeter family: build
grooves where each voice runs in its own meter and subdivision, audition them in the browser, and save the
result as a compact program string the hardware units play back verbatim.</p>
<div class="embed-wrap">
<div data-varasys-metronome="editor"
data-patch="v1;t120;b16;kick:4=X..x;snare:4=.X.X;hatClosed:4/4;tom:3" data-height="680"></div>
</div>
<p class="cap">Live widget (embedded). <a href="/editor.html">Open the full editor ↗</a> · <a href="/embed.html">embed this</a></p>
<h2>Designed for</h2>
<p>Anyone working in odd or layered meter — practising a 4over3, programming a polyrhythmic groove, or
sketching parts before a rehearsal. It's the authoring tool: everything the hardware devices play is
created here and shared as a program string or setlist code.</p>
<ul>
<li><b>Perlane meter &amp; subdivision</b> — each voice has its own beat count and subdivision, with a
pad grid for accents, ghosts, and mutes.</li>
<li><b>Drum &amp; click voices</b> — synth and sampled sounds, swing, accent levels.</li>
<li><b>Trainer &amp; ramp</b> — barcount automation, tempo ramps, and a countdown.</li>
<li><b>Program string</b> — the live patch is shown, editable, and copy/pasteable; setlists encode to a
shareable code. This is the same string the <a href="/info-teacher.html">Teacher</a> and
<a href="/info-micro.html">Micro</a> units load.</li>
</ul>
<p class="sub" style="margin-top:18px">A web app — nothing to build, nothing to buy. (Bills of materials apply
only to the buildable hardware: <a href="/info-teacher.html">Teacher</a> and <a href="/info-micro.html">Micro</a>.)</p>
</main>
<div class="site-foot">VARASYS · Simplifying Complexity — <span id="appVersion">v0.0.1-dev</span></div>
<script>
const APP_VERSION = "v0.0.1-dev";
const $ = (id)=>document.getElementById(id);
try{ $("appVersion").textContent = "v"+APP_VERSION.replace(/^v/,""); }catch(e){}
const THEMES=["system","light","dark"];
function effectiveTheme(p){ return p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches?"light":"dark") : p; }
function themePref(){ try{ const p=localStorage.getItem("metronome.theme"); return (p==="light"||p==="dark"||p==="system")?p:"system"; }catch(e){ return "system"; } }
function applyTheme(p){ try{ localStorage.setItem("metronome.theme",p); }catch(e){}
document.documentElement.dataset.theme = effectiveTheme(p);
$("themeBtn").textContent = p==="system" ? "◐" : p==="light" ? "☀" : "☾"; $("themeBtn").title="Theme: "+p; }
$("themeBtn").onclick = ()=> applyTheme(THEMES[(THEMES.indexOf(themePref())+1)%THEMES.length]);
matchMedia("(prefers-color-scheme: light)").addEventListener("change", ()=>{ if(themePref()==="system") applyTheme("system"); });
applyTheme(themePref());
</script>
<script src="/embed.js"></script>
</body>
</html>

View file

@ -1,95 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PM1 Initial — info — VARASYS</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c;--bg2:#05070a;--txt:#c7d0db;--muted:#7f8b9a;--link:#6cb6ff;--panel-bg:#161b22;--panel-bd:#2a313c;--field-bg:#0e1116; }
:root[data-theme="light"]{ --bg1:#f5f8fc;--bg2:#dde4ec;--txt:#1e2630;--muted:#5c6776;--link:#1769c4;--panel-bg:#fff;--panel-bd:#d2dae4;--field-bg:#f1f4f8; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt); background:radial-gradient(circle at 50% -8%,var(--bg1),var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:760px; margin:26px auto 0; }
h1{ font-size:24px; margin:0 0 4px; } h2{ font-size:16px; margin:28px 0 8px; }
p{ color:var(--muted); font-size:14px; line-height:1.6; } p.lead{ max-width:62ch; color:var(--txt); }
.tags{ display:flex; gap:8px; flex-wrap:wrap; margin:8px 0 4px; }
.tag{ font-size:11px; color:var(--muted); border:1px solid var(--panel-bd); border-radius:999px; padding:2px 9px; }
.tag.concept{ color:var(--cyan); border-color:rgba(10,179,247,.45); }
.embed-wrap{ margin:16px 0 4px; }
.cap{ font-size:12px; color:var(--muted); }
.note{ margin-top:18px; padding:12px 14px; border:1px solid var(--panel-bd); border-radius:10px; background:var(--panel-bg); }
.note p{ margin:0; }
.site-foot{ max-width:760px; margin:40px auto 0; font-size:12px; color:var(--muted); }
</style>
</head>
<body>
<header class="site-head">
<div class="head-left">
<a class="brand" href="/" title="VARASYS — Simplifying Complexity">
<img class="brand-logo brand-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" />
<img class="brand-logo brand-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS — Simplifying Complexity" />
</a>
<span class="page-name"><b>PM1</b> · Initial — info</span>
</div>
<nav class="site-nav">
<a href="/editor.html">Editor</a>
<a href="/">Concepts</a>
<a href="/player.html">Open ↗</a>
<button id="themeBtn" class="tbtn" title="toggle light / dark theme"></button>
</nav>
</header>
<main>
<h1>PM1 — Initial</h1>
<div class="tags"><span class="tag concept">Concept</span><span class="tag">Idealized device</span><span class="tag">Not buildable as drawn</span></div>
<p class="lead">The idealized PM1: the player as a clean, screenfirst device with no concession to
mechanical parts yet. It's the look we design <i>toward</i> — the <a href="/info-teacher.html">Stage</a>
build is what that idea becomes once it's made from real components.</p>
<div class="embed-wrap">
<div data-varasys-metronome="initial"
data-patch="v1;t120;b16;kick:4=X.x.;snare:4=.X.X;hatClosed:4/4" data-height="560"></div>
</div>
<p class="cap">Live widget (embedded). <a href="/player.html">Open the full Initial page ↗</a> · <a href="/embed.html">embed this</a></p>
<h2>What it is</h2>
<p>A concept render of a dedicated PolyMeter player: full setlist navigation, a colour beat display showing
every lane, light/dark theming, and a fullscreen landscape "stage" view. It runs the same engine and program
strings as everything else in the family, but as an <i>idealized</i> object — before deciding which buttons,
encoders, jacks, and enclosure actually make it real.</p>
<div class="note">
<p>This is a concept, so there's <b>no bill of materials</b> — there's nothing to source for a render. The
buildable realization of this idea is the <a href="/info-teacher.html">PM1 Stage</a>, which has a full priced
BOM. For the smallest practical unit, see <a href="/info-micro.html">PMµ Micro</a>.</p>
</div>
</main>
<div class="site-foot">VARASYS · Simplifying Complexity — <span id="appVersion">v0.0.1-dev</span></div>
<script>
const APP_VERSION = "v0.0.1-dev";
const $ = (id)=>document.getElementById(id);
try{ $("appVersion").textContent = "v"+APP_VERSION.replace(/^v/,""); }catch(e){}
const THEMES=["system","light","dark"];
function effectiveTheme(p){ return p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches?"light":"dark") : p; }
function themePref(){ try{ const p=localStorage.getItem("metronome.theme"); return (p==="light"||p==="dark"||p==="system")?p:"system"; }catch(e){ return "system"; } }
function applyTheme(p){ try{ localStorage.setItem("metronome.theme",p); }catch(e){}
document.documentElement.dataset.theme = effectiveTheme(p);
$("themeBtn").textContent = p==="system" ? "◐" : p==="light" ? "☀" : "☾"; $("themeBtn").title="Theme: "+p; }
$("themeBtn").onclick = ()=> applyTheme(THEMES[(THEMES.indexOf(themePref())+1)%THEMES.length]);
matchMedia("(prefers-color-scheme: light)").addEventListener("change", ()=>{ if(themePref()==="system") applyTheme("system"); });
applyTheme(themePref());
</script>
<script src="/embed.js"></script>
</body>
</html>

View file

@ -1,118 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PMµ Micro — info &amp; BOM — VARASYS</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c;--bg2:#05070a;--txt:#c7d0db;--muted:#7f8b9a;--link:#6cb6ff;--panel-bg:#161b22;--panel-bd:#2a313c;--field-bg:#0e1116; }
:root[data-theme="light"]{ --bg1:#f5f8fc;--bg2:#dde4ec;--txt:#1e2630;--muted:#5c6776;--link:#1769c4;--panel-bg:#fff;--panel-bd:#d2dae4;--field-bg:#f1f4f8; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt); background:radial-gradient(circle at 50% -8%,var(--bg1),var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:760px; margin:26px auto 0; }
h1{ font-size:24px; margin:0 0 4px; } h2{ font-size:16px; margin:28px 0 8px; }
p{ color:var(--muted); font-size:14px; line-height:1.6; } p.lead{ max-width:62ch; color:var(--txt); }
.tags{ display:flex; gap:8px; flex-wrap:wrap; margin:8px 0 4px; }
.tag{ font-size:11px; color:var(--muted); border:1px solid var(--panel-bd); border-radius:999px; padding:2px 9px; }
.tag.hw{ color:var(--cyan); border-color:rgba(10,179,247,.45); }
.embed-wrap{ margin:16px 0 4px; }
.cap{ font-size:12px; color:var(--muted); }
.site-foot{ max-width:760px; margin:40px auto 0; font-size:12px; color:var(--muted); }
</style>
</head>
<body>
<header class="site-head">
<div class="head-left">
<a class="brand" href="/" title="VARASYS — Simplifying Complexity">
<img class="brand-logo brand-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" />
<img class="brand-logo brand-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS — Simplifying Complexity" />
</a>
<span class="page-name"><b>PMµ</b> · Micro — info</span>
</div>
<nav class="site-nav">
<a href="/editor.html">Editor</a>
<a href="/">Concepts</a>
<a href="/micro.html">Open ↗</a>
<button id="themeBtn" class="tbtn" title="toggle light / dark theme"></button>
</nav>
</header>
<main>
<h1>PMµ — Micro</h1>
<div class="tags"><span class="tag hw">Hardware</span><span class="tag">Inline practice bar</span><span class="tag">~$35 oneoff</span></div>
<p class="lead">A long, narrow practice bar you patch <i>into</i> your signal: instrument in one end, amp or
headphones out the other, the click mixed in. One clickable thumbroller does everything, an amber
14segment display shows tempo and track names, and it runs over USBC — a wall adapter or a power bank.</p>
<div class="embed-wrap">
<div data-varasys-metronome="micro"
data-patch="v1;t120;kick:4;snare:4=.X.X;hatClosed:4/2" data-height="240"></div>
</div>
<p class="cap">Live widget (embedded). <a href="/micro.html">Open the full Micro page ↗</a> · <a href="/embed.html">embed this</a></p>
<h2>Designed for</h2>
<p>Practising plugged in — at the desk or on the go. It sits inline in your signal chain: a 1/4″ TRS input on
one end, USBC and a 1/4″ TRS output on the other, with the metronome click summed into your signal in the
<b>analog domain</b> (and a small monitor speaker). No menus — a single clickable thumbroller does it all
(roll = tempo, press = start/stop, hold + roll = switch track), and the amber 14segment display shows the
BPM or the track name. Powered over USBC — a wall adapter for a permanent practicespace install, or a
pocket power bank when you're mobile (no internal battery to wear out); ships with the editor's grooves built in.</p>
<h2>Bill of materials</h2>
<p class="sub">Rough parts list — a USBCpowered RP2040 inline bar with analog click injection.
Ballpark oneoff prices (USD); cheaper at volume.</p>
<table class="bom">
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="3">Brain &amp; display</td></tr>
<tr><td class="part">RP2040 board, USBC <span class="spec">— e.g. Waveshare RP2040Zero</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">4char 14segment alphanumeric LED + I²C driver <span class="spec">— amber; HT16K33. Shows BPM &amp; track names</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">Control</td></tr>
<tr><td class="part">Clickable thumbroller <span class="spec">— EC11 encoder + roller wheel · roll / press / holdroll</span></td><td class="q">1</td><td class="c">2</td></tr>
<tr class="grp"><td colspan="3">Audio — analog click injection</td></tr>
<tr><td class="part">PCM5102A I²S DAC <span class="spec">— linelevel click</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr><td class="part">Dual opamp, NE5532 / OPA2134 <span class="spec">— hiZ instrument buffer + summing mixer</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr><td class="part">PAM8302A mono ClassD + 8 Ω speaker <span class="spec">— monitor</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">Connectors &amp; power</td></tr>
<tr><td class="part">1/4″ jack <span class="spec">— Inst In (TS) · Out (TRS)</span></td><td class="q">2</td><td class="c">2</td></tr>
<tr><td class="part">USBC bus power (5 V) + PWR LED <span class="spec">— wall adapter or power bank; also carries config</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr class="grp"><td colspan="3">Build</td></tr>
<tr><td class="part">Custom PCB (or perfboard)</td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">Passives, headers, wire <span class="spec">— R/C for the analog stage + decoupling</span></td><td class="q"></td><td class="c">2</td></tr>
<tr><td class="part">Extruded aluminium bar enclosure + end caps <span class="spec">— beadblasted, matteblack anodised</span></td><td class="q">1</td><td class="c">8</td></tr>
<tr class="total"><td>Total (oneoff)</td><td class="q"></td><td class="c">≈ $35</td></tr>
</tbody>
</table>
<p class="sub" style="margin-top:12px">Like the Teacher, the click is summed in the <b>analog domain</b>: a highimpedance
buffer of the 1/4″ instrument input is mixed with the DAC's click and sent to the 1/4″ output and the monitor
amp — your instrument is never redigitised (no added latency).</p>
</main>
<div class="site-foot">VARASYS · Simplifying Complexity — <span id="appVersion">v0.0.1-dev</span></div>
<script>
const APP_VERSION = "v0.0.1-dev";
const $ = (id)=>document.getElementById(id);
try{ $("appVersion").textContent = "v"+APP_VERSION.replace(/^v/,""); }catch(e){}
const THEMES=["system","light","dark"];
function effectiveTheme(p){ return p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches?"light":"dark") : p; }
function themePref(){ try{ const p=localStorage.getItem("metronome.theme"); return (p==="light"||p==="dark"||p==="system")?p:"system"; }catch(e){ return "system"; } }
function applyTheme(p){ try{ localStorage.setItem("metronome.theme",p); }catch(e){}
document.documentElement.dataset.theme = effectiveTheme(p);
$("themeBtn").textContent = p==="system" ? "◐" : p==="light" ? "☀" : "☾"; $("themeBtn").title="Theme: "+p; }
$("themeBtn").onclick = ()=> applyTheme(THEMES[(THEMES.indexOf(themePref())+1)%THEMES.length]);
matchMedia("(prefers-color-scheme: light)").addEventListener("change", ()=>{ if(themePref()==="system") applyTheme("system"); });
applyTheme(themePref());
</script>
<script src="/embed.js"></script>
</body>
</html>

View file

@ -1,111 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PMS Showcase — info &amp; BOM — VARASYS</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c;--bg2:#05070a;--txt:#c7d0db;--muted:#7f8b9a;--link:#6cb6ff;--panel-bg:#161b22;--panel-bd:#2a313c;--field-bg:#0e1116; }
:root[data-theme="light"]{ --bg1:#f5f8fc;--bg2:#dde4ec;--txt:#1e2630;--muted:#5c6776;--link:#1769c4;--panel-bg:#fff;--panel-bd:#d2dae4;--field-bg:#f1f4f8; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt); background:radial-gradient(circle at 50% -8%,var(--bg1),var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:760px; margin:26px auto 0; }
h1{ font-size:24px; margin:0 0 4px; } h2{ font-size:16px; margin:28px 0 8px; }
p{ color:var(--muted); font-size:14px; line-height:1.6; } p.lead{ max-width:62ch; color:var(--txt); }
.tags{ display:flex; gap:8px; flex-wrap:wrap; margin:8px 0 4px; }
.tag{ font-size:11px; color:var(--muted); border:1px solid var(--panel-bd); border-radius:999px; padding:2px 9px; }
.tag.hw{ color:var(--cyan); border-color:rgba(10,179,247,.45); }
.embed-wrap{ margin:16px 0 4px; display:flex; justify-content:center; }
.cap{ font-size:12px; color:var(--muted); text-align:center; }
.site-foot{ max-width:760px; margin:40px auto 0; font-size:12px; color:var(--muted); }
</style>
</head>
<body>
<header class="site-head">
<div class="head-left">
<a class="brand" href="/" title="VARASYS — Simplifying Complexity">
<img class="brand-logo brand-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" />
<img class="brand-logo brand-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS — Simplifying Complexity" />
</a>
<span class="page-name"><b>PMS</b> · Showcase — info</span>
</div>
<nav class="site-nav">
<a href="/editor.html">Editor</a>
<a href="/">Concepts</a>
<a href="/showcase.html">Open ↗</a>
<button id="themeBtn" class="tbtn" title="toggle light / dark theme"></button>
</nav>
</header>
<main>
<h1>PMS — Showcase</h1>
<div class="tags"><span class="tag hw">Hardware</span><span class="tag">Display piece</span><span class="tag">~$39 oneoff</span></div>
<p class="lead">A metronome as an object: the silhouette of a classic pyramid windup unit, but the swinging
pendulum is pure <b>RGB light</b> — a glowing bob easing to the beat just like the mechanical original — with
rows of light beneath it showing every lane's subdivisions, accents and mutes.</p>
<div class="embed-wrap">
<div data-varasys-metronome="showcase"
data-patch="v1;t108;kick:4=X..x;snare:4=.X.X;hatClosed:4/4;tom:3~" data-height="560"></div>
</div>
<p class="cap">Live widget (embedded). <a href="/showcase.html">Open the full Showcase page ↗</a> · <a href="/embed.html">embed this</a></p>
<h2>Designed for</h2>
<p>The shelf, the studio, the shop window — a beautiful, glanceable tempo reference that's a pleasure to watch.
The RGB pendulum swings in perfect time (decelerating to each extreme exactly as a weighted rod would), and
the segment rows turn your polymeter pattern into a light show: accents glow amber, normal steps cyan, ghosts
soft violet, mutes stay dark, and the playhead sweeps each lane. It runs the same grooves as everything else
(load any program string), plays the click through a small speaker, and is powered over USBC with a second
"thru" port to daisychain. No instrument I/O — it's a showpiece, not a signalchain tool.</p>
<h2>Bill of materials</h2>
<p class="sub">Rough parts list — a USBCpowered RP2040 display piece driving addressable RGB light.
Ballpark oneoff prices (USD); cheaper at volume.</p>
<table class="bom">
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="3">Brain</td></tr>
<tr><td class="part">RP2040 board, USBC <span class="spec">— e.g. Waveshare RP2040Zero</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">RGB light</td></tr>
<tr><td class="part">Addressable RGB LEDs (WS2812B) <span class="spec">— pendulum rod + lane segment rows, ~50 px</span></td><td class="q">1</td><td class="c">5</td></tr>
<tr><td class="part">Frosted acrylic diffuser / lightguide <span class="spec">— the glowing "pendulum" face</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr class="grp"><td colspan="3">Audio</td></tr>
<tr><td class="part">MAX98357A I²S amp + small speaker <span class="spec">— the click</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">Power &amp; build</td></tr>
<tr><td class="part">2× USBC (data+power &amp; powerthru) + PWR LED <span class="spec">— daisychain</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr><td class="part">Custom PCB (or perfboard)</td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">Passives, wire</td><td class="q"></td><td class="c">2</td></tr>
<tr><td class="part">Pyramid enclosure <span class="spec">— cast/CNC aluminium or hardwood, frosted front panel</span></td><td class="q">1</td><td class="c">14</td></tr>
<tr class="total"><td>Total (oneoff)</td><td class="q"></td><td class="c">≈ $39</td></tr>
</tbody>
</table>
</main>
<div class="site-foot">VARASYS · Simplifying Complexity — <span id="appVersion">v0.0.1-dev</span></div>
<script>
const APP_VERSION = "v0.0.1-dev";
const $ = (id)=>document.getElementById(id);
try{ $("appVersion").textContent = "v"+APP_VERSION.replace(/^v/,""); }catch(e){}
const THEMES=["system","light","dark"];
function effectiveTheme(p){ return p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches?"light":"dark") : p; }
function themePref(){ try{ const p=localStorage.getItem("metronome.theme"); return (p==="light"||p==="dark"||p==="system")?p:"system"; }catch(e){ return "system"; } }
function applyTheme(p){ try{ localStorage.setItem("metronome.theme",p); }catch(e){}
document.documentElement.dataset.theme = effectiveTheme(p);
$("themeBtn").textContent = p==="system" ? "◐" : p==="light" ? "☀" : "☾"; $("themeBtn").title="Theme: "+p; }
$("themeBtn").onclick = ()=> applyTheme(THEMES[(THEMES.indexOf(themePref())+1)%THEMES.length]);
matchMedia("(prefers-color-scheme: light)").addEventListener("change", ()=>{ if(themePref()==="system") applyTheme("system"); });
applyTheme(themePref());
</script>
<script src="/embed.js"></script>
</body>
</html>

View file

@ -1,121 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PM1 Stage — info &amp; BOM — VARASYS</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c;--bg2:#05070a;--txt:#c7d0db;--muted:#7f8b9a;--link:#6cb6ff;--panel-bg:#161b22;--panel-bd:#2a313c;--field-bg:#0e1116; }
:root[data-theme="light"]{ --bg1:#f5f8fc;--bg2:#dde4ec;--txt:#1e2630;--muted:#5c6776;--link:#1769c4;--panel-bg:#fff;--panel-bd:#d2dae4;--field-bg:#f1f4f8; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt); background:radial-gradient(circle at 50% -8%,var(--bg1),var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:760px; margin:26px auto 0; }
h1{ font-size:24px; margin:0 0 4px; } h2{ font-size:16px; margin:28px 0 8px; }
p{ color:var(--muted); font-size:14px; line-height:1.6; } p.lead{ max-width:62ch; color:var(--txt); }
.tags{ display:flex; gap:8px; flex-wrap:wrap; margin:8px 0 4px; }
.tag{ font-size:11px; color:var(--muted); border:1px solid var(--panel-bd); border-radius:999px; padding:2px 9px; }
.tag.hw{ color:var(--cyan); border-color:rgba(10,179,247,.45); }
.embed-wrap{ margin:16px 0 4px; }
.cap{ font-size:12px; color:var(--muted); }
.site-foot{ max-width:760px; margin:40px auto 0; font-size:12px; color:var(--muted); }
</style>
</head>
<body>
<header class="site-head">
<div class="head-left">
<a class="brand" href="/" title="VARASYS — Simplifying Complexity">
<img class="brand-logo brand-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" />
<img class="brand-logo brand-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS — Simplifying Complexity" />
</a>
<span class="page-name"><b>PM1</b> · Stage — info</span>
</div>
<nav class="site-nav">
<a href="/editor.html">Editor</a>
<a href="/">Concepts</a>
<a href="/stage.html">Open ↗</a>
<button id="themeBtn" class="tbtn" title="toggle light / dark theme"></button>
</nav>
</header>
<main>
<h1>PM1 — Stage</h1>
<div class="tags"><span class="tag hw">Hardware</span><span class="tag">Footpedal stompbox</span><span class="tag">~$52 oneoff</span></div>
<p class="lead">A footoperated polymeter stompbox for the stage: drive it handsfree with two heavy
footswitches and an expression pedal, read it off the floor from a big RGB beat light, and run your
instrument through it with the click mixed in. (For a desk/lesson unit with a full screen, see the
<a href="/teacher.html">Teacher</a>.)</p>
<div class="embed-wrap">
<div data-varasys-metronome="stage"
data-patch="v1;t120;b16;kick:4=X.x.;snare:4=.X.X;hatClosed:4/4" data-height="430"></div>
</div>
<p class="cap">Live widget (embedded). <a href="/stage.html">Open the full Stage page ↗</a> · <a href="/embed.html">embed this</a></p>
<h2>Designed for</h2>
<p>Live use, handsfree. The controls are built for feet: the <b>left footswitch</b> taps tempo (hold to
start/stop), the <b>right</b> steps through your set list (hold for previous), and a <b>1/4″ expressionpedal
input</b> sweeps tempo on the fly. A large RGB <b>beat light</b> is readable from standing height, with a
small angled TFT for the BPM, item name and beat. Your instrument passes through (1/4″ in) with the click
summed in the <b>analog domain</b> and sent to a balanced 1/4″ TRS out for the desk. Powered over USBC —
with a <b>second USBC "thru" port</b> so several pedals daisychain off one charger or power bank.</p>
<h2>Bill of materials</h2>
<p class="sub">Rough parts list — a footoperated RP2040 stompbox (USBC, dualport) with analog click injection.
Ballpark oneoff prices (USD); cheaper at volume.</p>
<table class="bom">
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="3">Brain &amp; display</td></tr>
<tr><td class="part">RP2040 board, USBC <span class="spec">— e.g. Waveshare RP2040Zero</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">1.3″ IPS TFT, ST7789 <span class="spec">— SPI; angled BPM / item readout</span></td><td class="q">1</td><td class="c">6</td></tr>
<tr><td class="part">Highbright diffused RGB beat indicator <span class="spec">— floorreadable</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr class="grp"><td colspan="3">Controls</td></tr>
<tr><td class="part">Heavyduty momentary footswitch (softtouch) <span class="spec">— Tap · Next</span></td><td class="q">2</td><td class="c">6</td></tr>
<tr><td class="part">1/4″ expressionpedal input jack (TRS) <span class="spec">— tempo sweep</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr class="grp"><td colspan="3">Audio — analog click injection</td></tr>
<tr><td class="part">PCM5102A I²S DAC <span class="spec">— linelevel click</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr><td class="part">Dual opamp, NE5532 / OPA2134 <span class="spec">— hiZ instrument buffer + summing mixer</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr><td class="part">Balanced line driver, DRV134 <span class="spec">— → 1/4″ TRS out</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">Connectors &amp; power</td></tr>
<tr><td class="part">1/4″ jack <span class="spec">— Inst In (TS) · Out (TRS) · Trig In (TS)</span></td><td class="q">3</td><td class="c">3</td></tr>
<tr><td class="part">2× USBC (data+power &amp; powerthru) + powerpath/protection + PWR LED <span class="spec">— daisychain pedals</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr class="grp"><td colspan="3">Build</td></tr>
<tr><td class="part">Custom PCB (or perfboard)</td><td class="q">1</td><td class="c">5</td></tr>
<tr><td class="part">Passives, headers, wire <span class="spec">— R/C for the analog stage + decoupling</span></td><td class="q"></td><td class="c">3</td></tr>
<tr><td class="part">Diecast aluminium stompbox (Hammond 1590BBstyle) <span class="spec">— beadblasted, matteblack Type II anodise, laseretched</span></td><td class="q">1</td><td class="c">12</td></tr>
<tr class="total"><td>Total (oneoff)</td><td class="q"></td><td class="c">≈ $52</td></tr>
</tbody>
</table>
<p class="sub" style="margin-top:12px">No builtin speaker — the Stage feeds your amp / PA. The click is summed in the
<b>analog domain</b> (hiZ instrument buffer + DAC → balanced line driver), so your instrument is never
redigitised (no added latency).</p>
</main>
<div class="site-foot">VARASYS · Simplifying Complexity — <span id="appVersion">v0.0.1-dev</span></div>
<script>
const APP_VERSION = "v0.0.1-dev";
const $ = (id)=>document.getElementById(id);
try{ $("appVersion").textContent = "v"+APP_VERSION.replace(/^v/,""); }catch(e){}
const THEMES=["system","light","dark"];
function effectiveTheme(p){ return p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches?"light":"dark") : p; }
function themePref(){ try{ const p=localStorage.getItem("metronome.theme"); return (p==="light"||p==="dark"||p==="system")?p:"system"; }catch(e){ return "system"; } }
function applyTheme(p){ try{ localStorage.setItem("metronome.theme",p); }catch(e){}
document.documentElement.dataset.theme = effectiveTheme(p);
$("themeBtn").textContent = p==="system" ? "◐" : p==="light" ? "☀" : "☾"; $("themeBtn").title="Theme: "+p; }
$("themeBtn").onclick = ()=> applyTheme(THEMES[(THEMES.indexOf(themePref())+1)%THEMES.length]);
matchMedia("(prefers-color-scheme: light)").addEventListener("change", ()=>{ if(themePref()==="system") applyTheme("system"); });
applyTheme(themePref());
</script>
<script src="/embed.js"></script>
</body>
</html>

View file

@ -1,121 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PM1 Teacher — info &amp; BOM — VARASYS</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
<script>
(function(){ try{ var p = localStorage.getItem("metronome.theme");
if (p!=="light" && p!=="dark" && p!=="system") p = "system";
document.documentElement.dataset.theme = p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p;
} catch(e){ document.documentElement.dataset.theme = "dark"; } })();
</script>
<style>
/*@BUILD:include:src/base.css@*/
:root{ --bg1:#12151c;--bg2:#05070a;--txt:#c7d0db;--muted:#7f8b9a;--link:#6cb6ff;--panel-bg:#161b22;--panel-bd:#2a313c;--field-bg:#0e1116; }
:root[data-theme="light"]{ --bg1:#f5f8fc;--bg2:#dde4ec;--txt:#1e2630;--muted:#5c6776;--link:#1769c4;--panel-bg:#fff;--panel-bd:#d2dae4;--field-bg:#f1f4f8; }
body{ margin:0; min-height:100vh; padding:22px 16px 56px; color:var(--txt); background:radial-gradient(circle at 50% -8%,var(--bg1),var(--bg2)); }
a{ color:var(--link); }
main{ width:100%; max-width:760px; margin:26px auto 0; }
h1{ font-size:24px; margin:0 0 4px; } h2{ font-size:16px; margin:28px 0 8px; }
p{ color:var(--muted); font-size:14px; line-height:1.6; } p.lead{ max-width:62ch; color:var(--txt); }
.tags{ display:flex; gap:8px; flex-wrap:wrap; margin:8px 0 4px; }
.tag{ font-size:11px; color:var(--muted); border:1px solid var(--panel-bd); border-radius:999px; padding:2px 9px; }
.tag.hw{ color:var(--cyan); border-color:rgba(10,179,247,.45); }
.embed-wrap{ margin:16px 0 4px; }
.cap{ font-size:12px; color:var(--muted); }
.site-foot{ max-width:760px; margin:40px auto 0; font-size:12px; color:var(--muted); }
</style>
</head>
<body>
<header class="site-head">
<div class="head-left">
<a class="brand" href="/" title="VARASYS — Simplifying Complexity">
<img class="brand-logo brand-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" />
<img class="brand-logo brand-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS — Simplifying Complexity" />
</a>
<span class="page-name"><b>PM1</b> · Teacher — info</span>
</div>
<nav class="site-nav">
<a href="/editor.html">Editor</a>
<a href="/">Concepts</a>
<a href="/teacher.html">Open ↗</a>
<button id="themeBtn" class="tbtn" title="toggle light / dark theme"></button>
</nav>
</header>
<main>
<h1>PM1 — Teacher</h1>
<div class="tags"><span class="tag hw">Hardware</span><span class="tag">Studio / lesson console</span><span class="tag">~$59 oneoff</span></div>
<p class="lead">The fullfeature desktop console: a colour readout of every lane, fast setlist navigation, and
your instrument running straight through with the click mixed in — the handson unit for a studio desk or a
teaching room, on a nonreflective matteblack case. (For handsfree live use, see the footoperated
<a href="/stage.html">Stage</a> stompbox.)</p>
<div class="embed-wrap">
<div data-varasys-metronome="teacher"
data-patch="v1;t120;b16;kick:4=X.x.;snare:4=.X.X;hatClosed:4/4" data-height="520"></div>
</div>
<p class="cap">Live widget (embedded). <a href="/teacher.html">Open the full Teacher page ↗</a> · <a href="/embed.html">embed this</a></p>
<h2>Designed for</h2>
<p>The studio desk and the teaching room. Topmounted 1/4″ jacks keep cabling tidy; you plug your
instrument in, the metronome click is summed into the signal in the <b>analog domain</b> (no redigitising,
no added latency) and sent to a balanced 1/4″ TRS output for the desk or interface, plus a small monitor
speaker. Powered over USBC — a wall adapter, or a power bank. The colour TFT shows tempo, the item name,
and all lane patterns; arcade buttons + a recessed thumbroller make it quick to drive while you teach or track.</p>
<h2>Bill of materials</h2>
<p class="sub">Rough parts list — a desk/studio RP2040 build (USBC powered) with analog click injection.
Ballpark oneoff prices (USD); cheaper at volume.</p>
<table class="bom">
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="3">Brain &amp; display</td></tr>
<tr><td class="part">RP2040 board, USBC <span class="spec">— e.g. Waveshare RP2040Zero / Picoclone</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">2.0″ 320×240 IPS TFT, ST7789 <span class="spec">— SPI</span></td><td class="q">1</td><td class="c">8</td></tr>
<tr class="grp"><td colspan="3">Controls</td></tr>
<tr><td class="part">Arcade pushbutton, 24 mm <span class="spec">— Prev · Next · Tap</span></td><td class="q">3</td><td class="c">4</td></tr>
<tr><td class="part">Arcade pushbutton, 30 mm <span class="spec">— Play</span></td><td class="q">1</td><td class="c">2</td></tr>
<tr><td class="part">Detented encoder (EC11 / PEC12) + sidemount thumbroller <span class="spec">— recessed; nothing to snap off</span></td><td class="q">1</td><td class="c">2</td></tr>
<tr class="grp"><td colspan="3">Audio — analog click injection</td></tr>
<tr><td class="part">PCM5102A I²S DAC <span class="spec">— linelevel click</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr><td class="part">Dual opamp, NE5532 / OPA2134 <span class="spec">— hiZ instrument buffer + summing mixer</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr><td class="part">Balanced line driver, DRV134 <span class="spec">— (or crosscoupled opamp) → 1/4″ TRS out</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">PAM8302A mono ClassD + 8 Ω 2 W speaker <span class="spec">— monitor</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">Connectors &amp; power</td></tr>
<tr><td class="part">1/4″ jack <span class="spec">— Inst In (TS) · Out (TRS) · Trig In (TS)</span></td><td class="q">3</td><td class="c">3</td></tr>
<tr><td class="part">USBC bus power (5 V) + PWR LED <span class="spec">— wall adapter or power bank; same port carries config; no battery</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr class="grp"><td colspan="3">Build</td></tr>
<tr><td class="part">Custom PCB (or perfboard)</td><td class="q">1</td><td class="c">5</td></tr>
<tr><td class="part">Passives, headers, wire <span class="spec">— R/C for the analog stage + decoupling</span></td><td class="q"></td><td class="c">3</td></tr>
<tr><td class="part">Diecast aluminium enclosure (Hammond 1590style) <span class="spec">— beadblasted, matteblack Type II anodise, laseretched legends</span></td><td class="q">1</td><td class="c">12</td></tr>
<tr class="total"><td>Total (oneoff)</td><td class="q"></td><td class="c">≈ $56</td></tr>
</tbody>
</table>
<p class="sub" style="margin-top:12px">Audio is summed in the <b>analog domain</b>: the DAC's click is mixed with a highimpedance
buffer of the 1/4″ instrument input, then fed to the balanced line driver (1/4″ TRS out) and the monitor amp —
so your instrument is never redigitised (no added latency).</p>
</main>
<div class="site-foot">VARASYS · Simplifying Complexity — <span id="appVersion">v0.0.1-dev</span></div>
<script>
const APP_VERSION = "v0.0.1-dev";
const $ = (id)=>document.getElementById(id);
try{ $("appVersion").textContent = "v"+APP_VERSION.replace(/^v/,""); }catch(e){}
const THEMES=["system","light","dark"];
function effectiveTheme(p){ return p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches?"light":"dark") : p; }
function themePref(){ try{ const p=localStorage.getItem("metronome.theme"); return (p==="light"||p==="dark"||p==="system")?p:"system"; }catch(e){ return "system"; } }
function applyTheme(p){ try{ localStorage.setItem("metronome.theme",p); }catch(e){}
document.documentElement.dataset.theme = effectiveTheme(p);
$("themeBtn").textContent = p==="system" ? "◐" : p==="light" ? "☀" : "☾"; $("themeBtn").title="Theme: "+p; }
$("themeBtn").onclick = ()=> applyTheme(THEMES[(THEMES.indexOf(themePref())+1)%THEMES.length]);
matchMedia("(prefers-color-scheme: light)").addEventListener("change", ()=>{ if(themePref()==="system") applyTheme("system"); });
applyTheme(themePref());
</script>
<script src="/embed.js"></script>
</body>
</html>

View file

@ -125,22 +125,7 @@
</head> </head>
<body> <body>
<header class="site-head"> /*@BUILD:include:src/header.html@*/
<div class="head-left">
<a class="brand" href="/" title="VARASYS — Simplifying Complexity">
<img class="brand-logo brand-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" />
<img class="brand-logo brand-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS — Simplifying Complexity" />
</a>
<span class="page-name"><b>PMµ</b> · Micro (inline practice bar)</span>
</div>
<nav class="site-nav">
<a href="/editor.html">Editor</a>
<a href="/">Concepts</a>
<a href="/info-micro.html">Info</a>
<a href="/embed.html">Embed</a>
<button id="themeBtn" class="tbtn" title="toggle light / dark theme"></button>
</nav>
</header>
<div class="device"> <div class="device">
<!-- LEFT END: instrument / aux in --> <!-- LEFT END: instrument / aux in -->
@ -309,15 +294,7 @@ function commitTrack(){ loadTrack(previewIdx); displayMode="track"; render(); cl
})(); })();
/* theme toggle — cycles system → light → dark; shares the "metronome.theme" key */ /* theme toggle — cycles system → light → dark; shares the "metronome.theme" key */
const THEMES=["system","light","dark"]; /*@BUILD:include:src/chrome.js@*/
function effectiveTheme(p){ return p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches?"light":"dark") : p; }
function themePref(){ try{ const p=localStorage.getItem("metronome.theme"); return (p==="light"||p==="dark"||p==="system")?p:"system"; }catch(e){ return "system"; } }
function applyTheme(p){ try{ localStorage.setItem("metronome.theme",p); }catch(e){}
document.documentElement.dataset.theme = effectiveTheme(p);
$("themeBtn").textContent = p==="system" ? "◐" : p==="light" ? "☀" : "☾"; $("themeBtn").title="Theme: "+p+" (system → light → dark)"; }
$("themeBtn").onclick = ()=> applyTheme(THEMES[(THEMES.indexOf(themePref())+1)%THEMES.length]);
matchMedia("(prefers-color-scheme: light)").addEventListener("change", ()=>{ if(themePref()==="system") applyTheme("system"); });
applyTheme(themePref());
addEventListener("keydown",(e)=>{ addEventListener("keydown",(e)=>{
const tag=e.target?e.target.tagName:""; if(tag==="INPUT"||tag==="TEXTAREA"||tag==="SELECT") return; const tag=e.target?e.target.tagName:""; if(tag==="INPUT"||tag==="TEXTAREA"||tag==="SELECT") return;
@ -334,5 +311,48 @@ addEventListener("keydown",(e)=>{
loadTrack(0); loadTrack(0);
render(); render();
</script> </script>
<section class="about pageonly">
<h2>PMµ — Micro</h2>
<div class="ff-tags"><span class="hw">Hardware</span><span>Inline practice bar</span><span>~$35 oneoff</span></div>
<p>A long, narrow practice bar you patch <i>into</i> your signal: instrument in one end, amp or headphones out
the other, the click mixed in. One clickable thumbroller does everything (roll = tempo, press = start/stop,
hold + roll = switch track), and an amber 14segment display shows tempo and track names.</p>
<p>The click is summed into your signal in the <b>analog domain</b> (plus a small monitor speaker). Powered over
USBC — a wall adapter for a permanent practicespace install, or a pocket power bank when you're mobile (no
internal battery to wear out); ships with the editor's grooves built in.</p>
</section>
<details class="spec pageonly">
<summary>Spec &amp; bill of materials</summary>
<div class="spec-body">
<p class="sub">Rough parts list — a USBCpowered RP2040 inline bar with analog click injection.
Ballpark oneoff prices (USD); cheaper at volume.</p>
<table class="bom">
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="3">Brain &amp; display</td></tr>
<tr><td class="part">RP2040 board, USBC <span class="spec">— e.g. Waveshare RP2040Zero</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">4char 14segment alphanumeric LED + I²C driver <span class="spec">— amber; HT16K33. Shows BPM &amp; track names</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">Control</td></tr>
<tr><td class="part">Clickable thumbroller <span class="spec">— EC11 encoder + roller wheel · roll / press / holdroll</span></td><td class="q">1</td><td class="c">2</td></tr>
<tr class="grp"><td colspan="3">Audio — analog click injection</td></tr>
<tr><td class="part">PCM5102A I²S DAC <span class="spec">— linelevel click</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr><td class="part">Dual opamp, NE5532 / OPA2134 <span class="spec">— hiZ instrument buffer + summing mixer</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr><td class="part">PAM8302A mono ClassD + 8 Ω speaker <span class="spec">— monitor</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">Connectors &amp; power</td></tr>
<tr><td class="part">1/4″ jack <span class="spec">— Inst In (TS) · Out (TRS)</span></td><td class="q">2</td><td class="c">2</td></tr>
<tr><td class="part">USBC bus power (5 V) + PWR LED <span class="spec">— wall adapter or power bank; also carries config</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr class="grp"><td colspan="3">Build</td></tr>
<tr><td class="part">Custom PCB (or perfboard)</td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">Passives, headers, wire <span class="spec">— R/C for the analog stage + decoupling</span></td><td class="q"></td><td class="c">2</td></tr>
<tr><td class="part">Extruded aluminium bar enclosure + end caps <span class="spec">— beadblasted, matteblack anodised</span></td><td class="q">1</td><td class="c">8</td></tr>
<tr class="total"><td>Total (oneoff)</td><td class="q"></td><td class="c">≈ $35</td></tr>
</tbody>
</table>
</div>
</details>
/*@BUILD:include:src/footer.html@*/
</body> </body>
</html> </html>

View file

@ -198,29 +198,21 @@
background:var(--bg1); color:var(--txt); font-size:20px; text-align:center; padding:24px } background:var(--bg1); color:var(--txt); font-size:20px; text-align:center; padding:24px }
body.stage .rotate-hint .rh-icon{ font-size:64px; line-height:1; color:var(--cyan) } body.stage .rotate-hint .rh-icon{ font-size:64px; line-height:1; color:var(--cyan) }
} }
/* fullscreen toggle (relocated out of the shared header) */
.fs-float{ background:transparent; color:var(--muted); border:1px solid var(--panel-bd); border-radius:8px;
padding:4px 11px; font-size:15px; line-height:1; cursor:pointer; }
.fs-float:hover{ color:var(--txt); }
body.stage .fs-float{ display:none; }
/* embed mode: just the device */ /* embed mode: just the device */
[data-embed] .panel { display:none !important; } [data-embed] .panel, [data-embed] .fs-float { display:none !important; }
</style> </style>
</head> </head>
<body> <body>
<header class="site-head"> /*@BUILD:include:src/header.html@*/
<div class="head-left">
<a class="brand" href="/" title="VARASYS — Simplifying Complexity"> <!-- fullscreen "stage mode" toggle — floats over the page (was a header button before the shared header) -->
<img class="brand-logo brand-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" /> <button id="fsBtn" class="fs-float" title="Full screen (landscape)"></button>
<img class="brand-logo brand-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS — Simplifying Complexity" />
</a>
<span class="page-name"><b>PM1</b> · Initial concept</span>
</div>
<nav class="site-nav">
<a href="/editor.html">Editor</a>
<a href="/">Concepts</a>
<a href="/info-initial.html">Info</a>
<a href="/embed.html">Embed</a>
<button id="fsBtn" class="tbtn" title="Full screen (landscape)"></button>
<button id="themeBtn" class="tbtn" title="toggle light / dark theme"></button>
</nav>
</header>
<!-- ===================== THE DEVICE ===================== --> <!-- ===================== THE DEVICE ===================== -->
<div class="device"> <div class="device">
@ -442,21 +434,7 @@ $("storedSel").onchange=(e)=>{ const v=e.target.value; if(!v) return;
const sl = v[0]==="b" ? e.target._builtin[+v.slice(1)] : e.target._lists[+v.slice(1)]; if(!sl) return; const sl = v[0]==="b" ? e.target._builtin[+v.slice(1)] : e.target._lists[+v.slice(1)]; if(!sl) return;
loadSetlistObj({title:sl.title,items:(sl.items||[]).map(it=>({...it}))}); setStatus("✓ Loaded set list “"+(sl.title||"set list")+"”.",true); }; loadSetlistObj({title:sl.title,items:(sl.items||[]).map(it=>({...it}))}); setStatus("✓ Loaded set list “"+(sl.title||"set list")+"”.",true); };
/* theme toggle — cycles system → light → dark; shares the editor's "metronome.theme" key */ /* theme toggle — cycles system → light → dark; shares the editor's "metronome.theme" key */
const THEMES = ["system","light","dark"]; /*@BUILD:include:src/chrome.js@*/
function effectiveTheme(p){ return p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches?"light":"dark") : p; }
function themePref(){ try{ const p=localStorage.getItem("metronome.theme"); return (p==="light"||p==="dark"||p==="system")?p:"system"; }catch(e){ return "system"; } }
function applyTheme(p){
try{ localStorage.setItem("metronome.theme",p); }catch(e){}
document.documentElement.dataset.theme = effectiveTheme(p);
const glyph = p==="system" ? "◐" : p==="light" ? "☀" : "☾";
const title = "Theme: "+p+" (click to cycle: system → light → dark)";
for(const id of ["themeBtn","fsThemeBtn"]){ const b=$(id); if(b){ b.textContent=glyph; b.title=title; } }
}
const cycleTheme = ()=> applyTheme(THEMES[(THEMES.indexOf(themePref())+1)%THEMES.length]);
$("themeBtn").onclick = cycleTheme;
$("fsThemeBtn").onclick = cycleTheme;
matchMedia("(prefers-color-scheme: light)").addEventListener("change", ()=>{ if(themePref()==="system") applyTheme("system"); });
applyTheme(themePref());
/* full-screen "stage" mode — real fullscreen + landscape lock where supported (Android/desktop), /* full-screen "stage" mode — real fullscreen + landscape lock where supported (Android/desktop),
CSS pseudo-fullscreen + a rotate hint where not (iPhone). body.stage drives the layout. */ CSS pseudo-fullscreen + a rotate hint where not (iPhone). body.stage drives the layout. */
@ -510,5 +488,19 @@ if(!setlist){ setlist=BUILTIN[0]; idx=0; loadSetup(setlist.items[0]); }
renderAll(); renderAll();
requestAnimationFrame(draw); requestAnimationFrame(draw);
</script> </script>
<section class="about pageonly">
<h2>PM1 — Initial</h2>
<div class="ff-tags"><span>Concept</span><span>Idealized device</span><span>Not buildable as drawn</span></div>
<p>The idealized PM1: the player as a clean, screenfirst device with no concession to mechanical parts yet.
It's the look we design <i>toward</i> — full setlist navigation, a colour beat display showing every lane,
light/dark theming, and a fullscreen landscape "stage" view. It runs the same engine and program strings as
everything else in the family, but as an <i>idealized</i> object, before deciding which buttons, encoders,
jacks and enclosure actually make it real.</p>
<p>Because it's a concept, there's <b>no bill of materials</b> — there's nothing to source for a render. The
buildable realization of this idea is the <a href="/teacher.html">PM1 Teacher</a> (full priced BOM there);
for the smallest practical unit, see the <a href="/micro.html">PMµ Micro</a>.</p>
</section>
/*@BUILD:include:src/footer.html@*/
</body> </body>
</html> </html>

View file

@ -69,6 +69,47 @@
<div class="hint">The pendulum <b>is</b> the display: every lane's subdivisions &amp; accents ride along the bar as <div class="hint">The pendulum <b>is</b> the display: every lane's subdivisions &amp; accents ride along the bar as
moving RGB light. Drag the <b>weight</b> up/down (or scroll) to set tempo — the scale is printed on the bar, moving RGB light. Drag the <b>weight</b> up/down (or scroll) to set tempo — the scale is printed on the bar,
just like a windup metronome. (No power switch: the real one starts when you lift it from its holder.)</div> just like a windup metronome. (No power switch: the real one starts when you lift it from its holder.)</div>
<section class="about pageonly">
<h2>PMS — Showcase</h2>
<div class="ff-tags"><span class="hw">Hardware</span><span>Display piece</span><span>~$41 oneoff</span></div>
<p>A metronome as an object: the silhouette of a classic pyramid windup unit, but the swinging pendulum is
pure <b>RGB light</b>. The whole bar is the display — every lane's subdivisions &amp; accents ride along its
length as moving points of light (all meters combined), a printed tempo scale runs up the vertical axis,
and a sliding <b>weight</b> sets the tempo just like the mechanical original.</p>
<p>It's a beautiful, glanceable tempo reference for the shelf, the studio, or a shop window: accents glow
amber, normal steps cyan, ghosts soft violet, and the pendulum eases to each beat exactly as a weighted rod
would. It runs the same grooves as everything else (load any program string), plays the click through a
small speaker, and is powered over USBC with a second "thru" port to daisychain. There's no power switch —
the real unit starts when you lift it from its holder / set it swinging. No instrument I/O; it's a showpiece.</p>
</section>
<details class="spec pageonly">
<summary>Spec &amp; bill of materials</summary>
<div class="spec-body">
<p class="sub">Rough parts list — a USBCpowered RP2040 display piece driving addressable RGB light.
Ballpark oneoff prices (USD); cheaper at volume.</p>
<table class="bom">
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="3">Brain</td></tr>
<tr><td class="part">RP2040 board, USBC <span class="spec">— e.g. Waveshare RP2040Zero</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">RGB light</td></tr>
<tr><td class="part">Addressable RGB LEDs (WS2812B) <span class="spec">— a strip down the pendulum bar, ~40 px</span></td><td class="q">1</td><td class="c">5</td></tr>
<tr><td class="part">Frosted acrylic diffuser / lightguide <span class="spec">— the glowing pendulum bar</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr class="grp"><td colspan="3">Audio</td></tr>
<tr><td class="part">MAX98357A I²S amp + small speaker <span class="spec">— the click</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">Power &amp; build</td></tr>
<tr><td class="part">2× USBC (data+power &amp; powerthru) + PWR LED <span class="spec">— daisychain</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr><td class="part">Tilt / lift sensor (accelerometer) <span class="spec">— starts when lifted from its holder</span></td><td class="q">1</td><td class="c">2</td></tr>
<tr><td class="part">Custom PCB (or perfboard)</td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">Passives, wire</td><td class="q"></td><td class="c">2</td></tr>
<tr><td class="part">Pyramid enclosure <span class="spec">— cast/CNC aluminium or hardwood, frosted front panel</span></td><td class="q">1</td><td class="c">14</td></tr>
<tr class="total"><td>Total (oneoff)</td><td class="q"></td><td class="c">≈ $41</td></tr>
</tbody>
</table>
</div>
</details>
</main> </main>
/*@BUILD:include:src/footer.html@*/ /*@BUILD:include:src/footer.html@*/

View file

@ -63,3 +63,13 @@ details.spec > summary::before { content:"▸"; color:var(--muted,#7f8b9a); tran
details.spec[open] > summary::before { transform:rotate(90deg); } details.spec[open] > summary::before { transform:rotate(90deg); }
details.spec .spec-body { padding:2px 16px 16px; } details.spec .spec-body { padding:2px 16px 16px; }
[data-embed] details.spec { display:none !important; } [data-embed] details.spec { display:none !important; }
/* ---- per-form-factor page: the description above the expandable spec ---- */
.about { width:100%; max-width:760px; margin:20px auto 0; color:var(--muted,#7f8b9a); font-size:14px; line-height:1.62; }
.about h2 { color:var(--txt,#c7d0db); font-size:18px; margin:0 0 4px; }
.about .ff-tags { display:flex; gap:8px; flex-wrap:wrap; margin:6px 0 10px; }
.about .ff-tags span { font-size:11px; color:var(--muted,#7f8b9a); border:1px solid var(--panel-bd,#2a313c); border-radius:999px; padding:2px 9px; }
.about .ff-tags .hw { color:var(--cyan); border-color:rgba(10,179,247,.45); }
.about p { margin:0 0 10px; max-width:64ch; }
/* page-only chrome (description, dimensioned views, loading panel) — hidden when embedded */
[data-embed] .pageonly { display:none !important; }

View file

@ -4,7 +4,6 @@
the page (deploy.sh rewrites that line) and the header/footer markup present. */ the page (deploy.sh rewrites that line) and the header/footer markup present. */
(function () { (function () {
var byId = function (id) { return document.getElementById(id); }; var byId = function (id) { return document.getElementById(id); };
try { var v = byId("appVersion"); if (v && typeof APP_VERSION !== "undefined") v.textContent = "v" + APP_VERSION.replace(/^v/, ""); } catch (e) {}
var THEMES = ["system", "light", "dark"]; var THEMES = ["system", "light", "dark"];
function eff(p) { return p === "system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p; } function eff(p) { return p === "system" ? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") : p; }
function pref() { try { var p = localStorage.getItem("metronome.theme"); return (p === "light" || p === "dark" || p === "system") ? p : "system"; } catch (e) { return "system"; } } function pref() { try { var p = localStorage.getItem("metronome.theme"); return (p === "light" || p === "dark" || p === "system") ? p : "system"; } catch (e) { return "system"; } }
@ -13,7 +12,11 @@
document.documentElement.dataset.theme = eff(p); document.documentElement.dataset.theme = eff(p);
var b = byId("themeBtn"); if (b) { b.textContent = p === "system" ? "◐" : p === "light" ? "☀" : "☾"; b.title = "Theme: " + p + " (system → light → dark)"; } var b = byId("themeBtn"); if (b) { b.textContent = p === "system" ? "◐" : p === "light" ? "☀" : "☾"; b.title = "Theme: " + p + " (system → light → dark)"; }
} }
var btn = byId("themeBtn"); if (btn) btn.onclick = function () { apply(THEMES[(THEMES.indexOf(pref()) + 1) % THEMES.length]); }; function init() {
try { var v = byId("appVersion"); if (v && typeof APP_VERSION !== "undefined") v.textContent = "v" + APP_VERSION.replace(/^v/, ""); } catch (e) {}
var btn = byId("themeBtn"); if (btn) btn.onclick = function () { apply(THEMES[(THEMES.indexOf(pref()) + 1) % THEMES.length]); };
apply(pref());
}
try { matchMedia("(prefers-color-scheme: light)").addEventListener("change", function () { if (pref() === "system") apply("system"); }); } catch (e) {} try { matchMedia("(prefers-color-scheme: light)").addEventListener("change", function () { if (pref() === "system") apply("system"); }); } catch (e) {}
apply(pref()); if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init); else init();
})(); })();

View file

@ -109,22 +109,7 @@
</head> </head>
<body> <body>
<header class="site-head"> /*@BUILD:include:src/header.html@*/
<div class="head-left">
<a class="brand" href="/" title="VARASYS — Simplifying Complexity">
<img class="brand-logo brand-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" />
<img class="brand-logo brand-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS — Simplifying Complexity" />
</a>
<span class="page-name"><b>PM1</b> · Stage (footpedal stompbox)</span>
</div>
<nav class="site-nav">
<a href="/editor.html">Editor</a>
<a href="/">Concepts</a>
<a href="/info-stage.html">Info</a>
<a href="/embed.html">Embed</a>
<button id="themeBtn" class="tbtn" title="toggle light / dark theme"></button>
</nav>
</header>
<div class="device"> <div class="device">
<!-- top edge: all jacks, including dual USB-C daisy-chain power --> <!-- top edge: all jacks, including dual USB-C daisy-chain power -->
@ -297,15 +282,7 @@ holdSwitch($("swNext"), ()=>loadTrack(trackIdx+1), ()=>loadTrack(trackIdx-1));
$("expPedal").addEventListener("input", (e)=>{ setBpm(+e.target.value); }); $("expPedal").addEventListener("input", (e)=>{ setBpm(+e.target.value); });
/* theme toggle (shared "metronome.theme") */ /* theme toggle (shared "metronome.theme") */
const THEMES=["system","light","dark"]; /*@BUILD:include:src/chrome.js@*/
function effTheme(p){ return p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches?"light":"dark") : p; }
function themePref(){ try{ const p=localStorage.getItem("metronome.theme"); return (p==="light"||p==="dark"||p==="system")?p:"system"; }catch(e){ return "system"; } }
function applyTheme(p){ try{ localStorage.setItem("metronome.theme",p); }catch(e){}
document.documentElement.dataset.theme = effTheme(p);
$("themeBtn").textContent = p==="system" ? "◐" : p==="light" ? "☀" : "☾"; $("themeBtn").title="Theme: "+p; }
$("themeBtn").onclick = ()=> applyTheme(THEMES[(THEMES.indexOf(themePref())+1)%THEMES.length]);
matchMedia("(prefers-color-scheme: light)").addEventListener("change", ()=>{ if(themePref()==="system") applyTheme("system"); });
applyTheme(themePref());
addEventListener("keydown",(e)=>{ addEventListener("keydown",(e)=>{
const tag=e.target?e.target.tagName:""; if(tag==="INPUT"||tag==="TEXTAREA"||tag==="SELECT") return; const tag=e.target?e.target.tagName:""; if(tag==="INPUT"||tag==="TEXTAREA"||tag==="SELECT") return;
@ -320,5 +297,55 @@ addEventListener("keydown",(e)=>{
loadTrack(0); loadTrack(0);
requestAnimationFrame(draw); requestAnimationFrame(draw);
</script> </script>
<section class="about pageonly">
<h2>PM1 — Stage</h2>
<div class="ff-tags"><span class="hw">Hardware</span><span>Footpedal stompbox</span><span>~$52 oneoff</span></div>
<p>A footoperated polymeter stompbox for the stage: drive it handsfree with two heavy footswitches and an
expression pedal, read it off the floor from the big RGB beat light, and run your instrument through it with
the click mixed in. (For a desk/lesson unit with a full screen, see the <a href="/teacher.html">Teacher</a>.)</p>
<p>The controls are built for feet: the <b>left footswitch</b> taps tempo (hold to start/stop), the <b>right</b>
steps through your set list (hold for previous), and a <b>1/4″ expressionpedal input</b> sweeps tempo on the
fly. Your instrument passes through (1/4″ in) with the click summed in the <b>analog domain</b> and sent to a
balanced 1/4″ TRS out. Powered over USBC — with a second USBC <b>"thru"</b> port so several pedals
daisychain off one charger or power bank.</p>
</section>
<details class="spec pageonly">
<summary>Spec &amp; bill of materials</summary>
<div class="spec-body">
<p class="sub">Rough parts list — a footoperated RP2040 stompbox (USBC, dualport) with analog click injection.
Ballpark oneoff prices (USD); cheaper at volume.</p>
<table class="bom">
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="3">Brain &amp; display</td></tr>
<tr><td class="part">RP2040 board, USBC <span class="spec">— e.g. Waveshare RP2040Zero</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">1.3″ IPS TFT, ST7789 <span class="spec">— SPI; angled BPM / item readout</span></td><td class="q">1</td><td class="c">6</td></tr>
<tr><td class="part">Highbright diffused RGB beat indicator <span class="spec">— floorreadable</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr class="grp"><td colspan="3">Controls</td></tr>
<tr><td class="part">Heavyduty momentary footswitch (softtouch) <span class="spec">— Tap · Next</span></td><td class="q">2</td><td class="c">6</td></tr>
<tr><td class="part">1/4″ expressionpedal input jack (TRS) <span class="spec">— tempo sweep</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr class="grp"><td colspan="3">Audio — analog click injection</td></tr>
<tr><td class="part">PCM5102A I²S DAC <span class="spec">— linelevel click</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr><td class="part">Dual opamp, NE5532 / OPA2134 <span class="spec">— hiZ instrument buffer + summing mixer</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr><td class="part">Balanced line driver, DRV134 <span class="spec">— → 1/4″ TRS out</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">Connectors &amp; power</td></tr>
<tr><td class="part">1/4″ jack <span class="spec">— Inst In (TS) · Out (TRS) · Trig In (TS)</span></td><td class="q">3</td><td class="c">3</td></tr>
<tr><td class="part">2× USBC (data+power &amp; powerthru) + powerpath/protection + PWR LED <span class="spec">— daisychain pedals</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr class="grp"><td colspan="3">Build</td></tr>
<tr><td class="part">Custom PCB (or perfboard)</td><td class="q">1</td><td class="c">5</td></tr>
<tr><td class="part">Passives, headers, wire <span class="spec">— R/C for the analog stage + decoupling</span></td><td class="q"></td><td class="c">3</td></tr>
<tr><td class="part">Diecast aluminium stompbox (Hammond 1590BBstyle) <span class="spec">— beadblasted, matteblack Type II anodise, laseretched</span></td><td class="q">1</td><td class="c">12</td></tr>
<tr class="total"><td>Total (oneoff)</td><td class="q"></td><td class="c">≈ $52</td></tr>
</tbody>
</table>
<p class="sub" style="margin-top:12px">No builtin speaker — the Stage feeds your amp / PA. The click is summed in
the <b>analog domain</b> (hiZ instrument buffer + DAC → balanced line driver), so your instrument is never
redigitised (no added latency).</p>
</div>
</details>
/*@BUILD:include:src/footer.html@*/
</body> </body>
</html> </html>

View file

@ -202,22 +202,7 @@
</head> </head>
<body> <body>
<header class="site-head"> /*@BUILD:include:src/header.html@*/
<div class="head-left">
<a class="brand" href="/" title="VARASYS — Simplifying Complexity">
<img class="brand-logo brand-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS — Simplifying Complexity" />
<img class="brand-logo brand-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS — Simplifying Complexity" />
</a>
<span class="page-name"><b>PM1</b> · Teacher (studio / lesson console)</span>
</div>
<nav class="site-nav">
<a href="/editor.html">Editor</a>
<a href="/">Concepts</a>
<a href="/info-teacher.html">Info</a>
<a href="/embed.html">Embed</a>
<button id="themeBtn" class="tbtn" title="toggle light / dark theme"></button>
</nav>
</header>
<div class="cols"> <div class="cols">
<div class="col-left"> <div class="col-left">
@ -287,10 +272,59 @@
<div class="status" id="status"></div> <div class="status" id="status"></div>
</div> </div>
<p class="speclink"><a href="/info-teacher.html">Spec &amp; full priced BOM ⓘ</a> — what the Teacher is designed for, and every part to build one.</p>
</div><!-- /col-left --> </div><!-- /col-left -->
</div><!-- /cols --> </div><!-- /cols -->
<section class="about pageonly">
<h2>PM1 — Teacher</h2>
<div class="ff-tags"><span class="hw">Hardware</span><span>Studio / lesson console</span><span>~$59 oneoff</span></div>
<p>The fullfeature desktop console: a colour readout of every lane, fast setlist navigation, and your
instrument running straight through with the click mixed in — the handson unit for a studio desk or a
teaching room, on a nonreflective matteblack case. (For handsfree live use, see the footoperated
<a href="/stage.html">Stage</a> stompbox.)</p>
<p>Topmounted 1/4″ jacks keep cabling tidy; the metronome click is summed into the signal in the
<b>analog domain</b> (no redigitising, no added latency) and sent to a balanced 1/4″ TRS output for the
desk or interface, plus a small monitor speaker. Powered over USBC — a wall adapter or a power bank. The
colour TFT shows tempo, the item name and all lane patterns; arcade buttons + a recessed thumbroller make
it quick to drive while you teach or track.</p>
</section>
<details class="spec pageonly">
<summary>Spec &amp; bill of materials</summary>
<div class="spec-body">
<p class="sub">Rough parts list — a desk/studio RP2040 build (USBC powered) with analog click injection.
Ballpark oneoff prices (USD); cheaper at volume.</p>
<table class="bom">
<thead><tr><th>Part</th><th class="q">Qty</th><th class="c">~$</th></tr></thead>
<tbody>
<tr class="grp"><td colspan="3">Brain &amp; display</td></tr>
<tr><td class="part">RP2040 board, USBC <span class="spec">— e.g. Waveshare RP2040Zero / Picoclone</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">2.0″ 320×240 IPS TFT, ST7789 <span class="spec">— SPI</span></td><td class="q">1</td><td class="c">8</td></tr>
<tr class="grp"><td colspan="3">Controls</td></tr>
<tr><td class="part">Arcade pushbutton, 24 mm <span class="spec">— Prev · Next · Tap</span></td><td class="q">3</td><td class="c">4</td></tr>
<tr><td class="part">Arcade pushbutton, 30 mm <span class="spec">— Play</span></td><td class="q">1</td><td class="c">2</td></tr>
<tr><td class="part">Detented encoder (EC11 / PEC12) + sidemount thumbroller <span class="spec">— recessed; nothing to snap off</span></td><td class="q">1</td><td class="c">2</td></tr>
<tr class="grp"><td colspan="3">Audio — analog click injection</td></tr>
<tr><td class="part">PCM5102A I²S DAC <span class="spec">— linelevel click</span></td><td class="q">1</td><td class="c">3</td></tr>
<tr><td class="part">Dual opamp, NE5532 / OPA2134 <span class="spec">— hiZ instrument buffer + summing mixer</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr><td class="part">Balanced line driver, DRV134 <span class="spec">— (or crosscoupled opamp) → 1/4″ TRS out</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr><td class="part">PAM8302A mono ClassD + 8 Ω 2 W speaker <span class="spec">— monitor</span></td><td class="q">1</td><td class="c">4</td></tr>
<tr class="grp"><td colspan="3">Connectors &amp; power</td></tr>
<tr><td class="part">1/4″ jack <span class="spec">— Inst In (TS) · Out (TRS) · Trig In (TS)</span></td><td class="q">3</td><td class="c">3</td></tr>
<tr><td class="part">USBC bus power (5 V) + PWR LED <span class="spec">— wall adapter or power bank; same port carries config; no battery</span></td><td class="q">1</td><td class="c">1</td></tr>
<tr class="grp"><td colspan="3">Build</td></tr>
<tr><td class="part">Custom PCB (or perfboard)</td><td class="q">1</td><td class="c">5</td></tr>
<tr><td class="part">Passives, headers, wire <span class="spec">— R/C for the analog stage + decoupling</span></td><td class="q"></td><td class="c">3</td></tr>
<tr><td class="part">Diecast aluminium enclosure (Hammond 1590style) <span class="spec">— beadblasted, matteblack Type II anodise, laseretched legends</span></td><td class="q">1</td><td class="c">12</td></tr>
<tr class="total"><td>Total (oneoff)</td><td class="q"></td><td class="c">≈ $56</td></tr>
</tbody>
</table>
<p class="sub" style="margin-top:12px">Audio is summed in the <b>analog domain</b>: the DAC's click is mixed with a
highimpedance buffer of the 1/4″ instrument input, then fed to the balanced line driver (1/4″ TRS out) and the
monitor amp — so your instrument is never redigitised (no added latency).</p>
</div>
</details>
<script> <script>
const APP_VERSION = "v0.0.1-dev"; const APP_VERSION = "v0.0.1-dev";
const $ = (id) => document.getElementById(id); const $ = (id) => document.getElementById(id);
@ -500,18 +534,7 @@ $("storedSel").onchange=(e)=>{ const v=e.target.value; if(!v) return;
})(); })();
/* theme toggle — cycles system → light → dark; shares the editor's "metronome.theme" key */ /* theme toggle — cycles system → light → dark; shares the editor's "metronome.theme" key */
const THEMES = ["system","light","dark"]; /*@BUILD:include:src/chrome.js@*/
function effectiveTheme(p){ return p==="system" ? (matchMedia("(prefers-color-scheme: light)").matches?"light":"dark") : p; }
function themePref(){ try{ const p=localStorage.getItem("metronome.theme"); return (p==="light"||p==="dark"||p==="system")?p:"system"; }catch(e){ return "system"; } }
function applyTheme(p){
try{ localStorage.setItem("metronome.theme",p); }catch(e){}
document.documentElement.dataset.theme = effectiveTheme(p);
$("themeBtn").textContent = p==="system" ? "◐" : p==="light" ? "☀" : "☾";
$("themeBtn").title = "Theme: "+p+" (click to cycle: system → light → dark)";
}
$("themeBtn").onclick = ()=> applyTheme(THEMES[(THEMES.indexOf(themePref())+1)%THEMES.length]);
matchMedia("(prefers-color-scheme: light)").addEventListener("change", ()=>{ if(themePref()==="system") applyTheme("system"); });
applyTheme(themePref());
addEventListener("keydown",(e)=>{ addEventListener("keydown",(e)=>{
const t=e.target, tag=t?t.tagName:""; if(tag==="TEXTAREA"||tag==="INPUT"||tag==="SELECT") return; const t=e.target, tag=t?t.tagName:""; if(tag==="TEXTAREA"||tag==="INPUT"||tag==="SELECT") return;
@ -531,5 +554,6 @@ if(!setlist){ setlist=BUILTIN[0]; idx=0; loadSetup(setlist.items[0]); }
renderAll(); renderAll();
requestAnimationFrame(draw); requestAnimationFrame(draw);
</script> </script>
/*@BUILD:include:src/footer.html@*/
</body> </body>
</html> </html>