Compare commits

..

No commits in common. "77fdf9ee70b436f4abb5816965c7d14577a1d9db" and "553a56d1ecc36f8fc3252a10f1aca7fb1255ae55" have entirely different histories.

2 changed files with 18 additions and 64 deletions

View file

@ -1 +1 @@
0.0.12 0.0.11

View file

@ -16,57 +16,29 @@
Audio here is the synthesized voice set (the firmware uses the same scheduler; Audio here is the synthesized voice set (the firmware uses the same scheduler;
on hardware the voices map to CC0 samples on the I2S DAC). One file, no deps. on hardware the voices map to CC0 samples on the I2S DAC). One file, no deps.
--> -->
<script>
// Set theme before first paint (avoids a flash). Shares the editor's "metronome.theme".
(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> <style>
/*@BUILD:include:src/base.css@*/ /*@BUILD:include:src/base.css@*/
:root{ :root{
/* environment (themed): room background + page/panel chrome */ --case:#1b1f27; --case2:#11141a; --edge:#0b0d11; --bezel:#0a0c10;
--bg1:#12151c; --bg2:#05070a; --txt:#c7d0db; --muted:#7f8b9a; --cyan:#0AB3F7; --amber:#ffd166;
--txt:#c7d0db; --muted:#7f8b9a; --link:#6cb6ff;
--panel-bg:#171b22; --panel-bd:#2a313c; --field-bg:#0e1116; --field-bd:#222a36;
/* the device case — kept a clear step lighter than the room so it reads as an object */
--case:#232a36; --case2:#151a22; --device-bd:#39434f;
--device-shadow:0 0 0 1px rgba(120,150,190,.06), 0 30px 70px rgba(0,0,0,.62), 0 0 46px rgba(10,179,247,.05);
/* device internals — fixed dark-hardware colours in BOTH themes */
--dtxt:#c7d0db; --dmuted:#7f8b9a;
--cyan:#0AB3F7; --amber:#ffd166; --edge:#0b0d11; --bezel:#0a0c10;
--screen:#04140f; --phos:#34e0a0; --phos-dim:#1f6e54; --led-off:#1b2a23; --screen:#04140f; --phos:#34e0a0; --phos-dim:#1f6e54; --led-off:#1b2a23;
} }
:root[data-theme="light"]{
/* light "desk": the dark device sits on a bright surface → strong contrast */
--bg1:#f5f8fc; --bg2:#dde4ec;
--txt:#1e2630; --muted:#5c6776; --link:#1769c4;
--panel-bg:#ffffff; --panel-bd:#d2dae4; --field-bg:#f1f4f8; --field-bd:#cdd6e0;
--case:#2b3340; --case2:#1b212b; --device-bd:#0f141b;
--device-shadow:0 0 0 1px rgba(0,0,0,.05), 0 26px 50px rgba(20,30,50,.30);
}
body{ body{
margin:0; min-height:100vh; padding:28px 16px 48px; margin:0; min-height:100vh; padding:28px 16px 48px;
background:radial-gradient(circle at 50% -8%, var(--bg1), var(--bg2)); background:radial-gradient(circle at 50% -8%, #20242c, #0c0e12);
color:var(--txt); color:var(--txt);
display:flex; flex-direction:column; align-items:center; gap:20px; display:flex; flex-direction:column; align-items:center; gap:20px;
} }
a{color:var(--link)} a{color:#6cb6ff}
.topbar{width:100%; max-width:560px; display:flex; align-items:center; justify-content:space-between; font-size:13px; color:var(--muted)} .topbar{width:100%; max-width:560px; display:flex; align-items:center; justify-content:space-between; font-size:13px; color:var(--muted)}
.topbar b{color:var(--txt)} .topbar b{color:var(--txt)}
.topbar-right{ display:flex; align-items:center; gap:12px }
.tbtn{ background:transparent; color:var(--muted); border:1px solid var(--panel-bd); border-radius:8px;
padding:3px 9px; font-size:14px; line-height:1; cursor:pointer }
.tbtn:hover{ color:var(--txt) }
/* ---- the device ---- */ /* ---- the device ---- */
.device{ .device{
width:100%; max-width:560px; position:relative; width:100%; max-width:560px; position:relative;
background:linear-gradient(180deg,var(--case),var(--case2)); background:linear-gradient(180deg,var(--case),var(--case2));
border:1px solid var(--device-bd); border-radius:22px; padding:22px 22px 26px; border:1px solid #2a313c; border-radius:22px; padding:22px 22px 26px;
box-shadow:var(--device-shadow), inset 0 1px 0 rgba(255,255,255,.06), inset 0 -2px 8px rgba(0,0,0,.55); box-shadow:0 26px 60px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.05), inset 0 -2px 8px rgba(0,0,0,.6);
} }
.device::before, .device::after, .device::before, .device::after,
.device .screw{ content:""; position:absolute; width:9px; height:9px; border-radius:50%; .device .screw{ content:""; position:absolute; width:9px; height:9px; border-radius:50%;
@ -78,8 +50,8 @@
.logo{ display:flex; align-items:baseline; gap:9px } .logo{ display:flex; align-items:baseline; gap:9px }
.logo .vk{ font-weight:800; letter-spacing:.22em; color:#fff; font-size:17px; .logo .vk{ font-weight:800; letter-spacing:.22em; color:#fff; font-size:17px;
background:var(--cyan); padding:2px 8px; border-radius:4px; box-shadow:0 0 10px rgba(10,179,247,.5) } background:var(--cyan); padding:2px 8px; border-radius:4px; box-shadow:0 0 10px rgba(10,179,247,.5) }
.logo .model{ color:var(--dmuted); font-size:12px; letter-spacing:.04em } .logo .model{ color:var(--muted); font-size:12px; letter-spacing:.04em }
.pwr{ display:flex; align-items:center; gap:7px; font-size:10px; color:var(--dmuted); text-transform:uppercase; letter-spacing:.12em } .pwr{ display:flex; align-items:center; gap:7px; font-size:10px; color:var(--muted); text-transform:uppercase; letter-spacing:.12em }
.pwr .dot{ width:8px; height:8px; border-radius:50%; background:#2fe07a; box-shadow:0 0 8px #2fe07a } .pwr .dot{ width:8px; height:8px; border-radius:50%; background:#2fe07a; box-shadow:0 0 8px #2fe07a }
/* ---- OLED ---- */ /* ---- OLED ---- */
@ -112,48 +84,44 @@
/* ---- controls ---- */ /* ---- controls ---- */
.controls{ display:flex; align-items:center; justify-content:center; gap:12px; margin:14px 4px 4px; flex-wrap:wrap } .controls{ display:flex; align-items:center; justify-content:center; gap:12px; margin:14px 4px 4px; flex-wrap:wrap }
.btn{ background:linear-gradient(180deg,#2b323d,#1b212a); color:var(--dtxt); border:1px solid #39424f; .btn{ background:linear-gradient(180deg,#2b323d,#1b212a); color:var(--txt); border:1px solid #39424f;
border-radius:11px; padding:12px 14px; font-size:15px; cursor:pointer; min-width:48px; border-radius:11px; padding:12px 14px; font-size:15px; cursor:pointer; min-width:48px;
box-shadow:0 3px 0 #0c0f14, inset 0 1px 0 rgba(255,255,255,.06); user-select:none; transition:transform .04s, box-shadow .04s } box-shadow:0 3px 0 #0c0f14, inset 0 1px 0 rgba(255,255,255,.06); user-select:none; transition:transform .04s, box-shadow .04s }
.btn:active{ transform:translateY(2px); box-shadow:0 1px 0 #0c0f14, inset 0 1px 0 rgba(255,255,255,.06) } .btn:active{ transform:translateY(2px); box-shadow:0 1px 0 #0c0f14, inset 0 1px 0 rgba(255,255,255,.06) }
.btn small{ display:block; font-size:9px; color:var(--dmuted); letter-spacing:.08em; margin-top:2px } .btn small{ display:block; font-size:9px; color:var(--muted); letter-spacing:.08em; margin-top:2px }
.btn.play{ min-width:74px; font-size:20px; background:linear-gradient(180deg,#1f7a4d,#155f3b); border-color:#2e7d32; color:#eafff3 } .btn.play{ min-width:74px; font-size:20px; background:linear-gradient(180deg,#1f7a4d,#155f3b); border-color:#2e7d32; color:#eafff3 }
.btn.play.on{ background:linear-gradient(180deg,#b23b3b,#8f2d2d); border-color:#c0392b } .btn.play.on{ background:linear-gradient(180deg,#b23b3b,#8f2d2d); border-color:#c0392b }
.knob{ width:52px; height:52px; border-radius:50%; background:radial-gradient(circle at 38% 32%,#3a424e,#171b22 72%); .knob{ width:52px; height:52px; border-radius:50%; background:radial-gradient(circle at 38% 32%,#3a424e,#171b22 72%);
border:1px solid #444c58; box-shadow:0 3px 8px rgba(0,0,0,.5), inset 0 1px 1px rgba(255,255,255,.08); position:relative; margin-left:4px } border:1px solid #444c58; box-shadow:0 3px 8px rgba(0,0,0,.5), inset 0 1px 1px rgba(255,255,255,.08); position:relative; margin-left:4px }
.knob::after{ content:""; position:absolute; left:50%; top:6px; width:2px; height:13px; background:var(--cyan); .knob::after{ content:""; position:absolute; left:50%; top:6px; width:2px; height:13px; background:var(--cyan);
border-radius:2px; transform-origin:50% 20px; transform:rotate(var(--a,0deg)); box-shadow:0 0 5px var(--cyan) } border-radius:2px; transform-origin:50% 20px; transform:rotate(var(--a,0deg)); box-shadow:0 0 5px var(--cyan) }
.knob-wrap{ display:flex; flex-direction:column; align-items:center; gap:4px; font-size:9px; color:var(--dmuted); letter-spacing:.1em } .knob-wrap{ display:flex; flex-direction:column; align-items:center; gap:4px; font-size:9px; color:var(--muted); letter-spacing:.1em }
/* ---- speaker grille ---- */ /* ---- speaker grille ---- */
.grille{ height:14px; margin:18px 6px 2px; border-radius:6px; .grille{ height:14px; margin:18px 6px 2px; border-radius:6px;
background:radial-gradient(circle, #000 1.1px, transparent 1.4px) 0 0/9px 9px; opacity:.5 } background:radial-gradient(circle, #000 1.1px, transparent 1.4px) 0 0/9px 9px; opacity:.5 }
/* ---- load panel ---- */ /* ---- load panel ---- */
.panel{ width:100%; max-width:560px; background:var(--panel-bg); border:1px solid var(--panel-bd); border-radius:14px; padding:16px } .panel{ width:100%; max-width:560px; background:#171b22; border:1px solid #2a313c; border-radius:14px; padding:16px }
.panel h2{ margin:0 0 4px; font-size:15px } .panel h2{ margin:0 0 4px; font-size:15px }
.panel p.sub{ margin:0 0 12px; font-size:12px; color:var(--muted); line-height:1.45 } .panel p.sub{ margin:0 0 12px; font-size:12px; color:var(--muted); line-height:1.45 }
.panel label{ font-size:12px; color:var(--muted); display:block; margin:10px 0 5px } .panel label{ font-size:12px; color:var(--muted); display:block; margin:10px 0 5px }
textarea{ width:100%; background:var(--field-bg); color:var(--txt); border:1px solid var(--field-bd); border-radius:9px; textarea{ width:100%; background:#0e1116; color:var(--txt); border:1px solid var(--edge); border-radius:9px;
padding:10px; font-family:"Courier New",monospace; font-size:12px; resize:vertical; min-height:58px } padding:10px; font-family:"Courier New",monospace; font-size:12px; resize:vertical; min-height:58px }
select, .ld{ border-radius:9px; padding:8px 10px; font-size:13px } select, .ld{ background:#0e1116; color:var(--txt); border:1px solid #39424f; border-radius:9px; padding:8px 10px; font-size:13px }
select{ background:var(--field-bg); color:var(--txt); border:1px solid var(--field-bd) } .ld{ cursor:pointer; background:linear-gradient(180deg,#2b323d,#1b212a) }
.ld{ cursor:pointer; color:var(--dtxt); background:linear-gradient(180deg,#2b323d,#1b212a); border:1px solid #39424f }
.row{ display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-top:8px } .row{ display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-top:8px }
.status{ margin-top:10px; font-size:12px; min-height:1.2em; font-family:"Courier New",monospace } .status{ margin-top:10px; font-size:12px; min-height:1.2em; font-family:"Courier New",monospace }
.status.ok{ color:#5fd08a } .status.err{ color:#ff7a7a } .status.ok{ color:#5fd08a } .status.err{ color:#ff7a7a }
.hint{ font-size:11px; color:var(--muted) } .hint{ font-size:11px; color:var(--muted) }
code{ background:var(--field-bg); border:1px solid var(--field-bd); border-radius:4px; padding:1px 5px; font-size:11px } code{ background:#0e1116; border:1px solid var(--edge); border-radius:4px; padding:1px 5px; font-size:11px }
</style> </style>
</head> </head>
<body> <body>
<div class="topbar"> <div class="topbar">
<span><b>VARASYS PM1</b> · hardware player (mockup)</span> <span><b>VARASYS PM1</b> · hardware player (mockup)</span>
<span class="topbar-right"> <a href="/index.html">Open editor ↗</a>
<button id="themeBtn" class="tbtn" title="toggle light / dark theme"></button>
<a href="/index.html">Open editor ↗</a>
</span>
</div> </div>
<!-- ===================== THE DEVICE ===================== --> <!-- ===================== THE DEVICE ===================== -->
@ -365,20 +333,6 @@ $("bLoad").onclick=()=>loadConfig($("cfg").value);
$("storedSel").onchange=(e)=>{ const v=e.target.value; if(!v) return; $("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 */
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+" (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;
const k=e.key; const k=e.key;