pm-mobile: shorter transport at bottom, header icons by logo, full-width volume, beat-glow TAP, roller wheel, track-panel above tempo

- Transport buttons reduced in height and pinned to the bottom; a flexible
  gap sits between the lanes and the buttons. Buttons shrink as more lanes
  are added (lanes grow, the spacer collapses, then the button grid shrinks).
- Header: share/help/theme/fullscreen icons moved up onto the logo row
  (right-aligned, smaller); volume slider is now its own full-width row.
- TAP button glows in time with the beat (rides the #pulse flash).
- BPM thumbwheel restyled as a horizontal roller: top/bottom end shadows +
  cylinder shading so it reads as a wheel you scrape vertically.
- Repeat / ramp / practice-gap panel moved above the BPM readout.
- #mid spacing opened up; landscape grid + header updated to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Me Here 2026-06-07 15:35:13 -05:00
parent d27bf07069
commit 344ff43ceb

View file

@ -60,16 +60,18 @@
@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)); } } @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 ---- */
#top{ flex:0 0 auto; display:flex; flex-direction:column; gap:9px; } #top{ flex:0 0 auto; display:flex; flex-direction:column; gap:11px; }
#brandrow{ display:flex; align-items:center; } #brandrow{ display:flex; align-items:center; gap:10px; }
#logoLink{ display:inline-flex; opacity:.9; } #logoLink{ display:inline-flex; opacity:.9; }
.brandlogo{ height:clamp(17px,3.4vmin,24px); width:auto; display:block; } .brandlogo{ height:clamp(17px,3.4vmin,24px); width:auto; display:block; }
.hicons{ display:flex; align-items:center; gap:8px; margin-left:auto; }
.hicons .icon{ width:36px; height:36px; font-size:16px; }
.sels{ display:flex; gap:8px; align-items:flex-end; } .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{ 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; } .sel > span{ font-size:10px; letter-spacing:.14em; text-transform:uppercase; color:var(--muted); padding-left:3px; }
.sel select{ width:100%; background:var(--field-bg); color:var(--txt); border:1px solid var(--field-bd); border-radius:10px; padding:10px 8px; font-size:15px; } .sel select{ width:100%; background:var(--field-bg); color:var(--txt); border:1px solid var(--field-bd); border-radius:10px; padding:10px 8px; font-size:15px; }
.trow{ display:flex; align-items:center; gap:10px; } .trow{ display:flex; align-items:center; gap:10px; }
.vol{ flex:1 1 auto; display:flex; align-items:center; gap:10px; color:var(--muted); min-width:0; } .vol{ width:100%; display:flex; align-items:center; gap:12px; color:var(--muted); min-width:0; }
.vol input{ flex:1 1 auto; min-width:0; accent-color:var(--cyan); } .vol input{ flex:1 1 auto; min-width:0; accent-color:var(--cyan); }
.dyn{ flex:0 0 auto; font-family:Georgia,"Times New Roman",serif; font-style:italic; font-weight:700; font-size:17px; color:var(--muted); line-height:1; } .dyn{ flex:0 0 auto; font-family:Georgia,"Times New Roman",serif; font-style:italic; font-weight:700; font-size:17px; color:var(--muted); line-height:1; }
.icon{ flex:0 0 auto; width:42px; height:42px; border-radius:50%; display:flex; align-items:center; justify-content:center; .icon{ flex:0 0 auto; width:42px; height:42px; border-radius:50%; display:flex; align-items:center; justify-content:center;
@ -77,7 +79,7 @@
.icon:active{ background:rgba(127,139,154,.30); } .icon:active{ background:rgba(127,139,154,.30); }
/* ---- 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:flex-start; gap:clamp(12px,2.8vmin,24px); }
#stage{ flex:0 0 auto; display:flex; flex-direction:column; align-items:center; width:100%; } #stage{ flex:0 0 auto; display:flex; flex-direction:column; align-items:center; width:100%; }
/* tempo plate: [TAP] [big BPM] [thumbwheel] — flashes on the beat */ /* tempo plate: [TAP] [big BPM] [thumbwheel] — flashes on the beat */
#pulse{ position:relative; width:100%; padding:clamp(8px,1.8vmin,14px); border-radius:16px; #pulse{ position:relative; width:100%; padding:clamp(8px,1.8vmin,14px); border-radius:16px;
@ -89,7 +91,13 @@
.tapbtn{ flex:0 0 auto; align-self:stretch; min-width:clamp(58px,16vmin,100px); border-radius:12px; .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); 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; font-size:clamp(13px,2.8vmin,18px); font-weight:600; letter-spacing:.14em; cursor:pointer;
transition:box-shadow .1s ease-out, border-color .1s ease-out, color .1s ease-out;
box-shadow:0 3px 0 rgba(0,0,0,.22), inset 0 1px 0 rgba(255,255,255,.06); } box-shadow:0 3px 0 rgba(0,0,0,.22), inset 0 1px 0 rgba(255,255,255,.06); }
/* TAP button glows in time with the beat (rides the #pulse flash) */
#pulse.hit .tapbtn{ border-color:var(--cyan); color:var(--cyan);
box-shadow:0 3px 0 rgba(0,0,0,.22), inset 0 1px 0 rgba(255,255,255,.06), 0 0 16px var(--glow), inset 0 0 12px var(--glow); }
#pulse.hit.acc .tapbtn{ border-color:var(--amber); color:var(--amber);
box-shadow:0 3px 0 rgba(0,0,0,.22), inset 0 1px 0 rgba(255,255,255,.06), 0 0 18px var(--aglow), inset 0 0 14px var(--aglow); }
.tapbtn:active{ transform:translateY(2px); box-shadow:0 1px 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; } #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; } #bpmNum{ font-size:clamp(44px,15vmin,120px); font-weight:800; font-variant-numeric:tabular-nums; letter-spacing:-.01em; }
@ -98,11 +106,13 @@
background:transparent; color:var(--txt); border:none; border-bottom:2px solid var(--cyan); outline:none; font-variant-numeric:tabular-nums; -moz-appearance:textfield; } 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; } #bpmIn::-webkit-outer-spin-button, #bpmIn::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }
/* thumbwheel encoder — drag up/down to scrape the tempo */ /* 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; #wheel{ flex:0 0 auto; align-self:stretch; width:clamp(30px,7.5vmin,46px); border-radius:11px; cursor:ns-resize; touch-action:none; overflow:hidden;
border:1px solid var(--btn-bd); border:1px solid var(--btn-bd);
background:repeating-linear-gradient(to bottom, rgba(0,0,0,.16) 0 1px, transparent 1px 6px), background:repeating-linear-gradient(to bottom, rgba(0,0,0,.22) 0 1px, transparent 1px 5px),
linear-gradient(to right, rgba(0,0,0,.26), rgba(255,255,255,.14) 48%, rgba(0,0,0,.26)), var(--field-bg); linear-gradient(to bottom, rgba(0,0,0,.55), rgba(0,0,0,0) 18%, rgba(255,255,255,.16) 50%, rgba(0,0,0,0) 82%, rgba(0,0,0,.55)),
box-shadow:inset 0 0 6px rgba(0,0,0,.25); } linear-gradient(to right, rgba(0,0,0,.18), rgba(255,255,255,.10) 50%, rgba(0,0,0,.18)),
var(--field-bg);
box-shadow:inset 0 10px 9px -8px rgba(0,0,0,.75), inset 0 -10px 9px -8px rgba(0,0,0,.75), inset 0 0 4px rgba(0,0,0,.3); }
#wheel:active{ border-color:var(--cyan); } #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; } #meterline{ font-size:clamp(12px,2.1vmin,16px); color:var(--muted); text-align:center; min-height:1.2em; letter-spacing:.02em; }
@ -163,13 +173,13 @@
/* ---- transport: tempo row (10//+/+10) then nav+play row (prev/play/practice/next) ---- */ /* ---- 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 */ /* 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); } #transport{ flex:0 1 auto; min-height:0; max-height:clamp(118px,30vh,210px); margin-top:auto; display:flex; flex-direction:column; align-items:stretch; width:100%; padding-top:6px; gap:clamp(5px,1.2vmin,9px); }
.tgrid{ display:grid; width:100%; flex:1 1 auto; min-height:0; gap:clamp(7px,1.7vmin,14px); .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"; } 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); .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:42px; font-size:clamp(18px,4.4vmin,30px); cursor:pointer; border-radius:13px; height:auto; min-height:30px; font-size:clamp(15px,3.6vmin,25px); 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; } 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; .journal{ flex:0 0 auto; width:100%; height:clamp(30px,6vmin,42px); border-radius:11px; cursor:pointer;
background:rgba(127,139,154,.10); border:1px solid var(--panel-bd); color:var(--muted); font-size:13px; 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; } 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.rec{ background:rgba(192,57,43,.12); border-color:#c0392b; color:var(--txt); cursor:default; }
@ -188,16 +198,18 @@
pulse + transport on the left, panel + lanes on the right */ pulse + transport on the left, panel + lanes on the right */
@media (orientation:landscape){ @media (orientation:landscape){
#app{ --maxw:1060px; } #app{ --maxw:1060px; }
#top{ flex-direction:row-reverse; align-items:flex-end; gap:14px; } #top{ flex-direction:row; flex-wrap:wrap; align-items:center; gap:8px 14px; }
#top .sels{ flex:3 1 0; min-width:0; } #brandrow{ flex:1 1 100%; }
#top .trow{ flex:2 1 0; min-width:0; } #top .vol{ order:2; flex:2 1 0; width:auto; min-width:0; }
#top .sels{ order:3; flex:3 1 0; min-width:0; }
#mid{ display:grid; align-items:center; gap:8px 4vw; #mid{ display:grid; align-items:center; gap:8px 4vw;
grid-template-columns:40% 60%; grid-template-rows:1fr auto; grid-template-columns:40% 60%; grid-template-rows:auto 1fr auto;
grid-template-areas:"stage detail" "transport detail"; } grid-template-areas:"panel detail" "stage detail" "transport detail"; }
#trackpanel{ grid-area:panel; align-self:start; }
#stage{ grid-area:stage; align-self:center; } #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; } #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; max-height:none; } #transport{ grid-area:transport; align-self:end; max-height:none; margin-top:0; }
.tbtn{ height:clamp(38px,12vmin,62px); } .tbtn{ height:clamp(36px,11vmin,58px); }
} }
[data-theme="light"] .logo-dark{ display:none; } [data-theme="dark"] .logo-light{ display:none; } [data-theme="light"] .logo-dark{ display:none; } [data-theme="dark"] .logo-light{ display:none; }
@ -254,14 +266,14 @@
<div id="top"> <div id="top">
<div id="brandrow"> <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> <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="hicons" id="utilrow">
<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> <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>
<div class="icon" id="helpBtn" title="Help" aria-label="Help">?</div> <div class="icon" id="helpBtn" title="Help" aria-label="Help">?</div>
<div class="icon" id="themeBtn" title="Theme" aria-label="Theme"></div> <div class="icon" id="themeBtn" title="Theme" aria-label="Theme"></div>
<div class="icon" id="fsBtn" title="Full screen" aria-label="Full screen"></div> <div class="icon" id="fsBtn" title="Full screen" aria-label="Full screen"></div>
</div> </div>
</div>
<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="sels"> <div class="sels">
<label class="sel"><span>Set list</span><select id="slSel"></select></label> <label class="sel"><span>Set list</span><select id="slSel"></select></label>
<label class="sel"><span>Track</span><select id="trkSel"></select></label> <label class="sel"><span>Track</span><select id="trkSel"></select></label>
@ -270,6 +282,16 @@
</div> </div>
<div id="mid"> <div id="mid">
<div id="trackpanel">
<div class="tp-row">
<label class="tp-chk"><input type="checkbox" id="ipRepeat" /> Repeat</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>
</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="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 id="stage"> <div id="stage">
<div id="pulse"> <div id="pulse">
<button id="bTapBtn" class="tapbtn" title="Tap tempo">TAP</button> <button id="bTapBtn" class="tapbtn" title="Tap tempo">TAP</button>
@ -281,16 +303,6 @@
</div> </div>
<div id="detail"> <div id="detail">
<div id="lanes"></div> <div id="lanes"></div>
<div id="trackpanel">
<div class="tp-row">
<label class="tp-chk"><input type="checkbox" id="ipRepeat" /> Repeat</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>
</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="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>
<div id="transport"> <div id="transport">
<div class="tgrid"> <div class="tgrid">
@ -829,12 +841,12 @@ function loadFromHash(text){
/* ========================= HELP TOUR ========================================= */ /* ========================= HELP TOUR ========================================= */
const TOUR=[ const TOUR=[
{sel:"#utilrow", title:"Controls", text:"Volume (soft p → loud f), ◐ light/dark theme, ⛶ full screen, the ↑ share menu, and ? to replay this tour anytime."}, {sel:"#utilrow", title:"Controls", text:"Up top: ↑ share menu, ? to replay this tour, ◐ light/dark theme, ⛶ full screen. The volume slider (soft p → loud f) runs full-width just below."},
{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 a lane's label to set its note value (eighths, triplets, sixteenths…), 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:"Optional per-track extras (under the lanes): Repeat for N bars then stop / next / prev track, a tempo ramp, and practice gaps."}, {sel:"#trackpanel", title:"Track settings", text:"Optional per-track extras (above the tempo): 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:"#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."}, {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."},
]; ];