pm-mobile: top logo, tap/number/wheel tempo, transport reorder, Journal, no staff
- VARASYS logo (tagline-on-the-side lockup) moved to the top, linking the
Codeberg repo; deleted the bottom session-bar footer.
- Tempo plate is now [TAP] [big BPM] [thumbwheel]: TAP = tap-tempo, tap the
number to type, drag the ridged wheel on the right to scrub. Removed the ♩
note glyph next to the number.
- Repeat is a checkbox (with Tempo ramp / Practice gaps); expanding any of them
now SHRINKS the transport buttons instead of scrolling the page (lanes scroll
on their own if there are many).
- Transport reordered: row 1 = −10 / − / + / +10, row 2 = prev / play / practice
/ next. Added a Journal button (reaches the practice-sessions log; doubles as
the live recording timer while practising).
- Removed the staff lines behind the lanes.
- New build markers @BUILD:logo-side-{dark,light}@ (assets added).
Engine untouched; conformance passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
36b7cacd3f
commit
d27bf07069
4 changed files with 69 additions and 63 deletions
1
assets/logo-side-dark.b64
Normal file
1
assets/logo-side-dark.b64
Normal file
File diff suppressed because one or more lines are too long
1
assets/logo-side-light.b64
Normal file
1
assets/logo-side-light.b64
Normal file
File diff suppressed because one or more lines are too long
2
build.sh
2
build.sh
|
|
@ -40,6 +40,8 @@ def build(name):
|
|||
src = src.replace("@BUILD:favicon@", (A / "favicon.b64").read_text().strip())
|
||||
src = src.replace("@BUILD:logo-dark@", (A / "logo-dark.b64").read_text().strip())
|
||||
src = src.replace("@BUILD:logo-light@", (A / "logo-light.b64").read_text().strip())
|
||||
src = src.replace("@BUILD:logo-side-dark@", (A / "logo-side-dark.b64").read_text().strip())
|
||||
src = src.replace("@BUILD:logo-side-light@", (A / "logo-side-light.b64").read_text().strip())
|
||||
src = src.replace("@BUILD:bravura@", (A / "bravura.woff2.b64").read_text().strip()) # SMuFL music font subset (PM_E-2 notation)
|
||||
assert "@BUILD:" not in src, f"unresolved build marker(s) remain in {name}"
|
||||
out = pathlib.Path("dist") / name
|
||||
|
|
|
|||
128
mobile.html
128
mobile.html
|
|
@ -55,12 +55,15 @@
|
|||
#app{ position:fixed; inset:0; overflow:hidden; display:flex; flex-direction:column; align-items:center; --maxw:600px; --pad:14px;
|
||||
padding:max(12px,env(safe-area-inset-top)) max(var(--pad),env(safe-area-inset-right))
|
||||
max(12px,env(safe-area-inset-bottom)) max(var(--pad),env(safe-area-inset-left)); }
|
||||
#top, #mid, #sessbar{ width:100%; max-width:var(--maxw); }
|
||||
#top, #mid{ width:100%; max-width:var(--maxw); }
|
||||
:fullscreen #app, html:fullscreen #app{ --pad:30px; padding-top:max(24px,env(safe-area-inset-top)); padding-bottom:max(18px,env(safe-area-inset-bottom)); }
|
||||
@media (display-mode:standalone){ #app{ --pad:26px; padding-top:max(20px,env(safe-area-inset-top)); padding-bottom:max(16px,env(safe-area-inset-bottom)); } }
|
||||
|
||||
/* ---- top ---- */
|
||||
#top{ flex:0 0 auto; display:flex; flex-direction:column; gap:10px; }
|
||||
#top{ flex:0 0 auto; display:flex; flex-direction:column; gap:9px; }
|
||||
#brandrow{ display:flex; align-items:center; }
|
||||
#logoLink{ display:inline-flex; opacity:.9; }
|
||||
.brandlogo{ height:clamp(17px,3.4vmin,24px); width:auto; display:block; }
|
||||
.sels{ display:flex; gap:8px; align-items:flex-end; }
|
||||
.sel{ flex:1 1 0; min-width:0; display:flex; flex-direction:column; gap:3px; }
|
||||
.sel > span{ font-size:10px; letter-spacing:.14em; text-transform:uppercase; color:var(--muted); padding-left:3px; }
|
||||
|
|
@ -76,31 +79,36 @@
|
|||
/* ---- middle: pulse + track panel + lanes + transport, centered as one block ---- */
|
||||
#mid{ flex:1 1 auto; min-height:0; display:flex; flex-direction:column; align-items:center; justify-content:safe center; gap:clamp(10px,2.4vmin,22px); }
|
||||
#stage{ flex:0 0 auto; display:flex; flex-direction:column; align-items:center; width:100%; }
|
||||
/* compact tempo "plate" (was a big circle) — flashes on the beat; tap/hold/drag target */
|
||||
#pulse{ position:relative; width:100%; padding:clamp(10px,2.4vmin,20px) 16px; border-radius:16px;
|
||||
display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center;
|
||||
/* tempo plate: [TAP] [big BPM] [thumbwheel] — flashes on the beat */
|
||||
#pulse{ position:relative; width:100%; padding:clamp(8px,1.8vmin,14px); border-radius:16px;
|
||||
display:flex; flex-direction:row; align-items:stretch; gap:clamp(8px,2vmin,16px);
|
||||
border:1px solid var(--ring); background:linear-gradient(180deg, rgba(127,139,154,.06), transparent);
|
||||
transition:box-shadow .12s ease-out, border-color .12s ease-out, background .12s ease-out; touch-action:none; cursor:pointer; }
|
||||
transition:box-shadow .12s ease-out, border-color .12s ease-out, background .12s ease-out; }
|
||||
#pulse.hit{ border-color:var(--cyan); box-shadow:0 0 22px var(--glow); background:rgba(10,179,247,.07); }
|
||||
#pulse.hit.acc{ border-color:var(--amber); box-shadow:0 0 26px var(--aglow); background:rgba(255,209,102,.08); }
|
||||
/* tempo as an engraved marking: ♩ = N */
|
||||
#bpm{ display:inline-flex; align-items:center; justify-content:center; gap:.12em; line-height:.82; }
|
||||
#bpmNum{ font-size:clamp(44px,14vmin,128px); font-weight:800; font-variant-numeric:tabular-nums; letter-spacing:-.01em; }
|
||||
.metmark{ display:inline-flex; align-items:center; gap:.12em; color:var(--muted); font-size:clamp(20px,5.6vmin,50px); font-weight:600; }
|
||||
.metmark .rhythm{ height:1.2em; width:auto; }
|
||||
.metmark::after{ content:"="; }
|
||||
#bpmlab{ font-size:clamp(9px,1.8vmin,14px); letter-spacing:.18em; text-transform:uppercase; color:var(--muted); margin-top:.7em; opacity:.8; }
|
||||
#bpmIn{ display:none; width:64%; text-align:center; font:inherit; font-size:clamp(42px,13vmin,120px); font-weight:800;
|
||||
.tapbtn{ flex:0 0 auto; align-self:stretch; min-width:clamp(58px,16vmin,100px); border-radius:12px;
|
||||
background:linear-gradient(180deg,var(--btn1),var(--btn2)); border:1px solid var(--btn-bd); color:var(--txt);
|
||||
font-size:clamp(13px,2.8vmin,18px); font-weight:600; letter-spacing:.14em; cursor:pointer;
|
||||
box-shadow:0 3px 0 rgba(0,0,0,.22), inset 0 1px 0 rgba(255,255,255,.06); }
|
||||
.tapbtn:active{ transform:translateY(2px); box-shadow:0 1px 0 rgba(0,0,0,.22), inset 0 1px 0 rgba(255,255,255,.06); }
|
||||
#bpm{ flex:1 1 auto; display:flex; flex-direction:column; align-items:center; justify-content:center; line-height:.82; cursor:pointer; min-width:0; }
|
||||
#bpmNum{ font-size:clamp(44px,15vmin,120px); font-weight:800; font-variant-numeric:tabular-nums; letter-spacing:-.01em; }
|
||||
#bpmlab{ font-size:clamp(9px,1.8vmin,13px); letter-spacing:.2em; text-transform:uppercase; color:var(--muted); margin-top:.5em; opacity:.85; }
|
||||
#bpmIn{ display:none; flex:1 1 auto; min-width:0; text-align:center; font:inherit; font-size:clamp(40px,13vmin,108px); font-weight:800;
|
||||
background:transparent; color:var(--txt); border:none; border-bottom:2px solid var(--cyan); outline:none; font-variant-numeric:tabular-nums; -moz-appearance:textfield; }
|
||||
#bpmIn::-webkit-outer-spin-button, #bpmIn::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }
|
||||
/* thumbwheel encoder — drag up/down to scrape the tempo */
|
||||
#wheel{ flex:0 0 auto; align-self:stretch; width:clamp(30px,7.5vmin,46px); border-radius:11px; cursor:ns-resize; touch-action:none;
|
||||
border:1px solid var(--btn-bd);
|
||||
background:repeating-linear-gradient(to bottom, rgba(0,0,0,.16) 0 1px, transparent 1px 6px),
|
||||
linear-gradient(to right, rgba(0,0,0,.26), rgba(255,255,255,.14) 48%, rgba(0,0,0,.26)), var(--field-bg);
|
||||
box-shadow:inset 0 0 6px rgba(0,0,0,.25); }
|
||||
#wheel:active{ border-color:var(--cyan); }
|
||||
#meterline{ font-size:clamp(12px,2.1vmin,16px); color:var(--muted); text-align:center; min-height:1.2em; letter-spacing:.02em; }
|
||||
|
||||
/* ---- track panel (repeat/end/ramp/gap/string) + editable lanes ---- */
|
||||
#detail{ flex:0 1 auto; width:100%; max-height:32vh; overflow-y:auto; display:flex; flex-direction:column; gap:8px; padding:2px 0; }
|
||||
#lanes{ display:flex; flex-direction:column; gap:6px; position:relative; }
|
||||
#lanes::before{ content:""; position:absolute; left:-2px; right:-2px; top:7%; bottom:7%; pointer-events:none; z-index:0;
|
||||
background:repeating-linear-gradient(to bottom, var(--staff) 0 1.5px, transparent 1.5px 25%); }
|
||||
#lanes > *{ position:relative; z-index:1; }
|
||||
/* ---- editable lanes (scroll if many) + track panel below ---- */
|
||||
#detail{ flex:0 1 auto; width:100%; display:flex; flex-direction:column; gap:8px; padding:2px 0; min-height:0; }
|
||||
#lanes{ display:flex; flex-direction:column; gap:6px; max-height:34vh; overflow-y:auto; }
|
||||
#trackpanel{ width:100%; background:var(--chip-bg); border:1px solid var(--chip-bd); border-radius:10px; padding:8px 11px; display:flex; flex-direction:column; gap:8px; font-size:12px; color:var(--muted); }
|
||||
#trackpanel .tp-row{ display:flex; align-items:center; gap:8px 18px; flex-wrap:nowrap; }
|
||||
#trackpanel label{ display:flex; align-items:center; gap:6px; white-space:nowrap; }
|
||||
|
|
@ -153,14 +161,20 @@
|
|||
.chip.feat{ font-size:clamp(10px,1.8vmin,13px); color:var(--muted); background:var(--chip-bg); border:1px solid var(--chip-bd); border-radius:7px; padding:3px 8px; white-space:nowrap; }
|
||||
.chip.feat.r{ border-color:var(--cyan); color:var(--cyan); } .chip.feat.g{ border-color:var(--amber); color:var(--amber); }
|
||||
|
||||
/* ---- transport ---- */
|
||||
/* transport grows to fill the space freed by the compact tempo plate */
|
||||
#transport{ flex:1 1 auto; max-height:clamp(150px,42vh,300px); display:flex; align-items:stretch; justify-content:center; width:100%; padding-top:6px; }
|
||||
.tgrid{ display:grid; width:100%; height:100%; gap:clamp(7px,1.7vmin,14px);
|
||||
grid-template-columns:1fr 1.5fr 1.5fr 1fr; grid-template-rows:1fr 1fr; grid-template-areas:"dn10 prev next up10" "dn play prac up"; }
|
||||
/* ---- transport: tempo row (−10/−/+/+10) then nav+play row (prev/play/practice/next) ---- */
|
||||
/* grows to fill freed space; SHRINKS (rather than the page scrolling) when the panel expands */
|
||||
#transport{ flex:1 1 auto; min-height:0; max-height:clamp(150px,42vh,300px); display:flex; flex-direction:column; align-items:stretch; width:100%; padding-top:6px; gap:clamp(6px,1.4vmin,11px); }
|
||||
.tgrid{ display:grid; width:100%; flex:1 1 auto; min-height:0; gap:clamp(7px,1.7vmin,14px);
|
||||
grid-template-columns:1fr 1.5fr 1.5fr 1fr; grid-template-rows:1fr 1fr; grid-template-areas:"dn10 dn up up10" "prev play prac next"; }
|
||||
.tbtn{ background:linear-gradient(180deg,var(--btn1),var(--btn2)); color:var(--txt); border:1px solid var(--btn-bd);
|
||||
border-radius:14px; height:auto; min-height:46px; font-size:clamp(18px,4.4vmin,30px); cursor:pointer;
|
||||
border-radius:14px; height:auto; min-height:42px; font-size:clamp(18px,4.4vmin,30px); cursor:pointer;
|
||||
box-shadow:0 3px 0 rgba(0,0,0,.28), inset 0 1px 0 rgba(255,255,255,.06); display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px; }
|
||||
.journal{ flex:0 0 auto; width:100%; height:clamp(34px,7vmin,46px); border-radius:11px; cursor:pointer;
|
||||
background:rgba(127,139,154,.10); border:1px solid var(--panel-bd); color:var(--muted); font-size:13px;
|
||||
display:flex; align-items:center; justify-content:center; gap:8px; }
|
||||
.journal.rec{ background:rgba(192,57,43,.12); border-color:#c0392b; color:var(--txt); cursor:default; }
|
||||
.journal .dotrec{ width:9px; height:9px; border-radius:50%; background:#e0493a; box-shadow:0 0 8px #e0493a; display:none; }
|
||||
.journal.rec .dotrec{ display:inline-block; }
|
||||
.tbtn small{ font-size:clamp(8px,1.5vmin,11px); letter-spacing:.1em; color:inherit; opacity:.85; }
|
||||
.tbtn:active{ transform:translateY(2px); box-shadow:0 1px 0 rgba(0,0,0,.28), inset 0 1px 0 rgba(255,255,255,.06); }
|
||||
#bDn10{grid-area:dn10} #bPrev{grid-area:prev} #bNext{grid-area:next} #bUp10{grid-area:up10}
|
||||
|
|
@ -182,22 +196,10 @@
|
|||
grid-template-areas:"stage detail" "transport detail"; }
|
||||
#stage{ grid-area:stage; align-self:center; }
|
||||
#detail{ grid-area:detail; align-self:stretch; width:auto; max-width:none; max-height:none; min-height:0; overflow-y:auto; }
|
||||
#transport{ grid-area:transport; align-self:end; }
|
||||
#pulse{ width:clamp(110px,40vmin,360px); height:clamp(110px,40vmin,360px); }
|
||||
.tbtn{ height:clamp(40px,13vmin,68px); }
|
||||
#transport{ grid-area:transport; align-self:end; max-height:none; }
|
||||
.tbtn{ height:clamp(38px,12vmin,62px); }
|
||||
}
|
||||
|
||||
/* ---- session bar ---- */
|
||||
#sessbar{ flex:0 0 auto; display:flex; align-items:center; gap:10px; width:100%; margin-top:6px; padding:9px 12px; border-radius:11px;
|
||||
background:rgba(127,139,154,.10); border:1px solid var(--panel-bd); color:var(--muted); font-size:13px; }
|
||||
#sessbar.rec{ background:rgba(192,57,43,.12); border-color:#c0392b; color:var(--txt); }
|
||||
#sessLink{ flex:1 1 auto; min-width:0; display:flex; align-items:center; gap:8px; text-decoration:none; color:inherit; cursor:pointer; }
|
||||
#sessbar .dotrec{ width:9px; height:9px; border-radius:50%; background:#e0493a; box-shadow:0 0 8px #e0493a; flex:0 0 auto; display:none; }
|
||||
#sessbar.rec .dotrec{ display:block; }
|
||||
#sessText{ flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
#repoLink{ flex:0 0 auto; display:inline-flex; align-items:center; opacity:.38; }
|
||||
#repoLink:hover{ opacity:.85; }
|
||||
#repoLink .rlogo{ height:13px; width:auto; display:block; }
|
||||
[data-theme="light"] .logo-dark{ display:none; } [data-theme="dark"] .logo-light{ display:none; }
|
||||
|
||||
/* ---- bottom sheet (lane editor) ---- */
|
||||
|
|
@ -250,6 +252,9 @@
|
|||
|
||||
<div id="app">
|
||||
<div id="top">
|
||||
<div id="brandrow">
|
||||
<a id="logoLink" href="https://codeberg.org/VARASYS/metronome" target="_blank" rel="noopener" title="VARASYS PolyMeter — source on Codeberg"><img class="brandlogo logo-dark" src="data:image/png;base64,@BUILD:logo-side-dark@" alt="VARASYS PolyMeter" /><img class="brandlogo logo-light" src="data:image/png;base64,@BUILD:logo-side-light@" alt="VARASYS PolyMeter" /></a>
|
||||
</div>
|
||||
<div class="trow" id="utilrow">
|
||||
<div class="vol"><span class="dyn" aria-hidden="true">p</span><input id="vol" type="range" min="0" max="100" value="85" aria-label="Volume" /><span class="dyn" aria-hidden="true">f</span></div>
|
||||
<div class="icon" id="shareBtn" title="Share / paste" aria-label="Share or paste"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><path d="M8 7l4-4 4 4"/><path d="M5 12v8h14v-8"/></svg></div>
|
||||
|
|
@ -267,9 +272,10 @@
|
|||
<div id="mid">
|
||||
<div id="stage">
|
||||
<div id="pulse">
|
||||
<div id="bpm"><span id="bpmMark" class="metmark" aria-hidden="true"></span><span id="bpmNum">120</span></div>
|
||||
<button id="bTapBtn" class="tapbtn" title="Tap tempo">TAP</button>
|
||||
<div id="bpm"><span id="bpmNum">120</span><span id="bpmlab">BPM</span></div>
|
||||
<input id="bpmIn" type="number" inputmode="numeric" min="30" max="300" />
|
||||
<div id="bpmlab">per minute</div>
|
||||
<div id="wheel" title="Drag to set tempo" aria-label="Tempo wheel"></div>
|
||||
</div>
|
||||
<div id="meterline"></div>
|
||||
</div>
|
||||
|
|
@ -289,21 +295,17 @@
|
|||
<div id="transport">
|
||||
<div class="tgrid">
|
||||
<button class="tbtn" id="bDn10" title="Tempo −10">−10</button>
|
||||
<button class="tbtn" id="bPrev" title="Previous track">⏮</button>
|
||||
<button class="tbtn" id="bNext" title="Next track">⏭</button>
|
||||
<button class="tbtn" id="bUp10" title="Tempo +10">+10</button>
|
||||
<button class="tbtn" id="bDown" title="Tempo −1">−</button>
|
||||
<button class="tbtn" id="bUp" title="Tempo +1">+</button>
|
||||
<button class="tbtn" id="bUp10" title="Tempo +10">+10</button>
|
||||
<button class="tbtn" id="bPrev" title="Previous track">⏮</button>
|
||||
<button class="tbtn play" id="bPlay" title="Play / Stop">▶<small>PLAY</small></button>
|
||||
<button class="tbtn prac" id="bPrac" title="Practice — logs your time to the practice log">⦿<small>PRACTICE</small></button>
|
||||
<button class="tbtn" id="bUp" title="Tempo +1">+</button>
|
||||
<button class="tbtn" id="bNext" title="Next track">⏭</button>
|
||||
</div>
|
||||
<button id="bJournal" class="journal"><span class="dotrec"></span><span id="jText">Journal →</span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="sessbar">
|
||||
<a id="sessLink" href="/mobile-sessions.html"><span class="dotrec"></span><span id="sessText">Practice sessions →</span></a>
|
||||
<a id="repoLink" href="https://codeberg.org/VARASYS/metronome" target="_blank" rel="noopener" title="Source & docs on Codeberg — VARASYS PolyMeter"><img class="rlogo logo-dark" src="data:image/png;base64,@BUILD:logo-dark@" alt="VARASYS" /><img class="rlogo logo-light" src="data:image/png;base64,@BUILD:logo-light@" alt="VARASYS" /></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- lane editor sheet -->
|
||||
|
|
@ -758,10 +760,10 @@ function renderTransport(){
|
|||
$("bPlay").innerHTML=(onAny?"■<small>STOP</small>":"▶<small>PLAY</small>"); $("bPlay").classList.toggle("on",onAny);
|
||||
const pr=state.running&&sessionActive;
|
||||
$("bPrac").innerHTML=(pr?"❚❚<small>PAUSE</small>":"⦿<small>PRACTICE</small>"); $("bPrac").classList.toggle("on",pr); }
|
||||
function renderSessionBar(){ const bar=$("sessbar"), n=lsGet(LS_SESSIONS,[]).length;
|
||||
function renderSessionBar(){ const bar=$("bJournal"), n=lsGet(LS_SESSIONS,[]).length; // the Journal button doubles as live session status
|
||||
if(sessionActive){ bar.classList.add("rec"); const segs=session.segments.length+(trackSegStart?1:0);
|
||||
$("sessText").textContent="Practising · session "+fmt((Date.now()-session.at)/1000)+" · "+segs+" track"+(segs===1?"":"s"); }
|
||||
else { bar.classList.remove("rec"); $("sessText").textContent=(lastSaved?"✓ Session saved · ":"")+"Practice sessions"+(n?(" ("+n+")"):"")+" →"; } }
|
||||
$("jText").textContent="Recording "+fmt((Date.now()-session.at)/1000)+" · "+segs+" track"+(segs===1?"":"s"); }
|
||||
else { bar.classList.remove("rec"); $("jText").textContent=(lastSaved?"✓ saved · ":"")+"Journal"+(n?(" ("+n+")"):"")+" →"; } }
|
||||
function renderAll(){ renderInfo(); renderTransport(); saveState(); }
|
||||
|
||||
let lastBeatKey=-1, pulseTimer=null;
|
||||
|
|
@ -786,12 +788,13 @@ function openBpmEdit(){ editingBpm=true; const i=$("bpmIn"); $("bpm").style.disp
|
|||
function closeBpmEdit(commit){ const i=$("bpmIn"); if(commit){ const v=parseInt(i.value,10); if(v){ ramp.on=false; setBpm(v); } } editingBpm=false; i.style.display="none"; $("bpm").style.display=""; renderAll(); }
|
||||
$("bpmIn").addEventListener("keydown",(e)=>{ if(e.key==="Enter"){ closeBpmEdit(true); } else if(e.key==="Escape"){ closeBpmEdit(false); } });
|
||||
$("bpmIn").addEventListener("blur",()=>{ if(editingBpm) closeBpmEdit(true); });
|
||||
(function(){ const p=$("pulse"); let dragging=false, moved=false, lpFired=false, startY=0, startBpm=120, lpTimer=null;
|
||||
p.addEventListener("pointerdown",(e)=>{ if(editingBpm) return; dragging=true; moved=false; lpFired=false; startY=e.clientY; startBpm=state.bpm;
|
||||
p.setPointerCapture(e.pointerId); lpTimer=setTimeout(()=>{ lpFired=true; openBpmEdit(); },450); });
|
||||
p.addEventListener("pointermove",(e)=>{ if(!dragging) return; const dy=startY-e.clientY; if(Math.abs(dy)>6){ moved=true; clearTimeout(lpTimer); ramp.on=false; setBpm(startBpm+dy*0.5); renderAll(); } });
|
||||
p.addEventListener("pointerup",(e)=>{ if(!dragging) return; dragging=false; clearTimeout(lpTimer); try{p.releasePointerCapture(e.pointerId);}catch(_){} if(!moved && !lpFired) tapTempo(); });
|
||||
p.addEventListener("pointercancel",()=>{ dragging=false; clearTimeout(lpTimer); });
|
||||
$("bTapBtn").onclick=tapTempo; // TAP button (left of the BPM) — tap to set
|
||||
$("bpm").onclick=()=>{ if(!editingBpm) openBpmEdit(); }; // tap the number to type
|
||||
(function(){ const w=$("wheel"); let dragging=false, startY=0, startBpm=120; // thumbwheel (right) — drag to scrub
|
||||
w.addEventListener("pointerdown",(e)=>{ dragging=true; startY=e.clientY; startBpm=state.bpm; w.setPointerCapture(e.pointerId); e.preventDefault(); });
|
||||
w.addEventListener("pointermove",(e)=>{ if(!dragging) return; ramp.on=false; setBpm(startBpm+(startY-e.clientY)*0.5); renderAll(); });
|
||||
w.addEventListener("pointerup",(e)=>{ dragging=false; try{w.releasePointerCapture(e.pointerId);}catch(_){} });
|
||||
w.addEventListener("pointercancel",()=>{ dragging=false; });
|
||||
})();
|
||||
|
||||
/* ========================= PERSIST / RESTORE STATE =========================== */
|
||||
|
|
@ -833,7 +836,7 @@ const TOUR=[
|
|||
{sel:"#lanes", title:"Edit the beat", text:"Each lane is a row of pads that blink on the beat — tap a pad to cycle rest → beat → accent → ghost. Tap a lane's label to set its note value (eighths, triplets, sixteenths…), sound, grouping, mute or polymeter. “+ Add lane” for more."},
|
||||
{sel:"#trackpanel", title:"Track settings", text:"Optional per-track extras (under the lanes): Repeat for N bars then stop / next / prev track, a tempo ramp, and practice gaps."},
|
||||
{sel:"#bPrac", title:"Practice = a timed session", text:"Play just runs the metronome. Practice times your playing and logs it (not audio): it starts a session clock and Play becomes Stop — start/pause each track, then Stop to save the session."},
|
||||
{sel:"#sessbar", title:"Your practice log", text:"Review past sessions, add notes, and compare a track across days."},
|
||||
{sel:"#bJournal", title:"Practice journal", text:"Opens your saved practice sessions — notes and a per-track breakdown across days. While Practice is recording, this shows the live session timer."},
|
||||
];
|
||||
let tstep=0;
|
||||
function startTour(){ tstep=0; $("tour").classList.add("open"); showTour(); }
|
||||
|
|
@ -863,7 +866,7 @@ $("bPrev").onclick=()=>gotoItem(idx-1,state.running); $("bNext").onclick=()=>got
|
|||
$("bUp").onclick=()=>nudge(+1); $("bDown").onclick=()=>nudge(-1);
|
||||
$("bUp10").onclick=()=>nudge(+10); $("bDn10").onclick=()=>nudge(-10);
|
||||
$("vol").oninput=(e)=>{ state.volume=(+e.target.value)/100; if(masterGain) masterGain.gain.value=state.volume; saveState(); };
|
||||
$("sessLink").addEventListener("click",(e)=>{ if(sessionActive) e.preventDefault(); }); // don't lose an in-progress session
|
||||
$("bJournal").addEventListener("click",()=>{ if(!sessionActive) location.href="/mobile-sessions.html"; }); // mid-session: just shows the live timer
|
||||
|
||||
/*@BUILD:include:src/chrome.js@*/
|
||||
|
||||
|
|
@ -902,7 +905,6 @@ if(location.hash && /(p|sl)=/.test(location.hash)) loadFromHash(location.hash);
|
|||
else restoreState();
|
||||
if(!setlist){ slKey="b0"; setlist=BUILTIN[0]; idx=0; loadSetup(setlist.items[0]); }
|
||||
buildSetlistOptions(); buildTrackOptions();
|
||||
$("bpmMark").innerHTML=rhythmSVG(1); // ♩ in the "♩ = N" tempo marking
|
||||
$("vol").value=Math.round(state.volume*100); if(masterGain) masterGain.gain.value=state.volume;
|
||||
renderAll(); renderSessionBar();
|
||||
requestAnimationFrame(draw);
|
||||
|
|
|
|||
Loading…
Reference in a new issue