Warning
This project is fully AI generated so expect some technical slop 👀
A SvelteKit 3 / Svelte 5 project for studying Balisong tricks.
Use Node.js 22.17+ and pnpm 12.4.1 (pinned in package.json). Node 22 is used in CI. Run corepack enable once if pnpm is not on your PATH.
pnpm install --frozen-lockfile
pnpm devpnpm check # Strict TypeScript and Svelte diagnostics
pnpm test # Model and IndexedDB persistence tests
pnpm build # Prerender into build/
pnpm preview # Preview the production buildBrowser tests require Chromium once:
pnpm exec playwright install chromium
pnpm build
pnpm test:e2eTo verify a GitHub Pages project subpath locally, use the same base for both commands:
BASE_PATH=/bali-driller pnpm build
BASE_PATH=/bali-driller pnpm test:e2e- Create a GitHub repository and push this folder on
main. - In Settings → Pages → Build and deployment, choose GitHub Actions.
- The included
.github/workflows/deploy.ymlchecks, tests, builds and deploys on pushes tomainor manual dispatch.
Both jobs use ubuntu-24.04. The workflow uses Node 24 action runtimes (including the nested artifact uploader); the application Node version comes from .nvmrc. pnpm is installed before setup-node restores its lockfile-keyed cache.
The workflow gets BASE_PATH from GitHub's Pages configuration, supporting project repositories, user/organization sites and custom domains. For a manual build, set BASE_PATH=/your-repository for a project site, or omit it for a domain root. Publish the contents of build/. static/.nojekyll is included.
SSR stays enabled and all routes are prerendered, with trailing slashes and no SPA fallback. Both the interface and its initial loading state are present in the generated HTML. Browser storage opens only after mounting. See SvelteKit's static deployment guide.
- IndexedDB stores imported video bytes (and reads existing Blob records), titles, creators, tags, thumbnails, playback speed, mirroring, nominal frame rate and saved A/B sections. Changes save automatically; the footer reports pending, successful or failed saves. A failed metadata save can be retried.
- localStorage remembers the last opened clip. If unavailable, the library still works through IndexedDB.
- The bundled demo is a separate
static/media/demo.mp4asset, updated from the original Loop Study v0.8.1 source. It is seeded once per library; deleting it or emptying the library does not recreate it. - Multi-file imports use one transaction. If a batch fails, none of that batch is saved. Object URLs are recreated on load and revoked on deletion or teardown.
- The app requests persistent browser storage after an import. Browsers may decline. See MDN's persistence documentation.
Data belongs to the current browser profile and origin, not to a login. It does not sync between devices, browsers, localhost and GitHub Pages, or different domains. Clearing site data removes the library; browsers can evict nonpersistent storage. Keep the original recordings. There is no cloud backup or export feature. The database retains the original loop-study-v1 name and upgrades existing same-origin v1 data; data from a downloaded file:// HTML page is not automatically transferred to a hosted origin.
A service worker is not needed to save data, so none is registered. The app requires a network connection to load initially; offline app-shell caching is outside this migration's scope.
Import MP4/MOV/M4V/WebM files through the file picker or drag and drop; search by title, creator or tag; edit properties; play/pause/restart; scrub; choose 10–100% speed; set and save A/B loops; step by nominal frame duration; mirror, mute, zoom, pan and enter fullscreen. Keyboard shortcuts and frame-rate settings are in Help. The layout also stacks on phone-sized screens.
Codec support depends on the browser. H.264 MP4 is a practical compatibility choice. Frame stepping uses the chosen nominal rate, and looping uses browser media timing rather than frame-exact editing. The original optional Electron/desktop bridge and its conversion button were removed because a static website has no desktop conversion service. Imported video content is rendered as text/media, never inserted as HTML. No videos are uploaded.
src/routes/ SSG route and global layout
src/lib/components/ App shell, library, video player, playback, dialogs, icons
src/lib/study/model.ts Time formatting, validation and metadata normalization
src/lib/study/storage.ts IndexedDB transactions, upgrade and blob URL lifecycle
src/lib/study/player.svelte.ts Per-instance reactive playback and library controller
src/app.css Graphite styling and responsive practice/editor layouts
static/media/demo.mp4 Bundled v0.8.1 demo recording (~8 MB)
tests/ Model/storage tests and Playwright browser scenarios
.github/workflows/ GitHub Pages build and deployment
The controller owns playback state; components render it with Svelte bindings and event handlers. There are no global model objects, inline script loaders, imperative DOM-rendered lists or desktop bridge dependencies.
On desktop, the loop editor occupies a right sidebar so the video keeps its height. On phones, the app starts in practice mode; choose Edit loop to open the editor. Drag A or B directly on the timeline. Handles cannot cross and have separate hit areas for short loops. Zoom + magnifies the timeline around the last adjusted endpoint; Whole clip restores both endpoints. Arrow keys move a focused handle by 0.05 seconds, or 0.01 seconds with Shift. Practise loop plays the selected range without requiring a saved section.
If media loading fails, Retry saved video rereads its local data into a fresh media URL while retaining the loop, position and selected section. Returning to a hidden tab also reopens its saved media and leaves playback paused. Unexpectedly closed IndexedDB connections reopen on the next operation. Existing Blob-backed records remain readable; new imports store independent video bytes. The app icons and manifest support adding the page to a home screen, but do not provide offline caching.