Add build step (inline assets at build); drop "mockup" from the main app

The source index.html now keeps small @BUILD:* markers instead of the
~250KB of base64 blobs (audio samples, logos, favicon), which move to
assets/. build.sh inlines them into a self-contained dist/index.html
(+ dist/player.html); deploy.sh runs the build first and serves dist/.
dist/ is git-ignored. Keeps the single-file deploy while stopping the
samples from eating the editing budget.

Also reframe the main page as the full web app (it is not a mockup —
only the play-only player.html device is): drop "Mockup" from the title,
the source comment, and the README intro; add Build/Files docs and
correct the "no build step" claim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Me Here 2026-05-25 15:21:28 -05:00
parent 91cf48c551
commit 632890c812
9 changed files with 70 additions and 31 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
# Build output — assembled from index.html + assets/ by build.sh
dist/

View file

@ -1,15 +1,18 @@
# Stackable Metronome
A browser **polymetric groove trainer / metronome** — and the design mockup for a
Raspberry Pi Pico hardware build. Stack as many "meter lanes" as you like; each is
its own little metronome with a grouping, subdivision, drum voice and a per-step
pattern with accents. Layering lanes produces polymeter and true ratio polyrhythm.
A browser **polymetric groove trainer / metronome** — a full web app, and the
reference design for a Raspberry Pi Pico hardware build (whose playonly device is
mocked up at [`player.html`](player.html)). Stack as many "meter lanes" as you like;
each is its own little metronome with a grouping, subdivision, drum voice and a
perstep pattern with accents. Layering lanes produces polymeter and true ratio polyrhythm.
**Live:** https://metronome.varasys.io · **Source:** https://codeberg.org/VARASYS/metronome
It's a single, selfcontained `index.html`**zero dependencies**: no framework,
no build step, no bundled or CDN libraries, and nothing fetched at runtime. State
(set lists, the practice log, theme and UI preferences) lives in `localStorage`.
The **deployed page is a single, selfcontained `index.html`****zero dependencies**:
no framework, no CDN libraries, nothing fetched at runtime. It's assembled by a small
build step (`build.sh`) that inlines the audio samples + brand assets (kept in `assets/`)
into the HTML, so the source stays lean. State (set lists, the practice log, theme and
UI preferences) lives in `localStorage`.
Because nothing loads from the network, you can save the page (`Ctrl`/`⌘`+`S`) and
open it straight from disk to run fully offline. One catch from a local `file://`:
@ -153,9 +156,17 @@ disturbing what's playing, then commit on a musical boundary — no audible gap.
- All transitions — manual or auto, beat or bar — keep the clock continuous; the loaded
item can even live in a set list you're not currently viewing (the player names it).
## Build
The source `index.html` keeps small `@BUILD:*` markers in place of the large
base64 blobs (audio samples, brand logos, favicon) — those live in `assets/`.
`./build.sh` inlines them into a selfcontained `dist/index.html` (and copies
`player.html`). `dist/` is generated, gitignored — don't edit it by hand.
`deploy.sh` runs the build first, so a deploy always serves a freshly assembled page.
## Versioning
`VERSION` holds the formal version. `deploy.sh` stamps the served page:
`VERSION` holds the formal version. `deploy.sh` builds, then stamps the served page:
- **Formal** — a clean commit tagged `v<VERSION>``X.Y.Z`.
- **Dev** — anything else → `X.Y.Z-dev.<utc-timestamp>.<short-sha>[.dirty]`.
@ -168,8 +179,11 @@ Push the tag, then deploy.
| File | Purpose |
|------|---------|
| `index.html` | the whole app |
| `deploy.sh` | publish to the Caddy web root |
| `index.html` | the whole app (source, with `@BUILD:*` asset markers) |
| `player.html` | the playonly hardwaredevice mockup (`/player.html`) |
| `assets/` | base64 blobs inlined at build (samples, logos, favicon) |
| `build.sh` | inline `assets/` into `dist/` (selfcontained pages) |
| `deploy.sh` | build, then publish to the Caddy web root |
| `release.sh` | tag a formal version |
| `VERSION` | formal version string |
| `LICENSE` | GNU AGPL v3 license text |

1
assets/favicon.b64 Normal file
View file

@ -0,0 +1 @@
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHJlY3Qgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIiByeD0iNyIgZmlsbD0iIzFDMjgzRiIvPjxwYXRoIGQ9Ik0xMiA2aDhsNCAyMUg4eiIgZmlsbD0iIzBBQjNGNyIvPjxwYXRoIGQ9Ik0xNiAyNEwxOS42IDkiIHN0cm9rZT0iIzFDMjgzRiIgc3Ryb2tlLXdpZHRoPSIxLjgiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgZmlsbD0ibm9uZSIvPjxyZWN0IHg9IjE2LjQiIHk9IjEzLjIiIHdpZHRoPSI0LjQiIGhlaWdodD0iMi44IiByeD0iMC42IiB0cmFuc2Zvcm09InJvdGF0ZSgtMTMgMTguNiAxNC42KSIgZmlsbD0iIzFDMjgzRiIvPjwvc3ZnPgo=

