Skip to content
Personal

Pulse Player

A client-side music player built around session continuity and control craft — not competing on library size.

Role
Design & development, solo
Year
2026
Stack
Vanilla JS, Vite
Status
Complete

Pulse Player isn’t “Spotify but smaller.” It doesn’t compete on library size or social features — it competes on session continuity and control quality that works identically well with a mouse, a touchscreen, or a keyboard. No accounts, no streaming infrastructure, no social layer, all deliberately out of scope from the first planning document.

What makes it work

  • Session continuity, honestly presented. Reload mid-song and it resumes exactly where you left off — visibly paused, since browsers can’t auto-play audio without a fresh user gesture. The same Play button resumes it; nothing pretends otherwise.
  • Real dialogs, not custom overlays. The Now Playing and Queue panels use the native <dialog> element — correct focus-on-open, Tab-containment, Escape-to-close, and focus-restore, all for free, verified with a test proving background elements are genuinely unfocusable.

Design at a glance

  • bg · #12141a
  • surface · #1b1e27
  • accent · #6ee7c8
  • text · #f2f3f5
  • Now Playing — pending capture

Built to last

  • Queue, favorites, and playback position survive a reload
  • Zero horizontal overflow across 8 real device widths, 320–1440px, even with dialogs open
  • OS-level media controls — lock screen, hardware keys — via a thin Media Session adapter

Want an interface this considered for your own product?

See it live Start a project
For developers — the full technical breakdown

01. Vanilla JS over a framework, deliberately

Problem
Needed one-way data flow and predictable state across nine modules without hiding the fundamentals the project exists to demonstrate.
Choice
A ~100-line custom observable store — no framework, no virtual DOM.
Result
The same guarantees a framework would give, with module boundaries designed to stay framework-portable if scope ever grew.

02. A bug found through real adversarial testing

Problem
The position-save timer reset on every timeupdate tick, so it silently never fired during playback.
Choice
Switched from debounce to throttle — coalesce into an already-pending save instead of resetting it.
Result
Verified with a live playback-then-reload test confirming the exact resume position survived.

Accessibility & performance

  • Native <dialog> for Now Playing/Queue — real focus trap, Escape-to-close, focus-restore, for free
  • A single aria-live region for track changes/errors, deliberately isolated from continuous progress updates
  • Zero horizontal overflow across 8 real widths, 320–1440px, verified with dialogs open
  • Media Session wired as a thin adapter, feature-detected with a truthiness check rather than just 'mediaSession' in navigator

What I’d do differently

No accounts, no streaming, no social features — deliberately out of scope to keep the actual build to a high standard rather than half-built across a dozen features. If this became a real product, syncing a library across devices would be the natural next step.

Stack

  • Vanilla JavaScript
  • Vite
  • Media Session API
  • Native <dialog>