Welcome to Mankunku

Jazz ear training — call and response. Pick your instrument to get started.

Tech Stack

Framework & Build

TechnologyVersionRole
SvelteKit^2.50App framework (routing, SSR, adapters)
Svelte 5^5.51UI framework (runes mode for reactivity)
Vite^7.3Build tool and dev server
TypeScript^5.9Type safety (strict mode)
Tailwind CSS^4.2Utility-first styling via @tailwindcss/vite

Audio Libraries

LibraryVersionRole
Tone.js^15.1Transport scheduling, audio graph, synths (metronome)
smplr^0.19SoundFont instrument playback (GM samples)
Pitchy^4.1McLeod Pitch Method — real-time pitch detection

Music Notation

LibraryVersionRole
abcjs^6.6Renders ABC notation to SVG in the browser

Testing

ToolVersionRole
Vitest^4.1Unit testing (node environment)
Playwright^1.58End-to-end browser testing
@testing-library/svelte^5.3Component testing utilities

PWA

ToolVersionRole
@vite-pwa/sveltekit^1.1PWA integration with auto-update service worker

The PWA is configured in vite.config.ts with:

  • registerType: 'autoUpdate' — service worker updates automatically
  • Workbox pre-caches JS, CSS, HTML, SVG, and WOFF2 files
  • SoundFont (.sf2) files use CacheFirst runtime caching (30-day expiry)
  • Standalone display mode with dark theme color (#0f172a)

Styling Approach

Mankunku uses Tailwind CSS v4 with CSS custom properties for theming:

/* src/app.css */
:root {
  --color-bg: #0f172a;
  --color-bg-secondary: #1e293b;
  --color-bg-tertiary: #334155;
  /* Ear Training (default) — Blue Note peacock teal */
  --color-accent: #2e8b9e;
  --color-accent-hover: #1f6b7a;
  /* Blue Note brass — decorative chrome accent */
  --color-brass: #c8923d;
  --color-paper: #1a1410;
  /* Vintage recording-booth red for the active/stop state */
  --color-onair: #a8463a;
  /* ... */
}
[data-domain='lick-practice'] {
  /* Warm terracotta for lick-practice routes */
  --color-accent: #c96a3e;
  --color-accent-hover: #a64f27;
}
[data-domain='neutral'] {
  --color-accent: #94a3b8;
  --color-accent-hover: #cbd5e1;
}
:root.light { /* light-mode equivalents */ }

Components reference these variables inline: bg-[var(--color-bg-secondary)]. Theme switching toggles the .light class on . Route domain (ear-training / lick-practice / neutral) is derived in +layout.svelte and applied as data-domain on the layout root — flipping --color-accent re-colors every interactive surface. See documentation/architecture/design-system.md.

The display serif Fraunces (variable font, weight 300–800, Latin subset, self-hosted for offline PWA support) is used for the wordmark, page titles, key/grade readouts, and the primary "Ear Training / Lick Practice" nav labels via the .font-display utility.

Configuration Files

  • svelte.config.js — Enables runes mode for all non-node_modules files via dynamicCompileOptions. Uses adapter-node so the server can run authentication hooks and session middleware.
  • tsconfig.json — Extends SvelteKit's generated config. Strict mode enabled with bundler module resolution.
  • vite.config.ts — Registers Tailwind, SvelteKit, and PWA plugins. Test config points to tests/unit/**/*.test.ts with node environment.

Architecture Summary

Mankunku is a local-first PWA with optional cloud sync:

  • State persistence — All user progress, settings, and session history are stored in localStorage first. An optional Supabase backend (src/lib/supabase/, src/routes/api/account/) provides authenticated cloud sync so the same data follows a user across devices.
  • Audio pipeline — Built entirely on Web Audio APIs. An AudioWorklet handles onset detection, an AnalyserNode feeds the pitch detector, and Tone.js manages transport scheduling for metronome and phrase playback.
  • Music theory — Scales, intervals, transposition, key signatures, and scoring algorithms are implemented in pure TypeScript with no external music theory libraries. The 35-scale catalog and ~250 lick library are defined as typed data structures.
  • Deploymentadapter-node produces a Node.js server bundle (deployed via rsync + PM2 to a Digital Ocean VM). The PWA service worker still enables full offline functionality after initial load.

Why These Choices

  • Svelte 5 runes over stores: Fine-grained reactivity without boilerplate. $state and $derived replace writable/derived stores with simpler semantics.
  • Tone.js for transport: Provides sample-accurate scheduling via a centralized Transport, essential for synchronizing metronome clicks with phrase playback.
  • smplr over Tone.js sampler: Smaller bundle for GM SoundFont playback. Shares the same AudioContext.
  • Pitchy over Web Audio AnalyserNode alone: Implements the McLeod Pitch Method which is more accurate for monophonic instruments than simple FFT peak detection.
  • ABC notation over MusicXML: Text-based format is trivial to generate from MIDI data. abcjs renders it to SVG with no server required.
  • Local-first with optional Supabase: All writes hit localStorage first so the app works fully offline; an authenticated user's Supabase sync is background fire-and-forget, not a request path.