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:
parent
d27bf07069
commit
344ff43ceb
1 changed files with 49 additions and 37 deletions
86
mobile.html
86
mobile.html
|
|
@ -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)); } }
|
||||
|
||||
/* ---- top ---- */
|
||||
#top{ flex:0 0 auto; display:flex; flex-direction:column; gap:9px; }
|
||||
#brandrow{ display:flex; align-items:center; }
|
||||
#top{ flex:0 0 auto; display:flex; flex-direction:column; gap:11px; }
|
||||
#brandrow{ display:flex; align-items:center; gap:10px; }
|
||||
#logoLink{ display:inline-flex; opacity:.9; }
|
||||
.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; }
|
||||
.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 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; }
|
||||
.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); }
|
||||
.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;
|
||||
|
|
@ -77,7 +79,7 @@
|
|||
.icon:active{ background:rgba(127,139,154,.30); }
|
||||
|
||||
/* ---- 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%; }
|
||||
/* tempo plate: [TAP] [big BPM] [thumbwheel] — flashes on the beat */
|
||||
#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;
|
||||
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;
|
||||
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); }
|
||||
/* 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); }
|
||||
#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; }
|
||||
|
|
@ -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; }
|
||||
#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;
|
||||
#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);
|
||||
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); }
|
||||
background:repeating-linear-gradient(to bottom, rgba(0,0,0,.22) 0 1px, transparent 1px 5px),
|
||||
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)),
|
||||
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); }
|
||||
#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) ---- */
|
||||
/* 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);
|
||||
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: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; }
|
||||
.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;
|
||||
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; }
|
||||
|
|
@ -188,16 +198,18 @@
|
|||
pulse + transport on the left, panel + lanes on the right */
|
||||
@media (orientation:landscape){
|
||||
#app{ --maxw:1060px; }
|
||||
#top{ flex-direction:row-reverse; align-items:flex-end; gap:14px; }
|
||||
#top .sels{ flex:3 1 0; min-width:0; }
|
||||
#top .trow{ flex:2 1 0; min-width:0; }
|
||||
#top{ flex-direction:row; flex-wrap:wrap; align-items:center; gap:8px 14px; }
|
||||
#brandrow{ flex:1 1 100%; }
|
||||
#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;
|
||||
grid-template-columns:40% 60%; grid-template-rows:1fr auto;
|
||||
grid-template-areas:"stage detail" "transport detail"; }
|
||||
grid-template-columns:40% 60%; grid-template-rows:auto 1fr auto;
|
||||
grid-template-areas:"panel detail" "stage detail" "transport detail"; }
|
||||
#trackpanel{ grid-area:panel; align-self:start; }
|
||||
#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; max-height:none; }
|
||||
.tbtn{ height:clamp(38px,12vmin,62px); }
|
||||
#transport{ grid-area:transport; align-self:end; max-height:none; margin-top:0; }
|
||||
.tbtn{ height:clamp(36px,11vmin,58px); }
|
||||
}
|
||||
|
||||
[data-theme="light"] .logo-dark{ display:none; } [data-theme="dark"] .logo-light{ display:none; }
|
||||
|
|
@ -254,14 +266,14 @@
|
|||
<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 class="hicons" id="utilrow">
|
||||
<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="themeBtn" title="Theme" aria-label="Theme">◐</div>
|
||||
<div class="icon" id="fsBtn" title="Full screen" aria-label="Full screen">⛶</div>
|
||||
</div>
|
||||
</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>
|
||||
<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="fsBtn" title="Full screen" aria-label="Full screen">⛶</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">
|
||||
<label class="sel"><span>Set list</span><select id="slSel"></select></label>
|
||||
<label class="sel"><span>Track</span><select id="trkSel"></select></label>
|
||||
|
|
@ -270,6 +282,16 @@
|
|||
</div>
|
||||
|
||||
<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="pulse">
|
||||
<button id="bTapBtn" class="tapbtn" title="Tap tempo">TAP</button>
|
||||
|
|
@ -281,16 +303,6 @@
|
|||
</div>
|
||||
<div id="detail">
|
||||
<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 id="transport">
|
||||
<div class="tgrid">
|
||||
|
|
@ -829,12 +841,12 @@ function loadFromHash(text){
|
|||
|
||||
/* ========================= HELP 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:"#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:"#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:"#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."},
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in a new issue