1
assets/logo-dark.b64 Normal file

File diff suppressed because one or more lines are too long

1
assets/logo-light.b64 Normal file

File diff suppressed because one or more lines are too long

1
assets/samples.json Normal file

File diff suppressed because one or more lines are too long

24
build.sh Executable file
View file

@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Assemble the deployed single-file pages from source + assets/.
#
# The source index.html keeps small @BUILD:* markers instead of the large base64
# blobs (audio samples, brand logos, favicon). This inlines those assets so the
# built page in dist/ is one self-contained file (zero deps, works fully offline).
# deploy.sh runs this first. dist/ is generated — don't edit or commit it.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
mkdir -p dist
python3 - <<'PY'
import json, os, pathlib
A = pathlib.Path("assets")
src = pathlib.Path("index.html").read_text()
src = src.replace("@BUILD:favicon@", (A / "favicon.b64").read_text().strip())
src = src.replace("@BUILD:logo-dark@", (A / "logo-dark.b64").read_text().strip())
src = src.replace("@BUILD:logo-light@", (A / "logo-light.b64").read_text().strip())
src = src.replace("/*@BUILD:samples@*/{}", json.dumps(json.load(open(A / "samples.json"))))
assert "@BUILD:" not in src, "unresolved build marker(s) remain in index.html"
pathlib.Path("dist/index.html").write_text(src)
pathlib.Path("dist/player.html").write_text(pathlib.Path("player.html").read_text()) # no assets to inline
print("built dist/index.html (%dKB) + dist/player.html (%dKB)" %
(os.path.getsize("dist/index.html") // 1024, os.path.getsize("dist/player.html") // 1024))
PY

View file

@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Deploy the metronome mockup to the Caddy web root that serves
# Deploy the metronome to the Caddy web root that serves
# https://metronome.varasys.io
#
# Caddy config: /var/lib/caddy/Caddyfile (metronome.varasys.io:8443 block)
@ -12,10 +12,15 @@ set -euo pipefail
SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEST_DIR="/var/lib/caddy/www/metronome"
DIST_DIR="$SRC_DIR/dist"
[[ -f "$SRC_DIR/index.html" ]] || { echo "error: $SRC_DIR/index.html not found" >&2; exit 1; }
[[ -d "$DEST_DIR" ]] || { echo "error: web root $DEST_DIR is missing — is Caddy set up?" >&2; exit 1; }
# Assemble the self-contained pages (inlines assets/ into dist/). dist/ is git-ignored.
"$SRC_DIR/build.sh"
[[ -f "$DIST_DIR/index.html" ]] || { echo "error: build did not produce $DIST_DIR/index.html" >&2; exit 1; }
# --- compute build version ---------------------------------------------------
# Formal build: clean tree on a commit tagged v<VERSION> -> "X.Y.Z"
# Dev build: anything else -> "X.Y.Z-dev.<utc-ts>.<sha>[.dirty]"
@ -33,10 +38,10 @@ else
BUILD="$VER-dev.$(date -u +%Y%m%dT%H%M%SZ)" # not a git checkout
fi
# stamp the version into the deployed copy only (source stays clean)
sed "s|const APP_VERSION = \"[^\"]*\";|const APP_VERSION = \"$BUILD\";|" "$SRC_DIR/index.html" > "$DEST_DIR/index.html"
# stamp the version into the built copy only (source stays clean)
sed "s|const APP_VERSION = \"[^\"]*\";|const APP_VERSION = \"$BUILD\";|" "$DIST_DIR/index.html" > "$DEST_DIR/index.html"
echo "deployed v$BUILD ($(stat -c '%s' "$DEST_DIR/index.html") bytes) -> $DEST_DIR"
sed "s|const APP_VERSION = \"[^\"]*\";|const APP_VERSION = \"$BUILD\";|" "$SRC_DIR/player.html" > "$DEST_DIR/player.html"
sed "s|const APP_VERSION = \"[^\"]*\";|const APP_VERSION = \"$BUILD\";|" "$DIST_DIR/player.html" > "$DEST_DIR/player.html"
echo "deployed player.html ($(stat -c '%s' "$DEST_DIR/player.html") bytes)"
# If real audio samples are added later (see the plan's GM-sample note),

File diff suppressed because one or more lines are too long