Sync: the visual playhead now advances on a latency-compensated clock (currentTime − outputLatency||baseLatency) so the on-screen pulse lands when the click is HEARD, not when it's queued — previously the visual could lead the audio by the output buffer / Bluetooth latency (up to ~a subdivision). Applied to editor, player, teacher, and the new pages; also bound the visual queue (vq trim). No data races: single-threaded; only the rAF draw touches vqPtr/currentStep, and each vq entry carries the exact scheduled time of its sound. stage.html — foot-pedal stompbox: two heavy footswitches (Tap=tempo / hold=start- stop, Next=item / hold=prev), 1/4" expression-pedal input → tempo sweep, big floor-readable RGB beat light + angled TFT, analog instrument pass-through. showcase.html — pyramid display piece: an RGB-light pendulum easing to each beat plus per-lane segment rows showing subdivisions/accents/mutes (canvas). Both: dual USB-C (data+power and power-thru) to daisy-chain off one source. Wired into embed.js (stage, showcase variants), build.sh, deploy.sh, the concepts gallery + landing cards, info-stage.html (~$52) + info-showcase.html (~$39) with BOMs, and the README. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
40 lines
2 KiB
Bash
Executable file
40 lines
2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Assemble the deployed single-file pages from source + shared partials + assets/.
|
|
#
|
|
# Every page (the landing index.html, the editor.html app, the device mockups and
|
|
# the info-*.html pages) is a source that shares code via markers:
|
|
# /*@BUILD:include:src/<file>@*/ inlines a shared partial (engine, seed lists, base CSS)
|
|
# @BUILD:favicon@ / @BUILD:logo-*@ / /*@BUILD:samples@*/{} inline base64 assets
|
|
# This resolves them so each 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, re
|
|
A = pathlib.Path("assets")
|
|
SAMPLES = json.dumps(json.load(open(A / "samples.json")))
|
|
|
|
def build(name):
|
|
src = pathlib.Path(name).read_text()
|
|
# 1) inline shared partials (function-replacement: no backslash/group interpretation)
|
|
src = re.sub(r"/\*@BUILD:include:([^@]+)@\*/",
|
|
lambda m: pathlib.Path(m.group(1)).read_text().rstrip("\n"), src)
|
|
# 2) inline base64 assets
|
|
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@*/{}", SAMPLES)
|
|
assert "@BUILD:" not in src, f"unresolved build marker(s) remain in {name}"
|
|
out = pathlib.Path("dist") / name
|
|
out.write_text(src)
|
|
return out.stat().st_size
|
|
|
|
for name in ("index.html","editor.html","player.html","teacher.html","stage.html","micro.html","showcase.html",
|
|
"concepts.html","embed.html",
|
|
"info-editor.html","info-initial.html","info-teacher.html","info-stage.html","info-micro.html","info-showcase.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")
|
|
PY
|