From 8b795d4107372969f3fb2f097943e0e45680e903 Mon Sep 17 00:00:00 2001 From: Me Here Date: Sun, 7 Jun 2026 09:44:31 -0500 Subject: [PATCH] pm-mobile: editable lane pads, guided help tour, persisted state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Editable lanes (no notation/konnakol — just pads): each lane is a row of pads that blink on the beat; tap a pad to cycle rest → beat → accent → ghost. A lane's label opens a sheet to set sound, grouping (e.g. 2+2+3), subdivision, swing, mute and polymeter; plus "+ Add lane" / delete. Edits are live and feed straight into the scheduler. (Replaces the read-only lane chips; the global feature chips — bars/end/ramp/gaps — stay.) - Help: a "?" runs a 7-step guided coachmark tour (spotlight + tooltip), shown once on first run and re-runnable anytime. Removed the instruction hint under the BPM (the tour covers it). Tour also frames tracks as named practice items. - Persist + restore: the working state (set list / track / tempo / volume / lane edits) is saved to metronome.mobile.state and restored on reload. - Dropped the separate beat-dot row — the pulse flash + per-lane pad playhead cover it, freeing room for the editable lanes. Engine untouched; conformance suite unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) --- mobile.html | 442 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 271 insertions(+), 171 deletions(-) diff --git a/mobile.html b/mobile.html index 4f6e873..2993183 100644 --- a/mobile.html +++ b/mobile.html @@ -2,11 +2,9 @@ - VARASYS PolyMeter — Mobile - @@ -47,78 +45,72 @@ --btn1:#ffffff; --btn2:#e7edf4; --btn-bd:#c8d2de; --chip-bg:#eef2f7; --chip-bd:#d3dbe5; } html,body{ height:100%; } - body{ - margin:0; overflow:hidden; color:var(--txt); + body{ margin:0; overflow:hidden; color:var(--txt); background:radial-gradient(circle at 50% -12%, var(--bg1), var(--bg2)); font-family:"Segoe UI", Roboto, Helvetica, Arial, sans-serif; -webkit-user-select:none; user-select:none; -webkit-tap-highlight-color:transparent; - touch-action:manipulation; overscroll-behavior:none; - } + touch-action:manipulation; overscroll-behavior:none; } #app{ position:fixed; inset:0; overflow:hidden; display:flex; flex-direction:column; padding:max(8px,env(safe-area-inset-top)) max(12px,env(safe-area-inset-right)) max(8px,env(safe-area-inset-bottom)) max(12px,env(safe-area-inset-left)); } - /* ---- top: set-list + track dropdowns, volume, theme/fullscreen ---- */ + /* ---- top ---- */ #top{ flex:0 0 auto; display:flex; flex-direction:column; gap:8px; } .sels{ display:flex; gap:8px; } .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; } + .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:9px; color:var(--muted); font-size:15px; min-width:0; } .vol input{ flex:1 1 auto; min-width:0; accent-color:var(--cyan); } .icon{ flex:0 0 auto; width:42px; height:42px; border-radius:50%; display:flex; align-items:center; justify-content:center; - font-size:18px; line-height:1; cursor:pointer; color:var(--txt); - background:rgba(127,139,154,.14); border:1px solid var(--panel-bd); } + font-size:18px; line-height:1; cursor:pointer; color:var(--txt); background:rgba(127,139,154,.14); border:1px solid var(--panel-bd); } .icon:active{ background:rgba(127,139,154,.30); } - /* ---- middle: stage (beats+pulse) + right column (detail + transport) ---- */ + /* ---- middle: stage (pulse) + right column (lanes/features + transport) ---- */ #mid{ flex:1 1 auto; min-height:0; display:flex; flex-direction:column; gap:6px; } - #stage{ flex:1 1 auto; min-height:0; display:flex; flex-direction:column; align-items:center; justify-content:center; - gap:clamp(10px,2.4vmin,30px); } - #beats{ display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:clamp(8px,1.7vmin,16px); max-width:92%; } - .dot{ width:clamp(12px,2.8vmin,28px); height:clamp(12px,2.8vmin,28px); border-radius:50%; - background:var(--led-off); border:1px solid rgba(0,0,0,.35); transition:background .05s, box-shadow .05s, transform .05s; } - .dot.group{ outline:1.5px solid var(--amber); outline-offset:3px; opacity:.95; } - .dot.on{ background:var(--cyan); box-shadow:0 0 12px var(--glow); transform:scale(1.12); } - .dot.on.group{ background:var(--amber); box-shadow:0 0 14px var(--aglow); } - - #pulse{ position:relative; width:clamp(160px,40vmin,380px); height:clamp(160px,40vmin,380px); border-radius:50%; + #stage{ flex:1 1 auto; min-height:0; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:clamp(8px,2vmin,22px); } + #pulse{ position:relative; width:clamp(150px,36vmin,340px); height:clamp(150px,36vmin,340px); border-radius:50%; 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%); - transition:transform .12s ease-out, box-shadow .12s ease-out, border-color .12s ease-out; - touch-action:none; cursor:pointer; } + transition:transform .12s ease-out, box-shadow .12s ease-out, border-color .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.acc{ border-color:var(--amber); box-shadow:0 0 72px var(--aglow); } - #bpm{ font-size:clamp(50px,16vmin,150px); font-weight:800; line-height:.85; font-variant-numeric:tabular-nums; letter-spacing:-.01em; } + #bpm{ font-size:clamp(46px,15vmin,140px); font-weight:800; line-height:.85; font-variant-numeric:tabular-nums; letter-spacing:-.01em; } #bpmlab{ font-size:clamp(10px,2vmin,16px); letter-spacing:.3em; color:var(--muted); margin-top:.6em; } - #bpmhint{ font-size:clamp(9px,1.6vmin,12px); color:var(--muted); opacity:.7; margin-top:.5em; } - #bpmIn{ display:none; width:64%; text-align:center; font:inherit; font-size:clamp(46px,14vmin,130px); 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{ display:none; width:64%; text-align:center; font:inherit; font-size:clamp(42px,13vmin,120px); 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; } #meterline{ font-size:clamp(12px,2.1vmin,16px); color:var(--muted); text-align:center; min-height:1.2em; letter-spacing:.02em; } - /* ---- detail: lanes + features as chips ---- */ - #detail{ flex:0 0 auto; max-height:18vh; overflow-y:auto; display:flex; flex-direction:column; gap:5px; padding:2px 0; } + /* ---- editable lanes + feature chips ---- */ + #detail{ flex:0 0 auto; max-height:30vh; overflow-y:auto; display:flex; flex-direction:column; gap:6px; padding:2px 0; } + #lanes{ display:flex; flex-direction:column; gap:6px; } + .lane{ display:flex; align-items:center; gap:8px; } + .lane.off{ opacity:.5; } + .lmeta{ flex:0 0 auto; width:30%; max-width:130px; min-width:64px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; text-align:left; + background:var(--chip-bg); border:1px solid var(--chip-bd); color:var(--txt); border-radius:8px; padding:6px 8px; + font-size:clamp(10px,1.8vmin,13px); font-family:"Courier New",monospace; cursor:pointer; } + .lmeta .lg{ color:var(--muted); } + .pads{ flex:1 1 auto; display:flex; gap:3px; overflow-x:auto; padding-bottom:2px; min-width:0; } + .pad{ flex:1 0 14px; min-width:14px; height:clamp(20px,3.6vmin,28px); border-radius:5px; border:1px solid var(--chip-bd); background:var(--led-off); cursor:pointer; padding:0; } + .pad.gs{ border-color:var(--amber); } + .pad.on{ background:var(--cyan); } + .pad.acc{ background:var(--amber); } + .pad.ghost{ background:var(--cyan); opacity:.42; } + .pad.cur{ outline:2px solid var(--txt); outline-offset:-1px; box-shadow:0 0 8px var(--glow); } + .addlane{ align-self:flex-start; background:transparent; border:1px dashed var(--chip-bd); color:var(--muted); border-radius:8px; padding:5px 11px; font-size:12px; cursor:pointer; } .chips{ display:flex; flex-wrap:wrap; gap:6px; justify-content:center; } - .chip{ font-size:clamp(10px,1.8vmin,13px); font-family:"Courier New",monospace; background:var(--chip-bg); border:1px solid var(--chip-bd); - color:var(--txt); border-radius:7px; padding:3px 8px; white-space:nowrap; } - .chip.off{ opacity:.45; text-decoration:line-through; } - .chip.feat{ font-family:inherit; color:var(--muted); } - .chip.feat.r{ border-color:var(--cyan); color:var(--cyan); } - .chip.feat.g{ border-color:var(--amber); color:var(--amber); } + .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: tempo grid (−10/− +/+10) + prev/next + play/practice ---- */ - #transport{ flex:0 0 auto; display:flex; justify-content:center; padding-top:4px; } - .tgrid{ display:grid; width:100%; max-width:560px; gap:clamp(6px,1.7vmin,13px); - grid-template-columns:1fr 1.5fr 1.5fr 1fr; - grid-template-areas:"dn10 prev next up10" "dn play prac up"; } + /* ---- transport ---- */ + #transport{ flex:0 0 auto; display:flex; justify-content:center; padding-top:2px; } + .tgrid{ display:grid; width:100%; max-width:560px; gap:clamp(6px,1.6vmin,13px); + grid-template-columns:1fr 1.5fr 1.5fr 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); - border-radius:14px; height:clamp(48px,12vmin,74px); font-size:clamp(18px,4.6vmin,28px); 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; } + border-radius:14px; height:clamp(46px,11vmin,72px); font-size:clamp(18px,4.4vmin,28px); 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; } .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} @@ -128,26 +120,53 @@ .tbtn.prac{ background:linear-gradient(180deg,#1c87b8,#136488); border-color:#1f9bd0; color:#eaf8ff; } .tbtn.prac.on{ background:linear-gradient(180deg,#c8922a,#9c6f12); border-color:#e0a93a; color:#fff; } - /* landscape phones: pulse left, right column = detail + transport */ @media (orientation:landscape) and (max-height:600px){ #mid{ flex-direction:row; align-items:stretch; gap:3vw; } - #stage{ flex:1 1 50%; gap:clamp(6px,1.6vmin,14px); } - #rightcol{ flex:1 1 50%; display:flex; flex-direction:column; justify-content:center; gap:6px; min-width:0; } - #pulse{ width:clamp(140px,38vmin,280px); height:clamp(140px,38vmin,280px); } - #bpmlab,#bpmhint{ display:none; } - #detail{ max-height:30vh; } + #stage{ flex:1 1 46%; gap:clamp(6px,1.6vmin,14px); } + #rightcol{ flex:1 1 54%; display:flex; flex-direction:column; justify-content:center; gap:6px; min-width:0; } + #pulse{ width:clamp(130px,34vmin,260px); height:clamp(130px,34vmin,260px); } + #bpmlab{ display:none; } + #detail{ max-height:42vh; } .tbtn{ height:clamp(40px,12vmin,60px); } } - #rightcol{ display:contents; } /* portrait: detail + transport flow normally in #mid */ + #rightcol{ display:contents; } - /* ---- session bar (bottom) ---- */ - #sessbar{ flex:0 0 auto; display:flex; align-items:center; gap:8px; width:100%; margin-top:6px; - padding:9px 12px; border-radius:11px; cursor:pointer; text-decoration:none; - background:rgba(127,139,154,.10); border:1px solid var(--panel-bd); color:var(--muted); font-size:13px; } + /* ---- session bar ---- */ + #sessbar{ flex:0 0 auto; display:flex; align-items:center; gap:8px; width:100%; margin-top:6px; padding:9px 12px; border-radius:11px; + cursor:pointer; text-decoration:none; 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); cursor:default; } #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; } + #sessbar.rec .dotrec{ display:block; } #sessText{ flex:1 1 auto; } + + /* ---- bottom sheet (lane editor) ---- */ + #scrim{ position:fixed; inset:0; background:rgba(0,0,0,.55); opacity:0; pointer-events:none; transition:opacity .2s; z-index:40; } + #scrim.open{ opacity:1; pointer-events:auto; } + #laneSheet{ position:fixed; left:0; right:0; bottom:0; z-index:50; max-height:84vh; overflow-y:auto; + background:var(--panel-bg); border-top:1px solid var(--panel-bd); border-radius:18px 18px 0 0; transform:translateY(110%); + transition:transform .26s cubic-bezier(.2,.8,.2,1); + padding:12px max(16px,env(safe-area-inset-right)) max(18px,env(safe-area-inset-bottom)) max(16px,env(safe-area-inset-left)); } + #laneSheet.open{ transform:none; } + #laneSheet .grab{ width:42px; height:5px; border-radius:3px; background:var(--panel-bd); margin:0 auto 12px; } + #laneSheet h2{ margin:0 0 10px; font-size:16px; } + #laneSheet label{ display:block; font-size:12px; color:var(--muted); margin:10px 0 5px; } + #laneSheet select, #laneSheet input[type=text]{ width:100%; background:var(--field-bg); color:var(--txt); border:1px solid var(--field-bd); border-radius:10px; padding:11px; font-size:15px; } + .lrow{ display:flex; gap:14px; margin-top:10px; flex-wrap:wrap; } + .lrow .half{ flex:1 1 120px; margin:0; } + .chk{ display:flex; align-items:center; gap:8px; font-size:14px; color:var(--txt); margin-top:18px; } + .chk input{ width:20px; height:20px; accent-color:var(--cyan); } + .lfoot{ display:flex; justify-content:space-between; margin-top:18px; } + .lbtn{ cursor:pointer; color:var(--txt); background:linear-gradient(180deg,var(--btn1),var(--btn2)); border:1px solid var(--btn-bd); border-radius:10px; padding:10px 18px; font-size:14px; } + .lbtn.danger{ background:transparent; color:#ff7a7a; border-color:#ff7a7a; } + + /* ---- help tour (coachmarks) ---- */ + #tour{ position:fixed; inset:0; z-index:200; display:none; } + #tour.open{ display:block; } + #tourHole{ position:absolute; border-radius:12px; box-shadow:0 0 0 9999px rgba(0,0,0,.66); border:2px solid var(--cyan); transition:all .2s ease; pointer-events:none; } + #tourBox{ position:absolute; background:var(--panel-bg); border:1px solid var(--panel-bd); border-radius:12px; padding:14px; box-shadow:0 14px 44px rgba(0,0,0,.5); } + #tourBox h3{ margin:0 0 6px; font-size:15px; } + #tourBox p{ margin:0 0 12px; font-size:13px; color:var(--muted); line-height:1.45; } + #tourBox .trow{ display:flex; justify-content:space-between; align-items:center; gap:10px; } + .tdots{ font-size:12px; color:var(--muted); } @@ -160,6 +179,7 @@
🔈🔊
+
?
@@ -167,19 +187,17 @@
-
120
BPM
-
tap = tap tempo · hold = type · drag = scrub
-
+
@@ -190,7 +208,7 @@ - +
@@ -200,16 +218,47 @@ Practice sessions →
+ +
+
+
+

Edit lane

+ + +
+ + +
+
+ + +
+
+
+ + +
+
+
+

+
+ + +
+
+
+