The whole app lived in a 1000-line <style> block in the document head, which
put it out of reach of tooling and made the entry point unreadable. It now
sits in src/app.css, imported from dispatch.ts right after theme.css.
The order matters and is now the loaded one: app.css builds on the kit's
tokens and primitives, so rules of equal specificity in it win, where the
inline block used to lose. test/hero.html imports the two stylesheets the
same way instead of scraping them out of index.html, and its own button
rule is scoped so it stops repainting the previewed head.
Co-authored-by: Cursor <cursoragent@cursor.com>
feat: robust streaming, now-playing titles and a dedicated tuning model
Three parts of the app that were tangled into embed.ts now stand on their
own, each testable from a bench page under test/.
- stream-engine.ts: one <audio> element kept alive, HLS via hls.js (light
build, dynamically imported) for .m3u8 and a hardened native path for
everything else. Reconnects only once the buffer has run dry, so a
network blip costs no audible gap.
- now-playing.ts: resolves the track on air from side-channel endpoints
rather than in-band ICY metadata, which no browser can read cross-origin.
- tuning.ts: the antenna's 36° of travel stretched over a 280° arc means
8x the encoder noise and 8x the mechanical sag. A one-euro filter,
station magnetism, capture hysteresis and a bounded UI/antenna offset
turn that into a needle that holds still and a station that does not
flicker. The needle is pinned whenever the antenna is at rest, so
nothing on the dial moves when nothing is moving.
Stations carry tags and country instead of a single genre string.
Co…