From a3a09bc77d398e118783fc16f8d146db0126b8bc Mon Sep 17 00:00:00 2001 From: Me Here Date: Sun, 7 Jun 2026 08:21:52 -0500 Subject: [PATCH] pm-mobile: touch-first phone/tablet PWA player (mobile.html) A new full-screen, touch-first edition of the player aimed at phones through tablets - no native app, just a web page you can "Add to Home Screen". Reuses the shared engine + look-ahead scheduler (same player loop as player.html); new UI is a big pulsing beat display, beat-dot row with accent grouping, huge BPM (tap to type, vertical drag to scrub), prev/play/next +/- and tap-tempo, and a bottom sheet for set lists / patch+link loading / volume. Mobile concerns handled: - iOS ring/silent switch: navigator.audioSession.type="playback" + a silent buffer warmup inside the play gesture, so audio isn't muted by the switch. - Screen Wake Lock while running (re-acquired on visibilitychange). - PWA: manifest.webmanifest + apple-touch meta + mobile-sw.js (network-first app shell, passthrough for everything else) -> installable + offline. Multi-file is fine here since it targets mobile (waives the single-file rule). - viewport-fit=cover + safe-area insets, no user zoom, touch-action:manipulation, overscroll-behavior:none; transport buttons flex-share the row so they never overflow a narrow phone; responsive portrait/landscape, phone->tablet. - Fullscreen toggle where supported (Android/desktop; iOS uses home-screen PWA). Wired into build.sh + deploy.sh (page + PWA assets) and added to the index gallery as PM_M-1 Mobile. New metronome app icons generated in assets/. Conformance suite unaffected (engine untouched): 47 pass, 1 known. Co-Authored-By: Claude Opus 4.8 (1M context) --- assets/icon-180.png | Bin 0 -> 5484 bytes assets/icon-192.png | Bin 0 -> 5451 bytes assets/icon-512.png | Bin 0 -> 16106 bytes build.sh | 8 +- deploy.sh | 6 +- index.html | 1 + manifest.webmanifest | 18 ++ mobile-sw.js | 51 +++++ mobile.html | 448 +++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 530 insertions(+), 2 deletions(-) create mode 100644 assets/icon-180.png create mode 100644 assets/icon-192.png create mode 100644 assets/icon-512.png create mode 100644 manifest.webmanifest create mode 100644 mobile-sw.js create mode 100644 mobile.html diff --git a/assets/icon-180.png b/assets/icon-180.png new file mode 100644 index 0000000000000000000000000000000000000000..591653dc81c7268de9bc267978f9f50f1aff1672 GIT binary patch literal 5484 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 literal 0 HcmV?d00001 diff --git a/assets/icon-192.png b/assets/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..17357e3d9dcf6f0fe5d708876c4ccd55f4be2aae GIT binary patch literal 5451 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 literal 0 HcmV?d00001 diff --git a/assets/icon-512.png b/assets/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..c02d04a40fcebc457910e7f64b355246c6d855de GIT binary patch literal 16106 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�j^cvr=@(3+bCRc>AddO8tQ( z(-z^Q&q^4@+++d#Q;upnIwb5jf;ZONd>fx!3QtfV;~+d1WIbY07?Pf0^5)v5wt+Hc zE`L0d!&u%=onP1G@bnAp;bL_#`~9Ykh{R#@0ljozIN0F?Jhf6EauQS?v_2$`vpX0S z3lyzBY&yTBpG?zY2`nl>!S~r@FQrz105!%*N014Ua&j+2uTP0o1RWbk<5|omTNso1 zkWfz^Y%RVg*BqoE!+ML#}r4t*sC&2?wCuwcGo{Ej8A8v253HI?y zJdofb+ANbzAP=B?ed_0i9{04(@E!VzqNXZ7Ts#4~jOp@~^Gnm%kna$*4?f^;41zl_bL;ZcjHz325FYb@6{G2dp>Q0 z6l*)`x>KEwpLPtDf>z`$Ml)-5X2Gh9kEuy+#6*MuNG~N^Du=sUabwDcRjv(3qL61^d zi2FdFwBqaVl)>pw1G@9ybtEZTCfn@3kx>7=%}X0oc>+PiSikbbB;9dk-HhGT=xIzH zo9G{ogIO!Owj76-jS~}+a(~`uAMrtWlAdE#RBcucRsB|C)7>guCvWI6(SN4C8DT46 z?5LU6|1?a#fN#xhBTqEz-gE2OUh4R&mMI%vKEaZ*F}&5IxBYP>@4Z;Ktb1R0u($_4 zJXWyOr#~db;d<7{Czfyx`8#!|vcFHUf@g0}Ttqb!@p8u9soPb~`28SzHJp~g>A*&- z_s#aQ$<;`zo&8%Y_w1KT&16#AKX1(I7?c{FIMbqH#T*}6WFGjre-Xbi-T}U^7&n$JWFG5T&IFaM)4iT#<;w#*trtZjAs$XnAO*L@ zV9{7}cv<6`LR`II>KZ#jOB`nIy|I2a*uMs}{*B)=WJ?+`cxIuVzinx!ALur=`GP#e z?+{r@%9#>)pn>7{=?2`Q?^KrFND~1Ak)c}J%Uo@VXPfLj(+D?agtC>5!T{rCei%ly zCp>nudF3HP?tv}%st=_~L7(6@O$z=BnSTi42TJu7ysLM`*|;x}O0wz;w-QGP_+~>q zhEr?|GxF11Qqb2o&TI59pP3s$^Ph4KNG-he22hk^0m#BAm%*&;V6FTcHiwJZq`&ws zPxhVwxf;-Ajo>e4-uIpEsO76hK6CEBekQ%dfXOlijIqTjAj=jIb`xS^Re8|@Sls2F z$+Ig6W3kk#g-tn%O)s;+RS3iFw6l#mzxx(AU`5-}jQSGK0h8AFprtF<4PUmWmGM86zbA zX-%?$fx|q~&yPFq<48sV4p&eU9AHdZ;bq|(tm94?Oo85aZ-v^wncIgA^Bj(`^2RA3 z2gpEdH;)UfdteOB$K$El*f-}m&)j9P8#|K-X|sN%Fa6f{VjL_@JSsy}TW26FadHd8 zZ1}znw^BY+F8M+Lx1OnH#ns2^XS78Q_?cK@H8i@ww5&&c`WQQMLc>M=UfVzEM-lfho%Iv(3W$35S`Ig-xxb zz+c==ooV)E{PE81mY06E_LQOZd@61d!hL-FWKu%sbb{wZR4C7vhP6iD&T}i~%XY_M z%1IO}Of>$9`)qWXQ7Kc_;zhol_P6c{Nch)aikAoqf&UY6pE>KghcC}pJT>E~>86zP z!rAtW2Nj`T$^0ww3P0CyF<#VP)OOGJ%Y?mHlk*kf%J1swSy}j&a}aOr&1g~F#td7A zu6*faY!3O4-*&zn#G9;vvaA8{#@&ON&4F0>+pK`uEtus0?3X`Nh3FQRnq z!6mh^w_fYLP7;2uI;n)o4n3WsOg)lW=@>+E z215?+d{F9-$;HwBSkEcsVe9QB4R(C3AQ)P zm=)c7z2!AwBhhXfZ`@Cix%kod0T-7%rQBK(kEDvl2IPYCmbY4OhR(rSTYLRvdWbWz%id;=~o`w5U0CWz*p&qtwxzNoJWB7#QTEzUd0{T)NS-Ek}C zPwV^{?mZs8cuJv~oK#+$cGRoQ1}&Dy}1AJJS%SRL%pNsA+SiIP-bTeCO3QFIZO zkj&`2bTigSBLv!NirgTeGrCpHY(wuqek!V04Aq^yaBYivkP<=4FZ=`e-JWqqkqMRf%7V$=L*udXq zBk7<6&@G**?pi7WBq(5Ljp-;*zG4*dyfmveMpi%)3- zfb{FmnNVE;8(&Py;1e9q_eCk=R5n8GZvmZcUTN_<@LNkN{pcsY?`o^9+OAD;gO@No zfyC<0h^)ZExFHExmd_Jq*483QLa#W^-uOFU&Ksi!tL8B?Ox^NxR^Nk~suRzM`$xCW4;FYMB57<&EQYbCe`NigcC-M{^Jk&*hms7U2KveG#?1`!X<7{ zQ807fI<$V8g$jD7)ZLoKxBn2k;t5i^$Z`TlE|mdm9)`(63eJ_i+8VVxUbjDzEPcz3 z)ZRQVZJZOVvP2U*=9m{b8_`7 zj7!O?OYHb}KkAZ=TfcF{m!3+BG?RWK9H55o46g$kia(fOg*a%QcqSj_L;A!IsJfI&I z-hm+C;DV+qrL_%O1_=VD;}g4&r-i11P2NN!*)m=j36ezY8y=mzG<^j|#C;zBC;lGH zcDO&3K;4Iy9_kq(-d&%Jqa_BHf%(&sm}|wH^>jc13tRgcNtZLM85wQ|x7l?_TiPKV zU&HFt$P>&{T}<`yHMPe+nl|=5ddL1;ryi2@uq54fB!Zk=;){db>!D?5IO=Ia;lBKz z)3=7E3bEr8_Bttz9KWfijh^QcoAYS+_3Pg={06|5AWjj-g8?&j!~*FqnNR@OQ6TMu z^TqowdpyD=bwmUizbtx|WyYd1vWMR}8NG810U24}ATrg0fvLE|eG@_?{gc{IsOEEz z){`0as{8#xrZ(V&=4qQTRxI&B9v=W!Y3aO{x$yvNQSxtgW5;^iNfm>HUvXQ^`-`*y z!rVBSnAL*`Gd1gqan!yEF!n6G09FI3#16B!;qNDs7{E~uKuvN%C1EVmnv)OHn#-Tq z8AJOeS3G3o>acX@MYDaJdZm*B42LIx4GmQVjsC%4u|Sn(A_O}9jjvjBlYbqXMyaof z4V50h2MYVql+@yj<&A*ON_@uTfG=WR=qVi$Bihu2oyXIY?C=%i{JJzs{yd0T9z@R_ z?xe0`u99j=bkXz$LWm1jZ?xZuNHK_oy@6w!m_&9#mydJ;8ApGsywPlJ(HX@ zF>9Nu?SG1mXe}zj85z7f#k7Ve1T|Y1=uw8N5n(p3?3ysa99jMi8z6>W!A3&RNhwny zR30Xu<8Vn#j|i{^$_Kf1>9#c-r~>0Iw(I0Q<)~7tVUknR}w__|iE;Rq|$FW9h|mIDilqt9+F$bJyUH`qJ5r zf%6_zn5l9WC91OYSLwebU%JXZbL|%Jn+Y-_Jd^&K%~JhmRMUdw3eP^;j*bd|A>F}| z6}w`Lyx<~d7WX67fu=6Z(!9JcK0rf0ENz!JZlj$}{ea!;zNft_Q)`B^+;z;)L@h>?aIPzYHNNxrQ z+!UHxNriFYWe?NcqS~!seuNxIJqQ;R0A%B82s%izUpPH0CUsD7h5M=kr^!`T&}eG8D3}OV<(hx@vzdQUSV4oE5z{&FSK!Hrg;lZ!i4a_{@O0Nwe zj+7L}TP7z0*|mPQn3K{PHTzH3%I}!}$$J)F!C1eZINO}}ga=%*UPEXf3&^#3W!A>` z!7S$(v{xl6NTxtE?dE2k`7p*Ipxx27z^RM(iys`lG%Mba+^3s65s^gSs zCy>!X)mZ6#8uVX@ibkyMuEwFhM*pith`l*tBEQlUM9)H#tyRmYuOY2Yaa7Z0D4DU> zLmo-mkSpds)+AR|3L@ht2XohTiFdgFxD9V3V3O5@`{(X!X4q~#>T8^BpfiD!ul<RVkN`*Wh*B!T;pDZ6*vR4sTCau) zbl+9|;`<$GQ05UdMsbY`T4#mb*QT&mt?o@LFC8H0efQOHhz;jNE9O3%YY@F+`&ich=@?h z-K?CxaQ`bEXxPXv+FE*S@Ih5ce{sDmjU3w? zO?X;I;HikA{q{#q^80g%`p5y(_a8>S(HRV2$`&K7FJW;0@6}+c&#R3ac6Btk?VldL z0Og(IRX8caOSk_qpOx&H<*{(|E^185)Y3=jF;hR%K2G8`m|^7&1=#eT{pM>0x#0lP zMSL4@di-_RffbbcF+2SpSuSyZyXAsEC{OV$pSY+7InmqL?=UX}x%ulD1^SO6Wj&{s z%L`y2@9+jpleAGoA_Lchd^-dGtNGkSV2?92Y7q8D5oqVS)N=*}R>B- zl8V+v-{G=KV*58fyu?0M!GWqy-6bnxYdk^!iL5=J?9hQEYyo5!`MF>mGJV>Z?6wY_ za9F99&Ue+|LiHmf$m#AIje_M_cF~U-mdp={-vg9YvZ{F=1hv7htV)b%;n*>54 zv`dp9yH$T{8)uLG;>dy;0%U_$ZjZ0rZ=(s$GA!Yjnpt>G=x)#BNopT)OWmoefnb{) zS&TF?W~@VJ9!8KkC(P&Z_(duKvFjXL5y?bPGBN%^?rFXnM&5bhrTdAHPuk~`6c?Qz z4NnJ++{y zXY5fHy~B1=3F7POdsK!LJ++Ow+6U2Xt`sNQ+WzV{DC|;yM-g8sRY9Y1&WD^wKzm0M zqc>9S_#6n?Y$s81CQrrY-K!^U?l1W#nFkThDJ{r-!n=g3?Kk4%$J0_+vE&p!y=TQ57-?{JL>jCTZN3vL3Qy& ztFVIW?Ci&Hb#R>;Wij&4@m(En?Do-xzJrgb*zjk`>E~O_Giy5&EYdZ9!^7O-mgiDn)B{$%B`C2w9iv2GyYO1#jhtxr}*_+3Jp$dqt<+_W#XT4n& zwfPR+!lUgzy|8s@lcPb)<+C9aZh*GoO!&QAaU-k!@YtE?Lw^=;Vyf2RIY4kk0Ix$ru6RYq+8U%_R{yEDl# zW%BLfAzz_HSikqHkL*8{WQzmBIqmjt1z$~r367%ElrRh1K7S)tBM~zc`YHLl$W556 zuFoF=_FCa;Jj_&2B!5ElDLWHvuZBkXWqOE}q$mg60>$422rB=hs}_nV$sruMyfHGL z`cD)wsUrHtwNlMZv3IGt!#esLg{{^JXz_AU# z5ENk#z|*M-$Umc)yG)KtDwoD@p{S=V8-Za?-=BW)Au6+!QZBdZFnF{AE2ZALcP|ei zP<}ArdV|=y{Ch%ubOHL?Zr!;=sZ@UA$vCyVpBia&|2JqVt4C49f z!~VF9=$*Z;b{s^i&_2kXC3cR$updqW>K@VMk;PafGLb5!0yGX)-+uuvZ zA^LNee(JiZ<~i=j)0~>s&n9ZQM1pl2$_hE%V>ig8K8!U#UTeFY^>;nMJK3j1|C~~; z-B*q+JP$b_mUKgnK9Ak`4W3gG3*UtQAsyVhh#8K$h#)5JP?9ufta;rt&0wc+hO+d& z$eg;2Z}2@f(3;|O!o;4YbU*Hxli2wj<+CplFZU-2Eqsq3DOU7~+mA=95@jB2aQS<- z7kw91&#_+Ptw95swFy7)=sR^pJD*}outsU)-F?@tuOBfyB6LiO)?O*URHmo&LDl#B4RZ`@Jh-iczmJ**{pT0X~beJAc*s0iwau@3n3#hj5I z{Is*WI5vo16f{@`k4q4Q@By#9J?a7GGtS)#d~zR1KOQfj)qf`67hTxm_!g7{hzmDT z_-w_Lv8@i1qRn2Mq(AWK?nt3bxHfawQpf$#V0KF00D}y z$wF}&%52+Ogx${(-CPssMBk)>8g^1~94;%3C({qEcGqTmJPX}{9zE~bO30VfHSv6e zBojEVSAJd@@YM|6&^<-{?C953t$s<)+SFaq+nTgY97`FM#+XY?=fP;7GhFn8?w4!g z(gY40^cVaFOg#;1Wz6k!`HQaSD`&nZ%$b}CCLhuqO=8@dNx z1V|R9obNw~X;@P35?a<>&bqDnifzx?3xYbYwL?9Vu3$~%I4*easPp$>=_AC6%h)H| zMwaxf<4Ob}1IGV{ccw|?Oqi1U*Ca^Hf(p;F`2+X9JE+6mXxJ8#WfASI)#dgeazuXoM*Vj%TX-&? zpogYP`=!jMb;%ZjX$3~>{XFdpkB=^6Gj7)`&7%{ecM^qqPU&_ZVdcob&Syq*uB05l zzyhJiD9DfFx@y!tNtIjL2D5di)tPli2@6d}DyuT{bLiiOP||IOKEK5>pCi&zpXn^? z%^E7Pba1t*JY>;CaM2^MVuXEd=H-P zHT%DM5)597{O4D^>`54KcqhmI&8M%Sjo@kVwg1V};=gD3fA4WL#V1@DWSsJUIPq>b zdBM}`|J7sQ?4WSTFw}0?jsMek+5G-t;EnhHxSMP=GcEzmm#^fXi6HP#K~@P_AZ-}% F{{X0p@frXC literal 0 HcmV?d00001 diff --git a/build.sh b/build.sh index 0c64958..bb2c5c6 100755 --- a/build.sh +++ b/build.sh @@ -46,12 +46,18 @@ def build(name): out.write_text(src) return out.stat().st_size -for name in ("index.html","editor.html","editor-beta.html","pm_e-2.html","player.html","teacher.html","stage.html","micro.html","showcase.html","kit.html","explorer.html","grid.html", +for name in ("index.html","editor.html","editor-beta.html","pm_e-2.html","player.html","mobile.html","teacher.html","stage.html","micro.html","showcase.html","kit.html","explorer.html","grid.html", "embed.html", "info-editor.html","info-pm_e-2.html","info-player.html","info-teacher.html","info-stage.html","info-micro.html","info-showcase.html","info-kit.html","info-explorer.html","info-grid.html"): print("built %s (%dKB)" % (name, build(name) // 1024)) pathlib.Path("dist/embed.js").write_text(pathlib.Path("embed.js").read_text()) # loader, served as-is print("copied embed.js") +# PWA support files for mobile.html (the phone/tablet app): manifest, service worker, icons. +for f in ("manifest.webmanifest", "mobile-sw.js"): + pathlib.Path("dist/" + f).write_text(pathlib.Path(f).read_text()) +for f in ("icon-192.png", "icon-512.png", "icon-180.png"): + pathlib.Path("dist/" + f).write_bytes((A / f).read_bytes()) +print("copied PWA files (manifest.webmanifest, mobile-sw.js, icon-{192,512,180}.png)") pathlib.Path("dist/pico-main.py").write_text(pathlib.Path("pico/main.py").read_text()) # PM_K-1 firmware, downloadable print("copied pico-main.py") _appsrc = pathlib.Path("pico-cp/app.py").read_text() diff --git a/deploy.sh b/deploy.sh index 06515bc..5ad06d4 100755 --- a/deploy.sh +++ b/deploy.sh @@ -40,13 +40,17 @@ fi # stamp the version into the built copy only (source stays clean) echo "deployed v$BUILD -> $DEST_DIR" -for f in index.html editor.html editor-beta.html pm_e-2.html player.html teacher.html stage.html micro.html showcase.html kit.html explorer.html grid.html \ +for f in index.html editor.html editor-beta.html pm_e-2.html player.html mobile.html teacher.html stage.html micro.html showcase.html kit.html explorer.html grid.html \ embed.html \ info-editor.html info-pm_e-2.html info-player.html info-teacher.html info-stage.html info-micro.html info-showcase.html info-kit.html info-explorer.html info-grid.html; do sed "s|const APP_VERSION = \"[^\"]*\";|const APP_VERSION = \"$BUILD\";|" "$DIST_DIR/$f" > "$DEST_DIR/$f" echo " $f ($(stat -c '%s' "$DEST_DIR/$f") bytes)" done cp "$DIST_DIR/embed.js" "$DEST_DIR/embed.js"; echo " embed.js ($(stat -c '%s' "$DEST_DIR/embed.js") bytes)" +# PWA assets for mobile.html (manifest + service worker + icons) — served at the web root +for f in manifest.webmanifest mobile-sw.js icon-192.png icon-512.png icon-180.png; do + cp "$DIST_DIR/$f" "$DEST_DIR/$f"; echo " $f ($(stat -c '%s' "$DEST_DIR/$f") bytes)" +done cp "$DIST_DIR/pico-main.py" "$DEST_DIR/pico-main.py"; echo " pico-main.py ($(stat -c '%s' "$DEST_DIR/pico-main.py") bytes)" # PM_K-1 firmware download # Rust firmware (RP2350) — served if built via rust/pm-kit/build.sh (gitignored artifact, not in dist/) if [[ -f "$SRC_DIR/rust/pm-kit/pm-kit.uf2" ]]; then diff --git a/index.html b/index.html index 02bf149..0f82a87 100644 --- a/index.html +++ b/index.html @@ -161,6 +161,7 @@ const SAMPLES = {}; let state = { bpm:120, volume:0.85 }, meters = [], muteWindo const VERSIONS = [ // PM_E-1 (editor.html) is hidden from the landing — PM_E-2 is the focus. The page still exists. { key:"pme2", file:"/pm_e-2.html", name:"PM_E‑2 Editor", chip:"app", h:640, sum:"The PolyMeter editor, built around engraved drum notation — a 5‑line percussion staff (Bravura/SMuFL) with Staff / TUBS / Konnakol views, edit‑on‑staff, plus flams/drags/rolls, odd meters & clave." }, + { key:"mobile", file:"/mobile.html", name:"PM_M‑1 Mobile", chip:"app", h:600, sum:"Phone & tablet app — a touch‑first, full‑screen player you can “Add to Home Screen.” Big tap targets, drag‑to‑scrub tempo, a pulsing beat display, screen‑wake‑lock, and an iOS fix for the ring/silent switch. Installable & works offline." }, { key:"kit", file:"/kit.html", name:"PM_K‑1 Kit", chip:"hw", h:560, sum:"Build it today — a Raspberry Pi Pico on the 52Pi touchscreen kit; tap the 3.5″ screen, joystick tempo, RGB beat light, buzzer. MicroPython firmware included." }, { key:"explorer", file:"/explorer.html", name:"PM_X‑1 Explorer", chip:"hw", h:500, sum:"Off‑the‑shelf — the Pimoroni Explorer (RP2350, 2.8″ LCD, 6 buttons, piezo) as a button‑driven sibling to the Kit. Edit on the web with Live sync; the device mirrors play/stop/tempo/track changes both ways." }, { key:"grid", file:"/grid.html", name:"PM_G‑1 Grid", chip:"hw", h:470, sum:"Off‑the‑shelf — a Pimoroni Pico Scroll Pack (17×7 white LED matrix + 4 buttons) on a Raspberry Pi Pico. The matrix IS the editor's lane × step pad grid in miniature; edit on the web with Live sync." }, diff --git a/manifest.webmanifest b/manifest.webmanifest new file mode 100644 index 0000000..103f2a4 --- /dev/null +++ b/manifest.webmanifest @@ -0,0 +1,18 @@ +{ + "name": "VARASYS PolyMeter", + "short_name": "PolyMeter", + "description": "Polymetric groove-trainer & metronome — touch-first, full-screen.", + "id": "/mobile.html", + "start_url": "/mobile.html?standalone=1", + "scope": "/mobile.html", + "display": "standalone", + "display_override": ["standalone", "fullscreen"], + "orientation": "any", + "background_color": "#05070a", + "theme_color": "#0b0d11", + "categories": ["music", "productivity", "utilities"], + "icons": [ + { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" }, + { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" } + ] +} diff --git a/mobile-sw.js b/mobile-sw.js new file mode 100644 index 0000000..b21bf11 --- /dev/null +++ b/mobile-sw.js @@ -0,0 +1,51 @@ +/* Service worker for the PolyMeter mobile app (mobile.html). + * + * Deliberately minimal and non-intrusive: it only manages its OWN app-shell URLs + * (the page, manifest, icons). For every other request it does NOT call + * respondWith(), so the rest of the site behaves exactly as if no SW existed. + * + * Strategy for the shell: network-first, fall back to cache. The page is a single + * self-contained file that is version-stamped on deploy, so when the device is + * online it always gets the freshest build; offline it still launches from cache. + */ +const CACHE = "polymeter-mobile-v1"; +const SHELL = [ + "/mobile.html", + "/manifest.webmanifest", + "/icon-192.png", + "/icon-512.png", + "/icon-180.png", +]; +const SHELL_PATHS = new Set(SHELL); + +self.addEventListener("install", (e) => { + self.skipWaiting(); + e.waitUntil(caches.open(CACHE).then((c) => c.addAll(SHELL)).catch(() => {})); +}); + +self.addEventListener("activate", (e) => { + e.waitUntil( + caches.keys() + .then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))) + .then(() => self.clients.claim()) + ); +}); + +self.addEventListener("fetch", (e) => { + const req = e.request; + if (req.method !== "GET") return; + const url = new URL(req.url); + if (url.origin !== self.location.origin) return; + // Treat any navigation to /mobile.html (with or without ?standalone=1 etc.) as the shell. + const path = url.pathname; + if (!SHELL_PATHS.has(path)) return; // not ours — let the browser handle it + + e.respondWith( + fetch(req) + .then((res) => { + if (res && res.ok) { const copy = res.clone(); caches.open(CACHE).then((c) => c.put(path, copy)); } + return res; + }) + .catch(() => caches.match(path).then((hit) => hit || caches.match("/mobile.html"))) + ); +}); diff --git a/mobile.html b/mobile.html new file mode 100644 index 0000000..49f070b --- /dev/null +++ b/mobile.html @@ -0,0 +1,448 @@ + + + + + + +VARASYS PolyMeter — Mobile + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
–/–
+
+
+
+
+ +
+
+
+
120
+ +
BPM
+
+
+
+ +
+
+ + + + + +
+ +
+
+ + +
+
+
+

Load a groove

+ + + + +
+ + +
+ +
🔈🔊
+
+  PolyMeter + +
+
+ + + +