# Boxed3D Music — music.boxed3d.com

Static music player for the Boxed3D owner's personal album collection. Works on desktop and mobile, installable as a PWA, fully static (no backend), hosted on Cloudflare Pages.

## Stack
- **Pure HTML/CSS/JS** — no build step, no framework
- **HTML5 `<audio>`** with custom queue management
- **Service Worker** — cache-first for audio files & covers (offline playback), stale-while-revalidate for app shell
- **PWA** — `manifest.webmanifest` + installable on Android/iOS
- **Media Session API** — lock-screen / hardware key integration

## Project layout
```
202609017_111111_music_boxed3d_com/
├── index.html              Album grid (home)
├── album.html              Album detail page (?id=...)
├── album.js                Album detail page logic
├── sw.js                   Service worker
├── manifest.webmanifest    PWA manifest
├── wrangler.toml           Cloudflare Pages config
├── data/
│   └── albums.json         Album/track metadata
├── assets/
│   ├── audio/              MP3 / m4a / ogg / wav / flac / opus files
│   ├── covers/             Album cover art (jpg/png/webp/svg)
│   ├── css/                main.css, player.css
│   ├── icons/              PWA icons (192, 512)
│   └── js/                 player.js, app.js, pwa.js, media-session.js
└── scripts/
    └── add_album.py        CLI to bulk-add albums (reads folder of audio)

NOTE: API token lives one level UP at `D:\Du_an_tuyen_chon\music-boxed3d.env.local`
      (kept outside the deploy root so it never ships to Cloudflare).
```

## Adding a new album

### Method 1 — manual
Edit `data/albums.json` and append:
```json
{
  "id": "my-album",
  "title": "My Album",
  "artist": "Some Artist",
  "year": 2024,
  "cover": "assets/covers/my-album.jpg",
  "genre": ["Pop", "Indie"],
  "tracks": [
    { "number": 1, "title": "Track One", "file": "assets/audio/my-album/01.mp3", "duration": 210 },
    { "number": 2, "title": "Track Two", "file": "assets/audio/my-album/02.mp3", "duration": 195 }
  ]
}
```
Then drop the audio files into `assets/audio/my-album/` and the cover into `assets/covers/`.

### Method 2 — CLI (auto-detects MP3 duration via mutagen)
```bash
pip install mutagen    # optional but recommended
python scripts/add_album.py /path/to/mp3/folder --title "My Album" --artist "Some Artist" --year 2024 --cover /path/to/cover.jpg --genre "Pop,Indie"
```
The script:
1. Scans the folder for audio files (sorted)
2. Reads duration from each file (if mutagen installed)
3. Generates slug id from title
4. Moves the cover into `assets/covers/`
5. Appends/updates the album in `data/albums.json`

## Local development

```bash
# Serve the directory on port 8000
python -m http.server 8000

# Open http://localhost:8000
```

Note: opening `file://` directly will not work because of `fetch()` CORS for `albums.json`.

## Deployment to Cloudflare Pages

API token lives at `D:\Du_an_tuyen_chon\music-boxed3d.env.local` (one level **above** the project root — never inside, so it never gets deployed).

```powershell
# From project root
cd D:\Du_an_tuyen_chon\202609017_111111_music_boxed3d_com
$env:CLOUDFLARE_API_TOKEN = (Get-Content "..\music-boxed3d.env.local" | Select-String 'CLOUDFLARE_API_TOKEN' | ForEach-Object { ($_ -split '=', 2)[1].Trim() })
npx wrangler pages deploy . --project-name music-boxed3d --commit-dirty=true --branch=main
```

After first deploy:
1. Open Cloudflare Dashboard → Pages → `music-boxed3d`
2. Custom domains → Set up a custom domain → `music.boxed3d.com`
3. Cloudflare auto-creates the CNAME since `boxed3d.com` is on Cloudflare DNS.

> **Why `..\music-boxed3d.env.local` and not `.env.local` inside the project?**
> `wrangler pages deploy .` ships the **entire** current directory. Anything inside the deploy root (including a `.env.local`) becomes publicly reachable at `https://music-boxed3d.pages.dev/.env.local`. Keeping the token file one level up means we can `source`/read it locally but it never gets uploaded.

## Browser support
- Chrome / Edge / Firefox / Safari 14+
- iOS Safari 14+ (PWA install supported from iOS 16.4)
- Android Chrome (full PWA install with offline)

## File-size limits
- Cloudflare Pages free tier: **25 MB per file**. Audio files up to that size are fine; for bigger files use HLS / chunked delivery (not needed for personal album collection).
- Total project size: **unlimited** for static assets, but be mindful of bandwidth — Cloudflare Pages free tier = unlimited bandwidth on static assets.

## Known limitations
- No streaming / no progressive MP3 — whole files cached (works fine for album-length MP3s ~5-15 MB each)
- No search across album contents (only metadata: title, artist, year, genre)
- No playlist persistence across browser sessions (queue is `sessionStorage` only — clears when tab closes)
- No sharing / social features — this is a personal site
