Sync: the visual playhead now advances on a latency-compensated clock (currentTime − outputLatency||baseLatency) so the on-screen pulse lands when the click is HEARD, not when it's queued — previously the visual could lead the audio by the output buffer / Bluetooth latency (up to ~a subdivision). Applied to editor, player, teacher, and the new pages; also bound the visual queue (vq trim). No data races: single-threaded; only the rAF draw touches vqPtr/currentStep, and each vq entry carries the exact scheduled time of its sound. stage.html — foot-pedal stompbox: two heavy footswitches (Tap=tempo / hold=start- stop, Next=item / hold=prev), 1/4" expression-pedal input → tempo sweep, big floor-readable RGB beat light + angled TFT, analog instrument pass-through. showcase.html — pyramid display piece: an RGB-light pendulum easing to each beat plus per-lane segment rows showing subdivisions/accents/mutes (canvas). Both: dual USB-C (data+power and power-thru) to daisy-chain off one source. Wired into embed.js (stage, showcase variants), build.sh, deploy.sh, the concepts gallery + landing cards, info-stage.html (~$52) + info-showcase.html (~$39) with BOMs, and the README. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
196 lines
11 KiB
HTML
196 lines
11 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>VARASYS PolyMeter — polymetric groove trainer & metronome</title>
|
||
<meta name="description" content="PolyMeter — a polymetric groove trainer and metronome. One engine, many form factors: a free web editor, hardware concepts, and an embeddable widget." />
|
||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,@BUILD:favicon@">
|
||
<!-- Landing page: the front door to the PolyMeter family. The app itself is /editor.html. Static page. -->
|
||
<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; }
|
||
:root[data-theme="light"]{ --bg1:#f5f8fc; --bg2:#dde4ec; --txt:#1e2630; --muted:#5c6776; --link:#1769c4;
|
||
--panel-bg:#ffffff; --panel-bd:#d2dae4; }
|
||
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:980px; margin:0 auto; }
|
||
|
||
/* hero */
|
||
.hero{ text-align:center; padding:54px 12px 38px; }
|
||
.hero h1{ font-size:clamp(40px, 9vw, 76px); margin:0; letter-spacing:-.02em; line-height:1;
|
||
background:linear-gradient(90deg, var(--cyan), #6cb6ff); -webkit-background-clip:text; background-clip:text; color:transparent; }
|
||
.hero .tagline{ margin:16px auto 0; font-size:clamp(16px, 2.6vw, 21px); color:var(--txt); font-weight:600; }
|
||
.hero .pitch{ margin:14px auto 0; max-width:60ch; color:var(--muted); font-size:15px; line-height:1.6; }
|
||
.cta{ display:flex; gap:12px; justify-content:center; flex-wrap:wrap; margin-top:26px; }
|
||
.btn{ display:inline-flex; align-items:center; gap:6px; text-decoration:none; font-weight:600; font-size:15px;
|
||
padding:11px 20px; border-radius:10px; border:1px solid var(--panel-bd); color:var(--txt); background:var(--panel-bg);
|
||
transition:.14s; }
|
||
.btn:hover{ border-color:var(--cyan); }
|
||
.btn.primary{ color:#04121b; border-color:transparent; background:linear-gradient(180deg, #34c6ff, var(--cyan)); }
|
||
.btn.primary:hover{ filter:brightness(1.06); }
|
||
|
||
/* form-factor cards (shared look with the Concepts gallery) */
|
||
.section-label{ text-align:center; font-size:11px; text-transform:uppercase; letter-spacing:.12em; color:var(--muted); margin:30px 0 14px; }
|
||
.grid{ display:grid; grid-template-columns:repeat(auto-fit, minmax(230px, 1fr)); gap:16px; }
|
||
.card{ background:var(--panel-bg); border:1px solid var(--panel-bd); border-radius:14px; padding:16px;
|
||
display:flex; flex-direction:column; gap:9px; }
|
||
.card h3{ margin:0; font-size:16px; }
|
||
.chip{ align-self:flex-start; font-size:10px; text-transform:uppercase; letter-spacing:.08em;
|
||
padding:2px 9px; border-radius:999px; border:1px solid var(--panel-bd); color:var(--muted); }
|
||
.chip.hw{ color:var(--cyan); border-color:rgba(10,179,247,.45); }
|
||
.chip.app{ color:#2fe07a; border-color:rgba(47,224,160,.45); }
|
||
.card p{ margin:0; font-size:13px; color:var(--muted); line-height:1.5; flex:1; }
|
||
.card .links{ display:flex; gap:16px; margin-top:4px; }
|
||
.card .links a{ color:var(--link); text-decoration:none; font-size:13px; font-weight:600; }
|
||
.more{ text-align:center; margin-top:18px; font-size:14px; }
|
||
/* philosophy section */
|
||
.philosophy{ margin-top:34px; }
|
||
.phil-grid{ display:grid; grid-template-columns:repeat(auto-fit, minmax(300px, 1fr)); gap:16px; }
|
||
.phil{ background:var(--panel-bg); border:1px solid var(--panel-bd); border-radius:14px; padding:18px 18px 16px; }
|
||
.phil h3{ margin:0 0 8px; font-size:15px; display:flex; align-items:center; gap:8px; }
|
||
.phil .ic{ font-size:17px; line-height:1; filter:grayscale(.1); }
|
||
.phil p{ margin:0; font-size:13.5px; color:var(--muted); line-height:1.62; }
|
||
.phil p b{ color:var(--txt); }
|
||
.site-foot{ max-width:980px; margin:42px auto 0; font-size:12px; color:var(--muted); text-align:center; }
|
||
.site-foot a{ 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>PolyMeter</b></span>
|
||
</div>
|
||
<nav class="site-nav">
|
||
<a href="/editor.html">Editor</a>
|
||
<a href="/concepts.html">Concepts</a>
|
||
<a href="/embed.html">Embed</a>
|
||
<button id="themeBtn" class="tbtn" title="toggle light / dark theme">☀</button>
|
||
</nav>
|
||
</header>
|
||
|
||
<main>
|
||
<section class="hero">
|
||
<h1>PolyMeter</h1>
|
||
<p class="tagline">Polymetric grooves — one engine, many form factors.</p>
|
||
<p class="pitch">Stack independent meter lanes, each with its own subdivision, drum voice and per‑step
|
||
accents, to build true polymeter and ratio polyrhythm. Design it in the browser, save it as a compact
|
||
program string, and play it back on the editor, the hardware concepts, or an embedded widget — all the
|
||
same engine.</p>
|
||
<div class="cta">
|
||
<a class="btn primary" href="/editor.html">Open the Editor →</a>
|
||
<a class="btn" href="/concepts.html">Browse concepts</a>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="section-label">The PolyMeter family</div>
|
||
<div class="grid">
|
||
<div class="card">
|
||
<span class="chip app">Web app</span>
|
||
<h3>PE‑1 — PolyMeter Editor</h3>
|
||
<p>The full editor: stack meter lanes, per‑step accents / ghosts / mutes, swing & ratio polyrhythm,
|
||
set lists, and shareable links. This is where you design grooves.</p>
|
||
<div class="links"><a href="/editor.html">Open ↗</a><a href="/info-editor.html">Info ⓘ</a></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<span class="chip hw">Hardware</span>
|
||
<h3>PM‑1 — Teacher</h3>
|
||
<p>Full‑feature desktop console for studio & lessons: 2.0″ colour TFT showing every lane, arcade buttons,
|
||
thumb‑roller, 1/4″ instrument pass‑through with analog click injection + balanced‑TRS out. USB‑C powered.</p>
|
||
<div class="links"><a href="/teacher.html">Open ↗</a><a href="/info-teacher.html">Info & BOM ⓘ</a></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<span class="chip hw">Hardware</span>
|
||
<h3>PM‑1 — Stage</h3>
|
||
<p>Foot‑pedal stompbox: two footswitches (tap / next), expression‑pedal input, a big floor‑readable RGB
|
||
beat light, instrument pass‑through with analog click. Dual‑USB‑C daisy‑chain.</p>
|
||
<div class="links"><a href="/stage.html">Open ↗</a><a href="/info-stage.html">Info & BOM ⓘ</a></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<span class="chip hw">Hardware</span>
|
||
<h3>PM‑µ — Micro</h3>
|
||
<p>Long, narrow inline practice bar: instrument in one end, amp/headphones out the other, click mixed in.
|
||
Clickable thumb‑roller, amber 14‑segment display, USB‑C powered.</p>
|
||
<div class="links"><a href="/micro.html">Open ↗</a><a href="/info-micro.html">Info & BOM ⓘ</a></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<span class="chip hw">Hardware</span>
|
||
<h3>PM‑S — Showcase</h3>
|
||
<p>A display piece shaped like a classic pyramid wind‑up metronome — an RGB‑light pendulum easing to the
|
||
beat, with light rows for every lane's subdivisions, accents & mutes. USB‑C powered.</p>
|
||
<div class="links"><a href="/showcase.html">Open ↗</a><a href="/info-showcase.html">Info & BOM ⓘ</a></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<span class="chip">Widget</span>
|
||
<h3>Embed anywhere</h3>
|
||
<p>Drop any form factor into your own page with one <code><div></code> + a script — preloaded with a
|
||
program string and auto‑sizing. Our own pages use the same loader.</p>
|
||
<div class="links"><a href="/embed.html">Docs ↗</a></div>
|
||
</div>
|
||
</div>
|
||
<p class="more"><a href="/concepts.html">See all concepts, including the PM‑1 Initial render →</a></p>
|
||
|
||
<section class="philosophy">
|
||
<div class="section-label">Philosophy</div>
|
||
<div class="phil-grid">
|
||
<div class="phil">
|
||
<h3><span class="ic">🛠️</span> Program on the web, play on any device</h3>
|
||
<p>The website is the workbench. Design your grooves in the <a href="/editor.html">PE‑1 editor</a> —
|
||
stack meters, set per‑step accents, build set lists — and every pattern saves to a compact
|
||
<b>program string</b> (a whole set list to a single code). That same string loads into whichever
|
||
form factor fits the moment: the <a href="/teacher.html">Teacher</a> on a studio desk, the
|
||
<a href="/micro.html">Micro</a> inline at the practice desk, or an <a href="/embed.html">embedded
|
||
widget</a> in someone else's app. One engine, one language — you author once and run it anywhere,
|
||
choosing the device by the use scenario rather than re‑learning a new box each time.</p>
|
||
</div>
|
||
<div class="phil">
|
||
<h3><span class="ic">🔌</span> USB‑C power everywhere — no batteries</h3>
|
||
<p>Every device in the family is powered over a single <b>USB‑C</b> port — no internal battery to
|
||
swell, leak or wear out, and nothing proprietary to replace. Plug into a wall adapter for a
|
||
permanent install, or carry a power bank exactly the way you already do for your phone. Standardising
|
||
on one connector across the whole range keeps the builds simple and <b>future‑proofs</b> the
|
||
project as USB‑C becomes universal.</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<div class="site-foot">VARASYS · Simplifying Complexity ·
|
||
<a href="https://codeberg.org/VARASYS/metronome" target="_blank" rel="noopener">source</a> —
|
||
<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+" (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());
|
||
</script>
|
||
</body>
|
||
</html>
|