pm-mobile: compact tempo plate, Repeat checkbox, controls under lanes, bigger transport
- Replaced the big BPM circle with a compact tempo "plate" (♩ = N · per minute) that flashes on the beat — reclaims the wasted vertical space. - Transport buttons now grow to fill the freed space (2×4 grid stretches). - Removed the bottom-sheet note-value picker; note value is chosen by graphic inside the lane modal only (kept there as you liked). - Repeat is now a checkbox (like Tempo ramp / Practice gaps); checking it reveals "Play N bars, then stop / next / prev". The whole control group moved BELOW the lanes. Engine untouched; conformance passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8402b4f92c
commit
36b7cacd3f
1 changed files with 30 additions and 42 deletions
72
mobile.html
72
mobile.html
|
|
@ -75,13 +75,14 @@
|
||||||
|
|
||||||
/* ---- middle: pulse + track panel + lanes + transport, centered as one block ---- */
|
/* ---- 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); }
|
#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; gap:clamp(6px,1.6vmin,14px); }
|
#stage{ flex:0 0 auto; display:flex; flex-direction:column; align-items:center; width:100%; }
|
||||||
#pulse{ position:relative; width:clamp(186px,46vmin,360px); height:clamp(186px,46vmin,360px); border-radius:50%;
|
/* 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;
|
display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center;
|
||||||
border:2px solid var(--ring); background:radial-gradient(circle at 50% 40%, rgba(127,139,154,.07), transparent 70%);
|
border:1px solid var(--ring); background:linear-gradient(180deg, rgba(127,139,154,.06), transparent);
|
||||||
transition:transform .12s ease-out, box-shadow .12s ease-out, border-color .12s ease-out; touch-action:none; cursor:pointer; }
|
transition:box-shadow .12s ease-out, border-color .12s ease-out, background .12s ease-out; touch-action:none; cursor:pointer; }
|
||||||
#pulse.hit{ transform:scale(1.045); border-color:var(--cyan); box-shadow:0 0 60px var(--glow); }
|
#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 72px var(--aglow); }
|
#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 */
|
/* tempo as an engraved marking: ♩ = N */
|
||||||
#bpm{ display:inline-flex; align-items:center; justify-content:center; gap:.12em; line-height:.82; }
|
#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; }
|
#bpmNum{ font-size:clamp(44px,14vmin,128px); font-weight:800; font-variant-numeric:tabular-nums; letter-spacing:-.01em; }
|
||||||
|
|
@ -153,12 +154,13 @@
|
||||||
.chip.feat.r{ border-color:var(--cyan); color:var(--cyan); } .chip.feat.g{ border-color:var(--amber); color:var(--amber); }
|
.chip.feat.r{ border-color:var(--cyan); color:var(--cyan); } .chip.feat.g{ border-color:var(--amber); color:var(--amber); }
|
||||||
|
|
||||||
/* ---- transport ---- */
|
/* ---- transport ---- */
|
||||||
#transport{ flex:0 0 auto; display:flex; justify-content:center; padding-top:2px; }
|
/* transport grows to fill the space freed by the compact tempo plate */
|
||||||
.tgrid{ display:grid; width:100%; gap:clamp(6px,1.6vmin,13px);
|
#transport{ flex:1 1 auto; max-height:clamp(150px,42vh,300px); display:flex; align-items:stretch; justify-content:center; width:100%; padding-top:6px; }
|
||||||
grid-template-columns:1fr 1.5fr 1.5fr 1fr; grid-template-areas:"dn10 prev next up10" "dn play prac up"; }
|
.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"; }
|
||||||
.tbtn{ background:linear-gradient(180deg,var(--btn1),var(--btn2)); color:var(--txt); border:1px solid var(--btn-bd);
|
.tbtn{ background:linear-gradient(180deg,var(--btn1),var(--btn2)); color:var(--txt); border:1px solid var(--btn-bd);
|
||||||
border-radius:14px; height:clamp(46px,11vmin,72px); font-size:clamp(18px,4.4vmin,28px); cursor:pointer;
|
border-radius:14px; height:auto; min-height:46px; 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:2px; }
|
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; }
|
||||||
.tbtn small{ font-size:clamp(8px,1.5vmin,11px); letter-spacing:.1em; color:inherit; opacity:.85; }
|
.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); }
|
.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}
|
#bDn10{grid-area:dn10} #bPrev{grid-area:prev} #bNext{grid-area:next} #bUp10{grid-area:up10}
|
||||||
|
|
@ -272,20 +274,17 @@
|
||||||
<div id="meterline"></div>
|
<div id="meterline"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="detail">
|
<div id="detail">
|
||||||
|
<div id="lanes"></div>
|
||||||
<div id="trackpanel">
|
<div id="trackpanel">
|
||||||
<div class="tp-row">
|
<div class="tp-row">
|
||||||
<label>Play <input id="ipBars" type="number" inputmode="numeric" min="0" max="999" /> bars,</label>
|
<label class="tp-chk"><input type="checkbox" id="ipRepeat" /> Repeat</label>
|
||||||
<label id="ipThen">then <select id="ipEnd"><option value="stop">stop</option><option value="next">next track</option><option value="prev">prev track</option></select></label>
|
|
||||||
<span id="ipLoop" class="tp-loop">then loop</span>
|
|
||||||
</div>
|
|
||||||
<div class="tp-row">
|
|
||||||
<label class="tp-chk"><input type="checkbox" id="ipRamp" /> Tempo ramp</label>
|
<label class="tp-chk"><input type="checkbox" id="ipRamp" /> Tempo ramp</label>
|
||||||
<label class="tp-chk"><input type="checkbox" id="ipGap" /> Practice gaps</label>
|
<label class="tp-chk"><input type="checkbox" id="ipGap" /> Practice gaps</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tp-sub off" id="ipRepeatRow">Play <input id="ipBars" type="number" inputmode="numeric" min="1" max="999" /> bars, then <select id="ipEnd"><option value="stop">stop</option><option value="next">next track</option><option value="prev">prev track</option></select></div>
|
||||||
<div class="tp-sub off" id="ipRampRow"><input id="ipRampStart" type="number" min="30" max="300" /> → +<input id="ipRampAmt" type="number" min="1" max="50" /> bpm / <input id="ipRampEvery" type="number" min="1" max="64" /> bars</div>
|
<div class="tp-sub off" id="ipRampRow"><input id="ipRampStart" type="number" min="30" max="300" /> → +<input id="ipRampAmt" type="number" min="1" max="50" /> bpm / <input id="ipRampEvery" type="number" min="1" max="64" /> bars</div>
|
||||||
<div class="tp-sub off" id="ipGapRow"><input id="ipGapPlay" type="number" min="1" max="32" /> play / <input id="ipGapMute" type="number" min="1" max="32" /> mute bars</div>
|
<div class="tp-sub off" id="ipGapRow"><input id="ipGapPlay" type="number" min="1" max="32" /> play / <input id="ipGapMute" type="number" min="1" max="32" /> mute bars</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="lanes"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="transport">
|
<div id="transport">
|
||||||
<div class="tgrid">
|
<div class="tgrid">
|
||||||
|
|
@ -324,13 +323,6 @@
|
||||||
<div class="lfoot"><button id="lsDel" class="lbtn danger">Delete lane</button><button id="lsDone" class="lbtn">Done</button></div>
|
<div class="lfoot"><button id="lsDel" class="lbtn danger">Delete lane</button><button id="lsDone" class="lbtn">Done</button></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- note-value picker (opened by tapping a lane's rhythm icon) -->
|
|
||||||
<div id="noteSheet">
|
|
||||||
<div class="grab"></div>
|
|
||||||
<h2>Note value</h2>
|
|
||||||
<div id="noteOpts" class="noterow"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- share sheet: share a track or set list as a link, or paste a string to load -->
|
<!-- share sheet: share a track or set list as a link, or paste a string to load -->
|
||||||
<div id="shareSheet">
|
<div id="shareSheet">
|
||||||
<div class="grab"></div>
|
<div class="grab"></div>
|
||||||
|
|
@ -539,8 +531,7 @@ function laneMetaHTML(m){ const eff=laneNoteValue(m);
|
||||||
const ref=(meters[0]?meters[0].beatsPerBar:m.beatsPerBar);
|
const ref=(meters[0]?meters[0].beatsPerBar:m.beatsPerBar);
|
||||||
const poly=m.poly?"<span class='polybadge' title='polyrhythm — "+m.beatsPerBar+" over "+ref+"'>↻"+m.beatsPerBar+":"+ref+"</span>":"";
|
const poly=m.poly?"<span class='polybadge' title='polyrhythm — "+m.beatsPerBar+" over "+ref+"'>↻"+m.beatsPerBar+":"+ref+"</span>":"";
|
||||||
return "<span class='ln-name'>"+esc(m.sound)+"</span><span class='rh-host' title='Note value'>"+rhythmSVG(eff)+"</span><span class='lg'>"+esc(m.groupsStr)+"</span>"+poly; }
|
return "<span class='ln-name'>"+esc(m.sound)+"</span><span class='rh-host' title='Note value'>"+rhythmSVG(eff)+"</span><span class='lg'>"+esc(m.groupsStr)+"</span>"+poly; }
|
||||||
function setLaneMeta(m){ if(!m._meta) return; m._meta.innerHTML=laneMetaHTML(m);
|
function setLaneMeta(m){ if(!m._meta) return; m._meta.innerHTML=laneMetaHTML(m); }
|
||||||
const rh=m._meta.querySelector(".rh-host"); if(rh) rh.onclick=(e)=>{ e.stopPropagation(); openNotePicker(m._idx); }; }
|
|
||||||
function buildLanes(){
|
function buildLanes(){
|
||||||
const box=$("lanes"); box.innerHTML="";
|
const box=$("lanes"); box.innerHTML="";
|
||||||
meters.forEach((m,i)=>{
|
meters.forEach((m,i)=>{
|
||||||
|
|
@ -571,9 +562,6 @@ function renderNoteOpts(box, cur, pick){ if(!box) return; box.innerHTML="";
|
||||||
function setLaneSub(i,n){ const m=meters[i]; if(!m) return;
|
function setLaneSub(i,n){ const m=meters[i]; if(!m) return;
|
||||||
rebuildLane(i,{groupsStr:m.groupsStr,stepsPerBeat:n,swing:m.swing,poly:m.poly,enabled:m.enabled,sound:m.sound,gainDb:m.gainDb,beatsOn:m.beatsOn.slice(),orns:(m.orns||[]).slice()});
|
rebuildLane(i,{groupsStr:m.groupsStr,stepsPerBeat:n,swing:m.swing,poly:m.poly,enabled:m.enabled,sound:m.sound,gainDb:m.gainDb,beatsOn:m.beatsOn.slice(),orns:(m.orns||[]).slice()});
|
||||||
laneSig=null; renderAll(); saveState(); }
|
laneSig=null; renderAll(); saveState(); }
|
||||||
function openNotePicker(i){ const m=meters[i]; if(!m) return; editLaneIdx=i;
|
|
||||||
renderNoteOpts($("noteOpts"), m.stepsPerBeat, (n)=>{ setLaneSub(i,n); closeSheets(); });
|
|
||||||
$("laneSheet").classList.remove("open"); $("saveSheet").classList.remove("open"); $("shareSheet").classList.remove("open"); $("scrim").classList.add("open"); $("noteSheet").classList.add("open"); }
|
|
||||||
function refreshLaneSheetNotes(){ const m=meters[editLaneIdx]; if(!m) return;
|
function refreshLaneSheetNotes(){ const m=meters[editLaneIdx]; if(!m) return;
|
||||||
renderNoteOpts($("lsNotes"), m.stepsPerBeat, (n)=>{ setLaneSub(editLaneIdx,n); refreshLaneSheetNotes(); }); }
|
renderNoteOpts($("lsNotes"), m.stepsPerBeat, (n)=>{ setLaneSub(editLaneIdx,n); refreshLaneSheetNotes(); }); }
|
||||||
function renderPadPlayheads(){ meters.forEach(m=>{ if(!m._padEls) return; const cur=state.running?m.currentStep:-1;
|
function renderPadPlayheads(){ meters.forEach(m=>{ if(!m._padEls) return; const cur=state.running?m.currentStep:-1;
|
||||||
|
|
@ -595,8 +583,8 @@ function gainLabel(db){ return (db>0?"+":"")+db+" dB"; }
|
||||||
function openLaneSheet(i){ editLaneIdx=i; const m=meters[i]; if(!m) return;
|
function openLaneSheet(i){ editLaneIdx=i; const m=meters[i]; if(!m) return;
|
||||||
$("lsSound").value=m.sound; $("lsGroup").value=m.groupsStr; $("lsPoly").checked=!!m.poly; $("lsMute").checked=!m.enabled;
|
$("lsSound").value=m.sound; $("lsGroup").value=m.groupsStr; $("lsPoly").checked=!!m.poly; $("lsMute").checked=!m.enabled;
|
||||||
$("lsGain").value=m.gainDb||0; $("lsGainVal").textContent=gainLabel(m.gainDb||0); refreshLaneSheetNotes();
|
$("lsGain").value=m.gainDb||0; $("lsGainVal").textContent=gainLabel(m.gainDb||0); refreshLaneSheetNotes();
|
||||||
$("saveSheet").classList.remove("open"); $("noteSheet").classList.remove("open"); $("shareSheet").classList.remove("open"); $("scrim").classList.add("open"); $("laneSheet").classList.add("open"); }
|
$("saveSheet").classList.remove("open"); $("shareSheet").classList.remove("open"); $("scrim").classList.add("open"); $("laneSheet").classList.add("open"); }
|
||||||
function closeSheets(){ ["laneSheet","saveSheet","noteSheet","shareSheet","scrim"].forEach(id=>$(id).classList.remove("open")); }
|
function closeSheets(){ ["laneSheet","saveSheet","shareSheet","scrim"].forEach(id=>$(id).classList.remove("open")); }
|
||||||
const closeLaneSheet=closeSheets;
|
const closeLaneSheet=closeSheets;
|
||||||
function applyLane(){ const m=meters[editLaneIdx]; if(!m) return;
|
function applyLane(){ const m=meters[editLaneIdx]; if(!m) return;
|
||||||
let grp=($("lsGroup").value||"").trim()||"4";
|
let grp=($("lsGroup").value||"").trim()||"4";
|
||||||
|
|
@ -615,9 +603,9 @@ function clampInt(v,lo,hi,def){ v=parseInt(v,10); if(isNaN(v)) return def; retur
|
||||||
function flashIp(msg){ $("ipMsg").textContent=msg; setTimeout(()=>{ if($("ipMsg").textContent===msg) $("ipMsg").textContent=""; },1600); }
|
function flashIp(msg){ $("ipMsg").textContent=msg; setTimeout(()=>{ if($("ipMsg").textContent===msg) $("ipMsg").textContent=""; },1600); }
|
||||||
function buildTrackPanel(){
|
function buildTrackPanel(){
|
||||||
if(document.activeElement&&document.activeElement.closest&&document.activeElement.closest("#trackpanel")) return; // don't fight the user mid-edit
|
if(document.activeElement&&document.activeElement.closest&&document.activeElement.closest("#trackpanel")) return; // don't fight the user mid-edit
|
||||||
const hasEnd=segBars>0;
|
const rep=segBars>0;
|
||||||
$("ipBars").value=segBars||0;
|
$("ipRepeat").checked=rep; $("ipRepeatRow").classList.toggle("off",!rep);
|
||||||
$("ipThen").style.display=hasEnd?"":"none"; $("ipLoop").style.display=hasEnd?"none":"";
|
$("ipBars").value=segBars||4;
|
||||||
$("ipEnd").value = curEnd==="stop"?"stop":(curEnd===-1?"prev":"next");
|
$("ipEnd").value = curEnd==="stop"?"stop":(curEnd===-1?"prev":"next");
|
||||||
$("ipRamp").checked=ramp.on; $("ipGap").checked=trainer.on;
|
$("ipRamp").checked=ramp.on; $("ipGap").checked=trainer.on;
|
||||||
$("ipRampStart").value=ramp.startBpm||80; $("ipRampAmt").value=ramp.amount||5; $("ipRampEvery").value=ramp.everyBars||4;
|
$("ipRampStart").value=ramp.startBpm||80; $("ipRampAmt").value=ramp.amount||5; $("ipRampEvery").value=ramp.everyBars||4;
|
||||||
|
|
@ -625,15 +613,15 @@ function buildTrackPanel(){
|
||||||
$("ipRampRow").classList.toggle("off",!ramp.on); $("ipGapRow").classList.toggle("off",!trainer.on);
|
$("ipRampRow").classList.toggle("off",!ramp.on); $("ipGapRow").classList.toggle("off",!trainer.on);
|
||||||
}
|
}
|
||||||
function applyTrackPanel(){
|
function applyTrackPanel(){
|
||||||
segBars=Math.max(0,parseInt($("ipBars").value,10)||0);
|
if($("ipRepeat").checked){ segBars=Math.max(1,parseInt($("ipBars").value,10)||4); const e=$("ipEnd").value; curEnd = e==="stop"?"stop":(e==="prev"?-1:1); }
|
||||||
if(segBars>0){ const e=$("ipEnd").value; curEnd = e==="stop"?"stop":(e==="prev"?-1:1); } else { curEnd=null; } // 0 bars = loop forever
|
else { segBars=0; curEnd=null; } // no Repeat = loop forever
|
||||||
$("ipThen").style.display=segBars>0?"":"none"; $("ipLoop").style.display=segBars>0?"none":"";
|
$("ipRepeatRow").classList.toggle("off",!$("ipRepeat").checked);
|
||||||
ramp.on=$("ipRamp").checked; ramp.startBpm=clampInt($("ipRampStart").value,30,300,80); ramp.amount=clampInt($("ipRampAmt").value,1,50,5); ramp.everyBars=clampInt($("ipRampEvery").value,1,64,4);
|
ramp.on=$("ipRamp").checked; ramp.startBpm=clampInt($("ipRampStart").value,30,300,80); ramp.amount=clampInt($("ipRampAmt").value,1,50,5); ramp.everyBars=clampInt($("ipRampEvery").value,1,64,4);
|
||||||
trainer.on=$("ipGap").checked; trainer.playBars=clampInt($("ipGapPlay").value,1,32,2); trainer.muteBars=clampInt($("ipGapMute").value,1,32,2);
|
trainer.on=$("ipGap").checked; trainer.playBars=clampInt($("ipGapPlay").value,1,32,2); trainer.muteBars=clampInt($("ipGapMute").value,1,32,2);
|
||||||
$("ipRampRow").classList.toggle("off",!ramp.on); $("ipGapRow").classList.toggle("off",!trainer.on);
|
$("ipRampRow").classList.toggle("off",!ramp.on); $("ipGapRow").classList.toggle("off",!trainer.on);
|
||||||
saveState();
|
saveState();
|
||||||
}
|
}
|
||||||
["ipBars","ipEnd","ipRamp","ipGap","ipRampStart","ipRampAmt","ipRampEvery","ipGapPlay","ipGapMute"].forEach(id=>$(id).addEventListener("change",applyTrackPanel));
|
["ipRepeat","ipBars","ipEnd","ipRamp","ipGap","ipRampStart","ipRampAmt","ipRampEvery","ipGapPlay","ipGapMute"].forEach(id=>$(id).addEventListener("change",applyTrackPanel));
|
||||||
|
|
||||||
/* ---- save & library: user set lists/tracks (same store + format as the editor) ---- */
|
/* ---- save & library: user set lists/tracks (same store + format as the editor) ---- */
|
||||||
function userSetlists(){ return lsGet(LS_SETLISTS,[]); }
|
function userSetlists(){ return lsGet(LS_SETLISTS,[]); }
|
||||||
|
|
@ -708,7 +696,7 @@ function openSaveSheet(){
|
||||||
$("saveName").value=currentName()||"My track"; buildSaveTo();
|
$("saveName").value=currentName()||"My track"; buildSaveTo();
|
||||||
const upd=$("saveUpd"); if(slKey[0]==="s"){ upd.style.display=""; upd.textContent='Update “'+(currentName()||"track")+'”'; } else upd.style.display="none";
|
const upd=$("saveUpd"); if(slKey[0]==="s"){ upd.style.display=""; upd.textContent='Update “'+(currentName()||"track")+'”'; } else upd.style.display="none";
|
||||||
$("saveMsg").textContent=""; renderLibrary();
|
$("saveMsg").textContent=""; renderLibrary();
|
||||||
$("laneSheet").classList.remove("open"); $("noteSheet").classList.remove("open"); $("shareSheet").classList.remove("open"); $("scrim").classList.add("open"); $("saveSheet").classList.add("open"); }
|
$("laneSheet").classList.remove("open"); $("shareSheet").classList.remove("open"); $("scrim").classList.add("open"); $("saveSheet").classList.add("open"); }
|
||||||
$("saveNew").onclick=doSaveAsNew; $("saveUpd").onclick=doUpdate; $("saveDone").onclick=closeSheets; $("saveBtn").onclick=openSaveSheet;
|
$("saveNew").onclick=doSaveAsNew; $("saveUpd").onclick=doUpdate; $("saveDone").onclick=closeSheets; $("saveBtn").onclick=openSaveSheet;
|
||||||
|
|
||||||
/* ---- share: a track or set list as a link, or paste a string to load ---- */
|
/* ---- share: a track or set list as a link, or paste a string to load ---- */
|
||||||
|
|
@ -721,7 +709,7 @@ function refreshShare(){ $("shareLink").value=shareUrl(); $("shareSeg").querySel
|
||||||
function flashShare(m){ $("shareMsg").textContent=m; setTimeout(()=>{ if($("shareMsg").textContent===m) $("shareMsg").textContent=""; },1600); }
|
function flashShare(m){ $("shareMsg").textContent=m; setTimeout(()=>{ if($("shareMsg").textContent===m) $("shareMsg").textContent=""; },1600); }
|
||||||
function copyText(s, ok){ if(navigator.clipboard&&navigator.clipboard.writeText){ navigator.clipboard.writeText(s).then(ok,()=>{}); } else { const t=$("shareLink"); t.value=s; t.select(); try{ document.execCommand("copy"); ok(); }catch(e){} refreshShare(); } }
|
function copyText(s, ok){ if(navigator.clipboard&&navigator.clipboard.writeText){ navigator.clipboard.writeText(s).then(ok,()=>{}); } else { const t=$("shareLink"); t.value=s; t.select(); try{ document.execCommand("copy"); ok(); }catch(e){} refreshShare(); } }
|
||||||
function openShareSheet(){ shareKind="p"; refreshShare(); $("sharePaste").value=""; $("shareMsg").textContent="";
|
function openShareSheet(){ shareKind="p"; refreshShare(); $("sharePaste").value=""; $("shareMsg").textContent="";
|
||||||
["laneSheet","saveSheet","noteSheet"].forEach(id=>$(id).classList.remove("open")); $("scrim").classList.add("open"); $("shareSheet").classList.add("open"); }
|
["laneSheet","saveSheet"].forEach(id=>$(id).classList.remove("open")); $("scrim").classList.add("open"); $("shareSheet").classList.add("open"); }
|
||||||
$("shareSeg").querySelectorAll("button").forEach(b=>b.onclick=()=>{ shareKind=b.dataset.k; refreshShare(); });
|
$("shareSeg").querySelectorAll("button").forEach(b=>b.onclick=()=>{ shareKind=b.dataset.k; refreshShare(); });
|
||||||
$("shareCopy").onclick=()=>copyText(shareUrl(),()=>flashShare("Link copied ✓"));
|
$("shareCopy").onclick=()=>copyText(shareUrl(),()=>flashShare("Link copied ✓"));
|
||||||
$("shareCopyT").onclick=()=>copyText(shareText(),()=>flashShare("Copied ✓"));
|
$("shareCopyT").onclick=()=>copyText(shareText(),()=>flashShare("Copied ✓"));
|
||||||
|
|
@ -842,8 +830,8 @@ const TOUR=[
|
||||||
{sel:".sels", title:"Pick what to play", text:"Choose a set list and the track within it. Tracks are your practice items — name them for whatever you're working on, even if two share the same beat."},
|
{sel:".sels", title:"Pick what to play", text:"Choose a set list and the track within it. Tracks are your practice items — name them for whatever you're working on, even if two share the same beat."},
|
||||||
{sel:"#saveBtn", title:"Save & library", text:"Save the current track — “Save as new”, or “Update” one of yours. The same sheet is your library: make set lists and rename / reorder / delete tracks. It all lives with the full editor too."},
|
{sel:"#saveBtn", title:"Save & library", text:"Save the current track — “Save as new”, or “Update” one of yours. The same sheet is your library: make set lists and rename / reorder / delete tracks. It all lives with the full editor too."},
|
||||||
{sel:"#pulse", title:"Tempo", text:"Tap the BPM to tap-tempo, press-and-hold to type an exact value, or drag up/down to scrub. ±10 / ±1 buttons nudge it."},
|
{sel:"#pulse", title:"Tempo", text:"Tap the BPM to tap-tempo, press-and-hold to type an exact value, or drag up/down to scrub. ±10 / ±1 buttons nudge it."},
|
||||||
{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 the little note icon to pick the note value (eighths, triplets, sixteenths…); tap the lane name for sound, grouping, mute or polymeter. “+ Add lane” for more."},
|
{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:"How many bars to play and what happens then (loop, stop, or jump to the next/previous track), plus a tempo ramp and practice gaps."},
|
{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:"#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:"#sessbar", title:"Your practice log", text:"Review past sessions, add notes, and compare a track across days."},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue