# MP3 Player — Functional Issues & Live Debug Reference

All line numbers below were read directly from your original files and verified.
Cosmetic issues (alt text, underlines, duplicate CSS, unused rules, image sizes)
are deliberately excluded — none of them affect behaviour.

---

## PART 1 — Things that are actually broken right now

### 1. Backslashes in an image path
**File:** `main.html` **Line:** 40

```html
<img src="Images\Mission Control\ftl.png" alt="Playlist 1 Cover">
```

Backslashes are a Windows folder separator, not a URL separator. This loads from
a local Windows drive and 404s everywhere else — a web server, GitHub Pages, a
Mac, or a Linux box.

**Fix:**
```html
<img src="Images/Mission Control/ftl.png" alt="Playlist 1 Cover">
```

**Symptom if broken again:** card 3 shows a broken-image icon, console shows
`404 (Not Found)` for the ftl.png request.

---

### 2. `overflow: hidden` on body makes playlist content unreachable
**File:** `main.css` **Lines:** 31–32

```css
body {
  background-color: var(--bg-color);
  color: var(--text-main);
  height: 100vh;        /* line 31 */
  overflow: hidden;     /* line 32 */
}
```

`main.css` is linked by the playlist pages too. From roughly 480px to 900px wide
the playlist layout stacks vertically and grows taller than the viewport, and
`overflow: hidden` means the bottom of the tracklist simply cannot be reached.
There is no scrollbar and the wheel does nothing.

The mobile media query at line 215 sets `overflow: auto` again, but only below
768px — so the 768–900px band stays broken.

**Fix:**
```css
body {
  background-color: var(--bg-color);
  color: var(--text-main);
  min-height: 100vh;
}
```

**Symptom if broken again:** on a narrow window the page refuses to scroll and
the last few tracks are cut off below the fold.

---

### 3. Shuffle can replay the track that just ended
**File:** `Playlists/Playlist1.html` **Line:** 165
**File:** `Playlists/Playlist2.html` **Line:** 164
**File:** `Playlists/Playlist3.html` **Line:** 175

```javascript
function nextTrack() {
  if (isShuffle) {
    index = Math.floor(Math.random() * tracks.length);
  } else {
    index = (index + 1) % tracks.length;
  }
```

`Math.random()` has no memory of the current index, so on a 5-track list there
is a 1-in-5 chance every skip lands on the song already playing. It restarts
instead of advancing.

**Fix:**
```javascript
function nextTrack() {
  if (isShuffle && tracks.length > 1) {
    let next;
    do {
      next = Math.floor(Math.random() * tracks.length);
    } while (next === index);
    index = next;
  } else {
    index = (index + 1) % tracks.length;
  }
```

The `tracks.length > 1` guard matters: with a single-track playlist the loop
would never find a different index and would hang the tab.

---

### 4. Missing viewport tag disables every media query on mobile
**Files:** `Playlists/Playlist1.html`, `Playlist2.html`, `Playlist3.html`
**Line:** missing — belongs immediately after line 4 (`<meta charset="UTF-8">`)

Without it a phone renders at a pretended 980px width, so your 900px and 480px
breakpoints never fire. `main.html` and `start.html` both have the tag; the three
playlist pages do not.

**Fix:** add to each `<head>`:
```html
<meta name="viewport" content="width=device-width, initial-scale=1.0">
```

**Symptom if broken again:** on a phone the playlist page renders as a
zoomed-out desktop layout with tiny text, instead of stacking.

---

### 5. `.grid-container` has no `grid-area` and works by luck
**File:** `main.css` **Lines:** 91 and 98

```css
.playlist-view {        /* line 91 — no element in any HTML file uses this class */
  grid-area: playlist;
  padding: 24px;
  overflow-y: auto;
}

.grid-container {       /* line 98 — this IS the class main.html uses */
  display: grid;
  ...                   /* no grid-area, no overflow-y */
}
```

The `grid-area: playlist` assignment ended up on an orphaned class. Your
`<main class="grid-container">` currently lands in the right cell only because
CSS grid auto-placement happens to drop it in the first free slot. Add one more
child to `.app-container`, or reorder the header/sidebar, and the cards jump
into the wrong cell. There's also no `overflow-y`, so on a short window the cards
are clipped with no scrollbar.

**Fix:** add two lines to `.grid-container` (line 98):
```css
.grid-container {
  grid-area: playlist;
  overflow-y: auto;
  display: grid;
  ...
}
```

---

## PART 2 — Live-debug reference: symptom → cause

Your player script is one top-level block. **An error anywhere stops every line
after it**, so the failure point tells you exactly where to look. Lines below are
from `Playlist1.html`; Playlist2 is 1 lower, Playlist3 is 10 higher.

### The element-ID map

Every `getElementById` string must match an `id` attribute exactly — same
spelling, same case, same hyphens. Break one and the script dies at its first use:

| id in HTML | HTML line | First used at | What dies from that point on |
|---|---|---|---|
| `audio` | 42 | 87 (`audio.onplay`) | Everything. Tracklist never renders. |
| `theme-btn` | 13 | 102 | Volume, shuffle, buttons, tracklist |
| `vol-slider` | 31 | 110 | Shuffle, buttons, tracklist |
| `shuffle-btn` | 26 | 121 | Buttons, tracklist |
| `play-btn` | 24 | 176 | Next/Prev/Shuffle wiring, initial load |
| `title` | 20 | 150 | Everything inside `loadTrack` |
| `cover` | 19 | 151 | Cover swap + initial load |
| `track-list` | 38 | 137 | Tracklist rendering |

**Console message to look for:** `Uncaught TypeError: Cannot set properties of
null (setting 'onplay')`. The property named in that message tells you which
line, and therefore which id, is wrong.

### Other high-value break points

**Script moved into `<head>`.** The `<script>` block sits at line 44, after the
markup. Move it to the `<head>` (or into an external file loaded without
`defer`) and every `getElementById` returns `null` because the elements don't
exist yet — total failure with the same TypeError. This is the single most
common way this kind of page gets broken.

**Syntax error in the `tracks` array** (a missing comma, quote, or brace).
The whole script never executes at all. Page renders fine, nothing is clickable,
tracklist is empty, console shows `Uncaught SyntaxError` with a line number.
Distinguish it from the null case: SyntaxError means nothing ran, TypeError
means it ran up to that line.

**A wrong path inside `tracks`.** This one is silent — no crash, no console
error by default. The play button flips to Pause and immediately back, or just
does nothing. Your code has no `error` handler on the audio element, so nothing
reports it. Watch the Network tab for a 404, or add this diagnostic line near
your other audio handlers (line ~97):

```javascript
audio.onerror = () => console.error("Audio failed to load:", audio.src);
```

Worth adding permanently — it turns the hardest failure to diagnose into an
obvious one.

**Case and folder depth in paths.** `Images/` vs `images/`, `.PNG` vs `.png` —
identical on Windows, a 404 on any real server. The playlist pages live in
`Playlists/`, so every asset reference needs the `../` prefix. Dropping it, or
moving a file up or down a folder, breaks the reference silently.

**Volume out of range.** Line 110 does `audio.volume = savedVol` straight from
localStorage with no validation. `volume` is one of the few properties that
throws on a bad value: anything above 1 raises an `IndexSizeError` and `NaN`
raises a `TypeError`, either of which kills the rest of the script. Your slider
is capped at `max="1"` so it won't happen through the UI, but a stale or
hand-edited localStorage value will do it. Hardening:

```javascript
let savedVol = Number(localStorage.getItem("msr_volume") ?? 0.8);
audio.volume = Math.min(1, Math.max(0, savedVol || 0.8));
```

**localStorage key drift.** Three keys are shared across all four pages:
`msr_theme`, `msr_volume`, `msr_shuffle`. Change one on one page and that page
silently stops sharing the setting — no error, the preference just doesn't
persist when you navigate.

**Theme button id differs between pages.** `main.html` line 15 uses
`id="theme-toggle"`; all three playlists use `id="theme-btn"`. Each page's JS
matches its own HTML so it works today, but copy a script block from one page to
the other and you get an instant null crash.

**`onclick` assignment overwrites.** Lines 176–179 use `playBtn.onclick = ...`.
Assigning a second handler to the same property silently replaces the first,
with no warning. `addEventListener` would stack instead. Relevant if a second
handler gets added anywhere.

---

## PART 3 — Minor logic notes

**`prevTrack` ignores shuffle** (line ~172). Next is random in shuffle mode, Prev
always steps back by one. Defensible as a design choice — most real players
behave this way — but worth knowing it's asymmetric rather than a bug you missed.

**`renderTracklist` rebuilds the whole list on every track change** (line 136).
It wipes the `<ul>` with `innerHTML = ""` and recreates every `<li>` just to move
the `active` highlight. Functionally correct, just does more work than needed.

---

## Quick checklist

| # | File | Line | Issue |
|---|---|---|---|
| 1 | main.html | 40 | Backslashes in image path |
| 2 | main.css | 31–32 | `overflow: hidden` clips playlist pages |
| 3 | Playlist1/2/3.html | 165 / 164 / 175 | Shuffle can repeat current track |
| 4 | Playlist1/2/3.html | after 4 | Missing viewport meta tag |
| 5 | main.css | 91, 98 | `grid-area` on an unused class |
