Files
rehearshalhub/CLAUDE.md
Mistral Vibe 4af19ed93b Updates
2026-04-01 09:56:19 +02:00

1041 lines
91 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RehearsalHub — Project Context for Claude Code
RehearsalHub is a web platform that lets bands relisten to recorded rehearsals, comment at specific timestamps, annotate moments, and collaborate asynchronously. This file is the single source of truth for design decisions, component inventory, UX principles, and open questions. Read it at the start of every session.
---
## Product Summary
- Bands record rehearsals and upload the audio files (or connect cloud storage)
- Files are organised by rehearsal session (date-based, read from filesystem or timestamp)
- Band members can listen, drop timestamped comments with avatar pins directly on the waveform, tag moments (suggestion / issue / keeper), and reply to each other
- Multiple bands per user — band switching works like a tenant switch (scoped sidebar)
- Storage is always the user's own (Google Drive, OneDrive, Nextcloud, S3, local) — files are never copied to RehearsalHub servers
---
## Design System
### Palette
Dark/grey interface throughout. No light mode in v1.
```
Background base: #0f0f12 (app shell, player)
Background deep: #0b0b0e (sidebar, comment strip)
Background raised: rgba(255,255,255,0.025) (cards, list items)
Border subtle: rgba(255,255,255,0.05)
Border default: rgba(255,255,255,0.08)
Border hover: rgba(255,255,255,0.12)
Text primary: #eeeef2
Text secondary: rgba(255,255,255,0.68)
Text muted: rgba(255,255,255,0.35)
Text hint: rgba(255,255,255,0.22)
Accent (amber): #e8a22a — primary action, playhead, active states
Accent hover: #f0b740
Member colours (avatar, pin, tag):
Alex → #7aabf0 bg rgba(91,156,240,0.18)
Maya → #d070c0 bg rgba(200,90,180,0.18)
Jordan → #4dba85 bg rgba(61,200,120,0.18)
Sam → #e8a22a bg rgba(232,162,42,0.18)
Kim → #a878e8 bg rgba(140,90,220,0.18)
Tag colours:
suggestion → #7aabf0 bg rgba(91,156,240,0.1)
issue → #e07070 bg rgba(220,80,80,0.1)
keeper → #4dba85 bg rgba(61,200,120,0.1)
Instrument tags:
Full band → #a878e8 bg rgba(140,90,220,0.1)
Guitar → #4dba85 bg rgba(61,200,120,0.1)
Drums → #7aabf0 bg rgba(91,156,240,0.1)
Vocals → #d070c0 bg rgba(200,90,180,0.1)
Keys → #e8a22a bg rgba(232,162,42,0.1)
Status:
Connected / success → #4dba85
Error / danger → #e07070
```
### Typography
```
Font stack: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif
Mono stack: 'SF Mono', 'Fira Code', monospace (filenames, timestamps, codes)
Sizes:
Page title 17px / weight 500
Section title 11px / weight 500 / uppercase / letter-spacing 0.7px / muted
Body 13px / weight 400
Secondary 12px
Hint / meta 11px
Micro 10px
```
### Spacing & Radius
```
App chrome padding: 2026px
Card padding: 1014px
Component gap: 812px
Border radius lg: 1214px (app shell, modals)
Border radius md: 810px (cards, inputs)
Border radius sm: 67px (buttons, tags)
Border radius pill: 20px (filter chips, timestamp pills)
```
### Buttons
```
Default: transparent bg, 1px rgba(255,255,255,0.09) border, muted text
hover → bg rgba(255,255,255,0.06)
Primary: rgba(232,162,42,0.14) bg, rgba(232,162,42,0.28) border, #e8a22a text
hover → rgba(232,162,42,0.22)
Danger: rgba(220,80,80,0.08) bg, rgba(220,80,80,0.2) border, #e07070 text
Icon btn: 3234px circle, same default/hover pattern
Play btn: 46px circle, solid #e8a22a bg, white icon
```
---
## Data Model
```
User
id, email, displayName, avatarUrl, instrument, bio
→ member of many Bands (via BandMember)
Band
id, name, initials, color, genre, description
settings: { membersCanInvite, publicPage, recordingsPrivate }
→ has many BandMembers
→ has many StorageLocations
→ has many Sessions
BandMember
userId, bandId, role (admin | member | listener)
joinedAt
StorageLocation
id, bandId, provider (gdrive | onedrive | dropbox | nextcloud | s3 | local)
config: { rootPath, credentials… }
defaultForUpload: boolean
Session
id, bandId, date (YYYY-MM-DD), title, notes
→ has many Recordings
Recording
id, sessionId, filename, durationSeconds
storageLocationId, storagePath
instrument (fullband | guitar | bass | drums | vocals | keys | other)
uploadedBy (userId), uploadedAt
Comment
id, recordingId, userId
timestampSeconds (float — pinned to waveform position)
text, tag (suggestion | issue | keeper | '')
parentId (for replies)
createdAt
```
---
## Application Shell
```
┌─────────────────────────────────────────────────────┐
│ Sidebar (210px) │ Main content area (flex 1) │
│ ─────────────────│──────────────────────────────────│
│ [Band switcher] │ Active view based on nav │
│ Nav items │ │
│ ─────────────────│ │
│ [User avatar] │ │
└─────────────────────────────────────────────────────┘
```
### Sidebar structure
1. **Band switcher** (top) — shows active band initials + name + chevron. Click opens a dropdown listing all the user's bands with their role. Bottom item is "Create new band". Switching band re-scopes the band-specific nav section label and all band settings views.
2. **Nav sections**
- *Account*: Profile, Notifications, Audio & Playback
- *Band — [Name]*: Members, Storage, Band Settings
3. **User row** (bottom) — avatar + display name + gear icon → opens Profile
---
## Views
### 1. Library
The main landing view. Shows all recordings grouped by session date. **Filter pills narrow within the date structure — they do NOT replace it.**
Key behaviours:
- Date groups always visible as headers with recording count
- Filter pills: All / instrument tags / Commented
- Search input searches filenames AND comment text
- Sort toggle: Date (default) / Duration / Most commented
- Each recording row shows: play button, filename (mono font), duration, instrument tag, comment count, mini waveform bars
- Currently playing row highlighted with amber border + "Playing" badge
- Click anywhere on a row → opens Player view for that recording
- "+ Upload" button → file picker or drag-and-drop
### 2. Player
The core experience. Split layout: waveform + queue on left, comment panel on right.
**Waveform area:**
- Canvas-rendered waveform, 104px tall
- Played portion renders in amber (#c8861a slightly darker), unplayed in rgba(255,255,255,0.09)
- Avatar pins float above the waveform at exact timestamp positions
- Each pin is the member's initials in a circle, coloured by member
- 1.5px stem connects pin to waveform
- Hover pin → tooltip with member name, timestamp, comment preview
- Click pin → jumps playhead to that timestamp AND highlights + scrolls to comment in the panel
- Playhead: 2px amber vertical line + 10px amber dot on top
- Scrubber bar below waveform (3px, click to seek)
- Time display: current time (amber) / midpoint / total
**Transport bar:**
- Speed selector (0.5× 0.75× 1× 1.25× 1.5× 2×) — left
- Skip 30s / Skip +30s
- Play/pause (46px amber circle) — centre
- Volume slider — right
**Comment compose (bottom of panel):**
- User avatar + textarea
- Timestamp pill (amber, live-updating, blinks dot while recording plays)
- Textarea expands on focus from ~42px to ~72px
- On focus: tag buttons appear (suggestion / issue / keeper) + Post button
- Post disabled until text entered
- On post: comment added to list, avatar pin added to waveform immediately
**Comment list:**
- Scrollable, max-height ~248px
- Each item: member avatar, member name, amber timestamp chip (click → seek), tag badge, comment text, Reply link
- Highlighted (amber tint) when playhead is within ~5s of that comment's timestamp
**Queue panel (below waveform, left side):**
- "Up next in session" label
- Remaining recordings in the session with index, filename, instrument tag, duration
**Breadcrumb header:**
Library [Date] [filename] with Share button
### 3. Discover (Search / Filter)
Global search across all sessions. Results are **always grouped by session** to preserve date context — filters narrow results, they don't destroy the grouping.
Key elements:
- Prominent search input
- Filter groups: Instrument / Member / Date range (independent pill groups)
- Results header: "N results across M sessions"
- Result groups: session date + name header, then result rows
- Each row: icon (comment vs filename match), filename, match type badge, timestamp + member attribution, instrument tag
- Click row → opens Player at that timestamp
### 4. Settings — Profile
User-level, not band-specific.
Fields: Avatar (upload / remove), Display name, Instrument/role, Bio, Email, Change password
Danger zone: Delete account (removes from all bands, deletes comments — does NOT touch storage files)
### 5. Settings — Notifications
Toggles (on/off) for:
- Email: new comment on a recording I've interacted with, @mentions, new recording uploaded, band invite
- In-app: replies to my comments, daily activity digest, nearby comment placed (within 5s of mine)
### 6. Settings — Audio & Playback
- Default playback speed (select)
- Skip interval: 10s / 30s / 60s (select)
- Toggles: auto-advance to next track, show waveform avatar pins, pause when adding a comment
- Waveform colour picker (amber default, 3 alternatives)
### 7. Settings — Members (band-scoped)
- Member list: avatar, name, email + instrument, role badge (Admin / Member), "···" action menu for non-self
- Roles explained in a 2-column card: Admin (full access) vs Member (listen + comment only)
- Invite form: email input + role select + Send invite button
- Hint: "No account needed to accept"
Role definitions:
```
Admin → upload, delete recordings, manage members, change settings, manage storage
Member → listen, comment, annotate — cannot upload or manage others
Listener → listen and comment only (no upload, no annotations) [planned]
```
### 8. Settings — Storage (band-scoped)
- Connected locations list: provider icon, name, path + usage stats, connected dot (green/grey), Manage / Connect button
- Disconnected providers shown as grey rows with Connect button
- "Add storage location" chip grid: Google Drive, OneDrive, Dropbox, Nextcloud, S3/Compatible, Local folder
- Important copy: *"RehearsalHub reads recordings directly from your storage — files are never copied to our servers"*
- Upload behaviour section: default upload destination (select among connected), auto-organise by date toggle
### 9. Settings — Band Settings (band-scoped, admin only)
- Band logo: upload / use initials
- Band name, Genre/description
- Privacy toggles: recordings visible to members only, allow members to invite others, public band page (shows name + session count only)
- Danger zone: Delete band (removes members, deletes comments/annotations — storage files NOT deleted)
---
## Key UX Principles
1. **Date structure is always the primary axis.** Filtering narrows within it; it never replaces it. Users should always know *when* something was recorded.
2. **Commenting must be frictionless.** Click waveform → playhead jumps + comment input focuses + timestamp pre-filled. One action to start a thought.
3. **Avatar pins are the engagement hook.** Seeing where bandmates commented draws you into the recording spatially. Dense pin clusters signal interesting moments.
4. **Filenames are a known pain point.** `full_rehearsal_take2.wav` means nothing in 3 weeks. Prompt for a human title on upload (but don't block without one).
5. **Storage is the user's, not ours.** Never frame RehearsalHub as a host. The platform is a viewer + collaboration layer on top of the user's existing files.
6. **Band switching is tenant-level.** Switching band re-scopes the entire band section of settings, the library, and the active session context. Account settings (profile, notifications, audio) are always global.
---
## Open Design Decisions
These are intentionally unresolved and should be discussed before building those features:
- **Range annotations** — users want to mark a *section* not just a point (e.g. "this whole chorus"). Drag-to-select on waveform creating a highlighted region with its own comment thread.
- **Comment clustering** — two pins at the same timestamp overlap. Stack vertically, offset horizontally, or show a count badge?
- **Invite link** — shareable URL with optional expiry, in addition to email invites. Much faster for band onboarding.
- **Listener role** — a third role (hear + comment, no upload) for producers, sound engineers, guests.
- **Session-level sharing** — share a single session externally (e.g. with a producer), scoped to that session only, without giving full band access.
- **Waveform range annotations** — tap-drag on waveform to create a region comment (different from point comment).
- **Mobile layout** — the player split layout (waveform left, comments right) must reflow. Comments panel becomes a bottom drawer. This is a significant layout rethink.
- **Keyboard shortcuts** — Space (play/pause), C (drop comment at playhead), ←/→ (seek by skip interval), J/L (half speed / normal speed).
- **Notification delivery** — in-app vs email is designed; push (mobile) and Slack/Discord webhooks are not yet specced.
---
## Mockups (Self-Contained HTML)
The following sections contain complete, runnable HTML mockups produced during design. Paste any section into an `.html` file and open in a browser to see the live UI. Use these as pixel-accurate references during implementation.
---
### Mockup A — Library + Player + Discover (App Shell)
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RehearsalHub — Library / Player / Discover</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{background:#18181e;display:flex;justify-content:center;align-items:flex-start;padding:24px;min-height:100vh;font-family:-apple-system,BlinkMacSystemFont,'Helvetica Neue',sans-serif;font-size:13px;color:#e0e0e8}
.app{display:flex;height:700px;background:#111113;border-radius:14px;overflow:hidden;width:100%;max-width:900px}
.sb{width:218px;min-width:218px;background:#0b0b0d;border-right:1px solid rgba(255,255,255,0.06);display:flex;flex-direction:column;overflow:hidden}
.sb-logo{padding:17px 14px 14px;display:flex;align-items:center;gap:10px;border-bottom:1px solid rgba(255,255,255,0.05)}
.sb-icon{width:28px;height:28px;background:#e8a22a;border-radius:7px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.sb-name{font-size:13px;font-weight:600;color:#eeeef2;letter-spacing:-.2px}
.sb-band{font-size:10px;color:rgba(255,255,255,0.25);margin-top:-1px}
.sb-nav{padding:10px 8px;border-bottom:1px solid rgba(255,255,255,0.05)}
.ni{display:flex;align-items:center;gap:9px;padding:7px 10px;border-radius:7px;cursor:pointer;color:rgba(255,255,255,0.35);transition:all .12s;user-select:none;margin-bottom:1px}
.ni:hover{background:rgba(255,255,255,0.045);color:rgba(255,255,255,0.7)}
.ni.on{background:rgba(232,162,42,0.12);color:#e8a22a}
.sb-sess{flex:1;overflow-y:auto;padding:10px 8px;min-height:0}
.sb-sess::-webkit-scrollbar{width:3px}
.sb-sess::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.08);border-radius:2px}
.sb-lbl{font-size:10px;font-weight:600;letter-spacing:.7px;color:rgba(255,255,255,0.2);text-transform:uppercase;padding:0 6px 5px}
.sr{padding:7px 8px;border-radius:7px;cursor:pointer;margin-bottom:1px;transition:background .1s}
.sr:hover{background:rgba(255,255,255,0.04)}
.sr.on{background:rgba(255,255,255,0.07)}
.sr-date{font-size:10px;font-family:'SF Mono','Fira Code',monospace;color:rgba(255,255,255,0.28)}
.sr-name{font-size:12px;color:rgba(255,255,255,0.55);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-top:1px}
.sr-meta{display:flex;gap:4px;margin-top:3px;flex-wrap:wrap}
.st{font-size:9px;padding:1px 5px;border-radius:3px;background:rgba(255,255,255,0.07);color:rgba(255,255,255,0.28)}
.st.new{background:rgba(61,200,120,0.12);color:#4dba85}
.main{flex:1;display:flex;flex-direction:column;overflow:hidden}
.view{display:none;flex:1;flex-direction:column;overflow:hidden}
.view.on{display:flex}
.tag{font-size:10px;padding:1px 6px;border-radius:3px;background:rgba(255,255,255,0.06);color:rgba(255,255,255,0.35)}
.tag.g{background:rgba(61,200,120,0.1);color:#4dba85}
.tag.d{background:rgba(91,156,240,0.1);color:#7aabf0}
.tag.v{background:rgba(200,90,180,0.1);color:#d070c0}
.tag.k{background:rgba(232,162,42,0.1);color:#e8a22a}
.tag.f{background:rgba(140,90,220,0.1);color:#a878e8}
.btn{padding:5px 12px;border-radius:6px;cursor:pointer;border:1px solid rgba(255,255,255,0.1);background:transparent;color:rgba(255,255,255,0.42);font-size:12px;font-family:inherit;transition:all .12s;white-space:nowrap}
.btn:hover{background:rgba(255,255,255,0.06);color:rgba(255,255,255,0.78)}
.btn.a{background:rgba(232,162,42,0.14);border-color:rgba(232,162,42,0.28);color:#e8a22a}
.btn.a:hover{background:rgba(232,162,42,0.22)}
/* LIBRARY */
.lh{padding:14px 22px 12px;border-bottom:1px solid rgba(255,255,255,0.055);flex-shrink:0}
.lh-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:11px}
.vtitle{font-size:16px;font-weight:500;color:#eeeef2}
.sw{position:relative;flex:1}
.sw input{width:100%;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);border-radius:7px;padding:7px 12px 7px 32px;color:#e2e2e8;font-size:13px;outline:none;font-family:inherit}
.sw input::placeholder{color:rgba(255,255,255,0.2)}
.sw input:focus{border-color:rgba(232,162,42,0.35)}
.si{position:absolute;left:10px;top:50%;transform:translateY(-50%);opacity:.3;pointer-events:none}
.pills{display:flex;gap:5px;flex-wrap:wrap;margin-top:10px}
.pill{padding:3px 10px;border-radius:20px;cursor:pointer;border:1px solid rgba(255,255,255,0.08);background:transparent;color:rgba(255,255,255,0.3);font-size:11px;transition:all .12s;user-select:none;font-family:inherit}
.pill:hover{border-color:rgba(255,255,255,0.16);color:rgba(255,255,255,0.6)}
.pill.on{background:rgba(232,162,42,0.1);border-color:rgba(232,162,42,0.28);color:#e8a22a}
.lc{flex:1;overflow-y:auto;padding:4px 22px 22px}
.lc::-webkit-scrollbar{width:4px}
.lc::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.08);border-radius:2px}
.dg{margin-top:18px}
.dg-hdr{display:flex;align-items:center;gap:10px;margin-bottom:8px}
.dg-lbl{font-size:10px;color:rgba(255,255,255,0.32);text-transform:uppercase;letter-spacing:.6px;font-weight:500;white-space:nowrap}
.dg-line{flex:1;height:1px;background:rgba(255,255,255,0.05)}
.dg-cnt{font-size:10px;color:rgba(255,255,255,0.18)}
.tc{display:flex;align-items:center;gap:11px;padding:9px 13px;border-radius:8px;margin-bottom:3px;background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.04);cursor:pointer;transition:all .12s}
.tc:hover{background:rgba(255,255,255,0.048);border-color:rgba(255,255,255,0.08)}
.tc.play{border-color:rgba(232,162,42,0.28);background:rgba(232,162,42,0.04)}
.tc-pb{width:28px;height:28px;border-radius:50%;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.08);display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all .12s}
.tc:hover .tc-pb{background:rgba(232,162,42,0.15);border-color:rgba(232,162,42,0.3)}
.tc.play .tc-pb{background:#e8a22a;border-color:#e8a22a}
.tc-info{flex:1;min-width:0}
.tc-name{font-size:13px;color:#c8c8d0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-bottom:3px;font-family:'SF Mono','Fira Code',monospace}
.tc-meta{display:flex;align-items:center;gap:7px}
.tc-dur{font-size:11px;font-family:'SF Mono','Fira Code',monospace;color:rgba(255,255,255,0.28)}
.tc-r{display:flex;align-items:center;gap:10px;flex-shrink:0}
.cc{display:flex;align-items:center;gap:4px;color:rgba(255,255,255,0.22);font-size:11px}
.mw{display:flex;align-items:flex-end;gap:1.5px;height:18px;width:34px}
.mwb{width:2px;background:rgba(255,255,255,0.11);border-radius:1px}
.tc.play .mwb{background:rgba(232,162,42,0.45)}
/* PLAYER */
.ph{padding:11px 20px;border-bottom:1px solid rgba(255,255,255,0.055);display:flex;align-items:center;gap:8px;flex-shrink:0}
.bc{display:flex;align-items:center;gap:5px}
.bci{font-size:11px;color:rgba(255,255,255,0.28);cursor:pointer}
.bci:hover{color:rgba(255,255,255,0.6)}
.bcs{color:rgba(255,255,255,0.15);font-size:11px}
.bcc{font-size:12px;color:rgba(255,255,255,0.7);font-family:'SF Mono','Fira Code',monospace}
.sp{flex:1}
.pb{display:flex;flex:1;overflow:hidden}
.pm{flex:1;display:flex;flex-direction:column;overflow:hidden;padding:16px 20px}
.wa{background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.055);border-radius:10px;padding:14px 14px 10px;margin-bottom:12px;flex-shrink:0}
.wa-top{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:6px}
.wa-title{font-size:11px;color:rgba(255,255,255,0.45);font-family:'SF Mono','Fira Code',monospace}
.wa-dur{font-size:10px;font-family:'SF Mono','Fira Code',monospace;color:rgba(255,255,255,0.28)}
.markers{position:relative;height:14px;margin-bottom:5px;overflow:visible}
canvas.wf{width:100%;height:80px;display:block;border-radius:3px}
.scrub{margin-top:9px;position:relative;height:4px;background:rgba(255,255,255,0.08);border-radius:2px;cursor:pointer}
.scrub:hover{background:rgba(255,255,255,0.12)}
.sp-prog{height:100%;background:#e8a22a;border-radius:2px;pointer-events:none}
.sp-thumb{position:absolute;top:-4px;width:12px;height:12px;background:#e8a22a;border-radius:50%;transform:translateX(-50%);pointer-events:none}
.transport{display:flex;align-items:center;gap:12px;padding:4px 0;flex-shrink:0}
.tb{width:32px;height:32px;border-radius:50%;border:1px solid rgba(255,255,255,0.1);background:rgba(255,255,255,0.04);display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .12s;color:rgba(255,255,255,0.45)}
.tb:hover{background:rgba(255,255,255,0.09);color:rgba(255,255,255,0.78)}
.pbt{width:40px;height:40px;background:#e8a22a;border-radius:50%;border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .12s;flex-shrink:0}
.pbt:hover{background:#f0b040;transform:scale(1.04)}
.td{font-family:'SF Mono','Fira Code',monospace;font-size:12px;color:rgba(255,255,255,0.35)}
.td span{color:#d8d8e0}
.vol{display:flex;align-items:center;gap:7px;margin-left:auto}
input[type=range].vs{-webkit-appearance:none;width:68px;height:3px;background:rgba(255,255,255,0.1);border-radius:2px;outline:none;cursor:pointer}
input[type=range].vs::-webkit-slider-thumb{-webkit-appearance:none;width:11px;height:11px;background:rgba(255,255,255,0.42);border-radius:50%;cursor:pointer}
.queue{flex:1;overflow-y:auto;margin-top:12px;min-height:0}
.q-lbl{font-size:10px;color:rgba(255,255,255,0.22);text-transform:uppercase;letter-spacing:.7px;font-weight:600;margin-bottom:7px}
.qi{display:flex;align-items:center;gap:10px;padding:7px 10px;border-radius:7px;background:rgba(255,255,255,0.025);border:1px solid rgba(255,255,255,0.04);cursor:pointer;margin-bottom:3px;transition:background .1s}
.qi:hover{background:rgba(255,255,255,0.05)}
.qi-n{font-size:10px;color:rgba(255,255,255,0.18);font-family:monospace;min-width:14px}
.qi-name{flex:1;font-size:12px;color:rgba(255,255,255,0.52);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:'SF Mono','Fira Code',monospace}
.qi-dur{font-size:10px;font-family:monospace;color:rgba(255,255,255,0.22)}
.cp{width:268px;min-width:268px;border-left:1px solid rgba(255,255,255,0.055);display:flex;flex-direction:column;overflow:hidden;background:rgba(0,0,0,0.12)}
.cp-hdr{padding:12px 15px;border-bottom:1px solid rgba(255,255,255,0.055);display:flex;align-items:center;justify-content:space-between;flex-shrink:0}
.cp-title{font-size:13px;font-weight:500;color:rgba(255,255,255,0.72)}
.cp-badge{font-size:11px;background:rgba(232,162,42,0.14);color:#e8a22a;padding:1px 8px;border-radius:10px}
.cl{flex:1;overflow-y:auto;padding:12px 14px}
.cl::-webkit-scrollbar{width:3px}
.cl::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.08);border-radius:2px}
.cm{margin-bottom:14px;padding-bottom:14px;border-bottom:1px solid rgba(255,255,255,0.04)}
.cm:last-child{border-bottom:none}
.cm-hdr{display:flex;align-items:center;gap:7px;margin-bottom:5px}
.av{width:21px;height:21px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:600;flex-shrink:0}
.cm-auth{font-size:12px;font-weight:500;color:rgba(255,255,255,0.72)}
.cm-ts{font-size:10px;font-family:'SF Mono','Fira Code',monospace;color:#e8a22a;background:rgba(232,162,42,0.1);padding:1px 5px;border-radius:3px;cursor:pointer;margin-left:auto}
.cm-ts:hover{background:rgba(232,162,42,0.2)}
.cm-txt{font-size:12px;color:rgba(255,255,255,0.42);line-height:1.55}
.cm-rep{font-size:11px;color:rgba(255,255,255,0.18);cursor:pointer;margin-top:4px}
.cm-rep:hover{color:rgba(255,255,255,0.5)}
.ci{padding:11px 14px;border-top:1px solid rgba(255,255,255,0.055);flex-shrink:0}
.ci textarea{width:100%;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.07);border-radius:7px;padding:8px 10px;color:#e2e2e8;font-size:12px;resize:none;outline:none;font-family:inherit;height:58px}
.ci textarea::placeholder{color:rgba(255,255,255,0.18)}
.ci textarea:focus{border-color:rgba(232,162,42,0.3)}
.ci-foot{display:flex;justify-content:space-between;align-items:center;margin-top:7px}
.ci-ts{font-size:10px;font-family:'SF Mono','Fira Code',monospace;color:rgba(255,255,255,0.25);background:rgba(255,255,255,0.05);padding:3px 8px;border-radius:4px;display:flex;align-items:center;gap:5px}
.ci-at{font-size:9px;background:rgba(232,162,42,0.1);color:#e8a22a;border-radius:2px;padding:1px 4px}
/* DISCOVER */
.dh{padding:18px 22px 14px;border-bottom:1px solid rgba(255,255,255,0.055);flex-shrink:0}
.dsw{position:relative;flex:1}
.dsw input{width:100%;background:rgba(255,255,255,0.055);border:1px solid rgba(255,255,255,0.09);border-radius:8px;padding:9px 14px 9px 38px;color:#e2e2e8;font-size:14px;outline:none;font-family:inherit}
.dsw input::placeholder{color:rgba(255,255,255,0.2)}
.dsw input:focus{border-color:rgba(232,162,42,0.38)}
.dsi{position:absolute;left:12px;top:50%;transform:translateY(-50%);opacity:.3;pointer-events:none}
.fgs{display:flex;gap:18px;flex-wrap:wrap;margin-top:14px}
.fg-lbl{font-size:10px;color:rgba(255,255,255,0.28);text-transform:uppercase;letter-spacing:.6px;font-weight:500}
.fg-pills{display:flex;gap:5px;flex-wrap:wrap;margin-top:5px}
.dc{flex:1;overflow-y:auto;padding:0 22px 22px}
.dc::-webkit-scrollbar{width:4px}
.dc::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.08);border-radius:2px}
.rh2{display:flex;align-items:center;gap:10px;padding:14px 0 8px}
.rc{font-size:12px;color:rgba(255,255,255,0.28)}
.rl{flex:1;height:1px;background:rgba(255,255,255,0.05)}
.rg{margin-bottom:18px}
.rg-hdr{display:flex;align-items:center;gap:10px;padding:6px 12px;background:rgba(255,255,255,0.025);border-radius:6px;margin-bottom:6px}
.rg-d{font-size:10px;font-family:'SF Mono','Fira Code',monospace;color:rgba(255,255,255,0.3)}
.rg-n{font-size:12px;color:rgba(255,255,255,0.42)}
.rg-cnt{margin-left:auto;font-size:10px;color:rgba(255,255,255,0.2)}
.rc2{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:7px;margin-bottom:3px;background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.04);cursor:pointer;transition:all .12s}
.rc2:hover{background:rgba(255,255,255,0.045);border-color:rgba(255,255,255,0.07)}
.rc2-name{flex:1;font-size:12px;color:#c4c4cc;font-family:'SF Mono','Fira Code',monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rc2-m{display:flex;align-items:center;gap:7px;flex-shrink:0}
.rc2-badge{font-size:10px;padding:1px 6px;border-radius:3px;background:rgba(232,162,42,0.12);color:#e8a22a}
</style>
</head>
<body>
<div class="app">
<div class="sb">
<div class="sb-logo">
<div class="sb-icon"><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="1" y="1.5" width="12" height="2" rx="1" fill="white" opacity=".9"/><rect x="1" y="5.5" width="9" height="2" rx="1" fill="white" opacity=".7"/><rect x="1" y="9.5" width="11" height="2" rx="1" fill="white" opacity=".8"/></svg></div>
<div><div class="sb-name">RehearsalHub</div><div class="sb-band">Loud Hands</div></div>
</div>
<div class="sb-nav">
<div class="ni on" data-v="library"><svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor"><path d="M2 3.5h10v1.5H2zm0 3h10v1.5H2zm0 3h7v1.5H2z"/></svg><span>Library</span></div>
<div class="ni" data-v="player"><svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor"><path d="M3 2l9 5-9 5V2z"/></svg><span>Player</span></div>
<div class="ni" data-v="discover"><svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="5.5" cy="5.5" r="3.5"/><path d="M8.5 8.5l3 3" stroke-linecap="round"/></svg><span>Discover</span></div>
</div>
<div class="sb-sess">
<div class="sb-lbl">Recent Sessions</div>
<div class="sr on"><div class="sr-date">2026-03-31</div><div class="sr-name">Late Night Jam</div><div class="sr-meta"><span class="st new">3 new comments</span></div></div>
<div class="sr"><div class="sr-date">2026-03-28</div><div class="sr-name">Song A Deep Dive</div><div class="sr-meta"><span class="st">5 tracks</span></div></div>
<div class="sr"><div class="sr-date">2026-03-21</div><div class="sr-name">Setlist Run</div><div class="sr-meta"><span class="st">4 tracks</span></div></div>
<div class="sr"><div class="sr-date">2026-03-14</div><div class="sr-name">New Material</div><div class="sr-meta"><span class="st">3 tracks</span></div></div>
</div>
</div>
<div class="main">
<!-- LIBRARY -->
<div id="view-library" class="view on">
<div class="lh">
<div class="lh-top"><span class="vtitle">Library</span><div style="display:flex;gap:8px"><button class="btn" onclick="sv('discover')">Filter</button><button class="btn a">+ Upload</button></div></div>
<div style="display:flex;gap:8px"><div class="sw" style="flex:1"><svg class="si" width="13" height="13" viewBox="0 0 13 13" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="5.5" cy="5.5" r="3.5"/><path d="M8.5 8.5l3 3" stroke-linecap="round"/></svg><input type="text" placeholder="Search recordings, comments…"></div><button class="btn">Sort: Date</button></div>
<div class="pills"><button class="pill on">All</button><button class="pill">Full band</button><button class="pill">Guitar</button><button class="pill">Vocals</button><button class="pill">Drums</button><button class="pill">Keys</button><button class="pill">Commented</button></div>
</div>
<div class="lc">
<div class="dg">
<div class="dg-hdr"><span class="dg-lbl">Today — Mar 31</span><div class="dg-line"></div><span class="dg-cnt">3 recordings</span></div>
<div class="tc play" onclick="sv('player')"><div class="tc-pb"><svg width="9" height="9" viewBox="0 0 9 9" fill="white"><path d="M2 1l6 3.5L2 8V1z"/></svg></div><div class="tc-info"><div class="tc-name">full_rehearsal_take2.wav</div><div class="tc-meta"><span class="tc-dur">28:14</span><span class="tag f">Full band</span><span style="font-size:10px;color:#4dba85;background:rgba(61,200,120,0.08);padding:1px 5px;border-radius:3px">Playing</span></div></div><div class="tc-r"><div class="cc"><svg width="11" height="11" viewBox="0 0 11 11" fill="currentColor"><path d="M1 1h9v7H6.5L5 9.5 3.5 8H1V1z"/></svg><span>7</span></div><div class="mw" id="mw0"></div></div></div>
<div class="tc" onclick="sv('player')"><div class="tc-pb"><svg width="9" height="9" viewBox="0 0 9 9" fill="currentColor" opacity=".4"><path d="M2 1l6 3.5L2 8V1z"/></svg></div><div class="tc-info"><div class="tc-name">song_a_guitar_take3.wav</div><div class="tc-meta"><span class="tc-dur">4:52</span><span class="tag g">Guitar</span></div></div><div class="tc-r"><div class="cc"><svg width="11" height="11" viewBox="0 0 11 11" fill="currentColor"><path d="M1 1h9v7H6.5L5 9.5 3.5 8H1V1z"/></svg><span>2</span></div><div class="mw" id="mw1"></div></div></div>
<div class="tc" onclick="sv('player')"><div class="tc-pb"><svg width="9" height="9" viewBox="0 0 9 9" fill="currentColor" opacity=".4"><path d="M2 1l6 3.5L2 8V1z"/></svg></div><div class="tc-info"><div class="tc-name">drums_only_warmup.wav</div><div class="tc-meta"><span class="tc-dur">7:33</span><span class="tag d">Drums</span></div></div><div class="tc-r"><div class="cc"><svg width="11" height="11" viewBox="0 0 11 11" fill="currentColor"><path d="M1 1h9v7H6.5L5 9.5 3.5 8H1V1z"/></svg><span>1</span></div><div class="mw" id="mw2"></div></div></div>
</div>
<div class="dg">
<div class="dg-hdr"><span class="dg-lbl">Mar 28</span><div class="dg-line"></div><span class="dg-cnt">5 recordings</span></div>
<div class="tc" onclick="sv('player')"><div class="tc-pb"><svg width="9" height="9" viewBox="0 0 9 9" fill="currentColor" opacity=".4"><path d="M2 1l6 3.5L2 8V1z"/></svg></div><div class="tc-info"><div class="tc-name">song_a_full_run_take1.wav</div><div class="tc-meta"><span class="tc-dur">5:18</span><span class="tag f">Full band</span></div></div><div class="tc-r"><div class="cc"><svg width="11" height="11" viewBox="0 0 11 11" fill="currentColor"><path d="M1 1h9v7H6.5L5 9.5 3.5 8H1V1z"/></svg><span>4</span></div><div class="mw" id="mw3"></div></div></div>
<div class="tc" onclick="sv('player')"><div class="tc-pb"><svg width="9" height="9" viewBox="0 0 9 9" fill="currentColor" opacity=".4"><path d="M2 1l6 3.5L2 8V1z"/></svg></div><div class="tc-info"><div class="tc-name">vocals_bridge_isolated.wav</div><div class="tc-meta"><span class="tc-dur">1:47</span><span class="tag v">Vocals</span></div></div><div class="tc-r"><div class="cc"><svg width="11" height="11" viewBox="0 0 11 11" fill="currentColor"><path d="M1 1h9v7H6.5L5 9.5 3.5 8H1V1z"/></svg><span>6</span></div><div class="mw" id="mw4"></div></div></div>
</div>
</div>
</div>
<!-- PLAYER -->
<div id="view-player" class="view">
<div class="ph"><div class="bc"><span class="bci" onclick="sv('library')">Library</span><span class="bcs"></span><span class="bci" onclick="sv('library')">Mar 31</span><span class="bcs"></span><span class="bcc">full_rehearsal_take2.wav</span></div><div class="sp"></div><div style="display:flex;align-items:center;gap:8px"><span class="tag f">Full band</span><button class="btn">Share</button><button class="btn a">+ Annotate</button></div></div>
<div class="pb">
<div class="pm">
<div class="wa"><div class="wa-top"><span class="wa-title">full_rehearsal_take2.wav</span><span class="wa-dur">28:14</span></div><div class="markers" id="mkrow"></div><canvas class="wf" id="wfc"></canvas><div class="scrub" id="scrub" onclick="handleScrub(event)"><div class="sp-prog" id="spp" style="width:35%"></div><div class="sp-thumb" id="spt" style="left:35%"></div></div></div>
<div class="transport"><button class="tb"><svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor"><path d="M2 2h1.5v8H2zm0 4L8.5 2v8L2 6z"/></svg></button><button class="tb"><svg width="13" height="13" viewBox="0 0 13 13" fill="currentColor"><path d="M6.5 2.5a4 4 0 1 0 3.46 2H8a2.5 2.5 0 1 1-1.5-2.28V4L9.5 2 6.5 0v2.5z"/></svg></button><button class="pbt" id="pbt" onclick="togglePlay()"><svg id="pico" width="15" height="15" viewBox="0 0 15 15" fill="white"><path d="M4 2.5l9 5-9 5V2.5z"/></svg></button><button class="tb"><svg width="13" height="13" viewBox="0 0 13 13" fill="currentColor" style="transform:scaleX(-1)"><path d="M6.5 2.5a4 4 0 1 0 3.46 2H8a2.5 2.5 0 1 1-1.5-2.28V4L9.5 2 6.5 0v2.5z"/></svg></button><button class="tb"><svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor" style="transform:scaleX(-1)"><path d="M2 2h1.5v8H2zm0 4L8.5 2v8L2 6z"/></svg></button><div class="td"><span id="tcu">09:52</span> / 28:14</div><div class="vol"><svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor"><path d="M2 5h3l4-3v10l-4-3H2V5z"/><path d="M10.5 4.5C11.5 5.5 11.5 8.5 10.5 9.5" stroke="currentColor" stroke-width="1.2" fill="none" stroke-linecap="round"/></svg><input type="range" class="vs" min="0" max="100" value="78" step="1"></div></div>
<div class="queue"><div class="q-lbl">Up next in session</div><div class="qi"><span class="qi-n">2</span><span class="qi-name">song_a_guitar_take3.wav</span><span class="tag g" style="font-size:10px">Guitar</span><span class="qi-dur">4:52</span></div><div class="qi"><span class="qi-n">3</span><span class="qi-name">drums_only_warmup.wav</span><span class="tag d" style="font-size:10px">Drums</span><span class="qi-dur">7:33</span></div></div>
</div>
<div class="cp">
<div class="cp-hdr"><span class="cp-title">Comments</span><span class="cp-badge">7</span></div>
<div class="cl">
<div class="cm"><div class="cm-hdr"><div class="av" style="background:rgba(91,156,240,0.18);color:#7aabf0">AL</div><span class="cm-auth">Alex</span><span class="cm-ts">2:18</span></div><div class="cm-txt">Transition into the chorus feels rushed — could we hold the pre-chorus a beat longer?</div><div class="cm-rep">↩ Reply</div></div>
<div class="cm"><div class="cm-hdr"><div class="av" style="background:rgba(200,90,180,0.15);color:#d070c0">MJ</div><span class="cm-auth">Maya</span><span class="cm-ts">4:41</span></div><div class="cm-txt">Guitar tone has that warmth we were looking for. Keeping this patch.</div><div class="cm-rep">↩ Reply</div></div>
<div class="cm" style="background:rgba(232,162,42,0.04);border-radius:6px;padding:8px;border:1px solid rgba(232,162,42,0.12)"><div class="cm-hdr"><div class="av" style="background:rgba(61,200,120,0.15);color:#4dba85">JD</div><span class="cm-auth">Jordan</span><span class="cm-ts">9:52</span></div><div class="cm-txt">Tempo drift starts here — check against click. Gets worse through the bridge.</div><div class="cm-rep">↩ Reply</div></div>
<div class="cm"><div class="cm-hdr"><div class="av" style="background:rgba(232,162,42,0.15);color:#e8a22a">SR</div><span class="cm-auth">Sam</span><span class="cm-ts">13:20</span></div><div class="cm-txt">The keys fill in bar 48 is perfect — keep for the studio recording.</div><div class="cm-rep">↩ Reply</div></div>
<div class="cm"><div class="cm-hdr"><div class="av" style="background:rgba(140,90,220,0.15);color:#a878e8">KL</div><span class="cm-auth">Kim</span><span class="cm-ts">20:18</span></div><div class="cm-txt">Something clips here on the room mic. @Jordan check gain staging?</div><div class="cm-rep">↩ Reply</div></div>
</div>
<div class="ci"><textarea placeholder="Add a comment… (auto-pins to playhead)"></textarea><div class="ci-foot"><div class="ci-ts"><span class="ci-at">AT</span><span id="ci-ts-val">9:52</span></div><button class="btn a" style="font-size:11px;padding:4px 10px">Post</button></div></div>
</div>
</div>
</div>
<!-- DISCOVER -->
<div id="view-discover" class="view">
<div class="dh">
<div style="display:flex;gap:10px"><div class="dsw"><svg class="dsi" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="6.5" cy="6.5" r="4.5"/><path d="M10.5 10.5l3.5 3.5" stroke-linecap="round"/></svg><input type="text" value="chorus transition" placeholder="Search recordings, comments, annotations…"></div></div>
<div class="fgs">
<div><div class="fg-lbl">Instrument</div><div class="fg-pills"><button class="pill on">All</button><button class="pill">Full band</button><button class="pill">Guitar</button><button class="pill">Vocals</button></div></div>
<div><div class="fg-lbl">Member</div><div class="fg-pills"><button class="pill on">All</button><button class="pill">Alex</button><button class="pill">Maya</button><button class="pill">Jordan</button></div></div>
<div><div class="fg-lbl">Date range</div><div class="fg-pills"><button class="pill">Last week</button><button class="pill on">This month</button><button class="pill">All time</button></div></div>
</div>
</div>
<div class="dc">
<div class="rh2"><span class="rc">14 results across 4 sessions</span><div class="rl"></div><span style="font-size:11px;color:rgba(255,255,255,0.2)">grouped by session</span></div>
<div class="rg"><div class="rg-hdr"><span class="rg-d">2026-03-31</span><span class="rg-n" style="margin-left:6px">Late Night Jam</span><span class="rg-cnt">5 results</span></div><div class="rc2" onclick="sv('player')"><svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor" style="opacity:.35;flex-shrink:0"><path d="M1 1h10v8H7.5L6 10.5 4.5 9H1V1z"/></svg><div class="rc2-name">full_rehearsal_take2.wav</div><div class="rc2-m"><span class="rc2-badge">comment match</span><span style="font-size:10px;color:rgba(255,255,255,0.22)">@2:18 · Alex</span><span class="tag f" style="font-size:10px">Full band</span></div></div><div class="rc2" onclick="sv('player')"><svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor" style="opacity:.35;flex-shrink:0"><path d="M1 1h10v8H7.5L6 10.5 4.5 9H1V1z"/></svg><div class="rc2-name">full_rehearsal_take2.wav</div><div class="rc2-m"><span class="rc2-badge">comment match</span><span style="font-size:10px;color:rgba(255,255,255,0.22)">@9:52 · Jordan</span><span class="tag f" style="font-size:10px">Full band</span></div></div></div>
<div class="rg"><div class="rg-hdr"><span class="rg-d">2026-03-28</span><span class="rg-n" style="margin-left:6px">Song A Deep Dive</span><span class="rg-cnt">4 results</span></div><div class="rc2" onclick="sv('player')"><svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.2" style="opacity:.4;flex-shrink:0"><circle cx="6" cy="5.5" r="3.5"/><path d="M6 4v2l1.2 1.2" stroke-linecap="round"/></svg><div class="rc2-name">song_a_full_run_take1.wav</div><div class="rc2-m"><span style="font-size:10px;padding:1px 6px;border-radius:3px;background:rgba(91,156,240,0.12);color:#7aabf0">filename match</span><span class="tag f" style="font-size:10px">Full band</span></div></div></div>
</div>
</div>
</div>
</div>
<script>
function sv(name){document.querySelectorAll('.view').forEach(function(v){v.classList.remove('on');});document.querySelectorAll('.ni').forEach(function(n){n.classList.remove('on');});var el=document.getElementById('view-'+name);if(el)el.classList.add('on');var ni=document.querySelector('.ni[data-v="'+name+'"]');if(ni)ni.classList.add('on');if(name==='player')setTimeout(function(){drawWF(curPct);renderMK();},60);}
document.querySelectorAll('.ni').forEach(function(n){n.addEventListener('click',function(){sv(this.dataset.v);});});
function mwBars(id,seed){var el=document.getElementById(id);if(!el)return;var s=seed,bars='';function r(){s=(s*1664525+1013904223)&0xffffffff;return(s>>>0)/0xffffffff;}for(var i=0;i<14;i++){var h=Math.max(15,Math.floor(r()*100));bars+='<div class="mwb" style="height:'+h+'%"></div>';}el.innerHTML=bars;}
for(var i=0;i<8;i++)mwBars('mw'+i,i*7919+31337);
var curPct=0.35;
function drawWF(pct){var c=document.getElementById('wfc');if(!c)return;var dpr=window.devicePixelRatio||1;c.width=c.offsetWidth*dpr;c.height=80*dpr;c.style.width='100%';c.style.height='80px';var ctx=c.getContext('2d'),W=c.width,H=c.height;ctx.clearRect(0,0,W,H);var bars=Math.floor(W/2.2),s=54321;function r(){s=(s*1664525+1013904223)&0xffffffff;return(s>>>0)/0xffffffff;}for(var i=0;i<bars;i++){var t=i/bars;var env=0.35+0.65*Math.sin(t*Math.PI)*(0.65+0.35*Math.sin(t*22+1.4));var h=(0.06+r()*0.88)*env*H;var x=i*2.2;ctx.fillStyle=t<pct?'#e8a22a':'rgba(255,255,255,0.12)';ctx.fillRect(x,(H-h)/2,1.6,h);}var px=W*pct;ctx.fillStyle='#e8a22a';ctx.globalAlpha=0.85;ctx.fillRect(px-1,0,2,H);ctx.globalAlpha=1;}
var mkData=[{p:.08,c:'#7aabf0',l:'2:18'},{p:.17,c:'#d070c0',l:'4:41'},{p:.35,c:'#4dba85',l:'9:52'},{p:.47,c:'#e8a22a',l:'13:20'},{p:.72,c:'#a878e8',l:'20:18'},{p:.85,c:'#4dba85',l:'24:04'}];
function renderMK(){var row=document.getElementById('mkrow');if(!row)return;row.innerHTML='';var W=row.offsetWidth;mkData.forEach(function(m){var mk=document.createElement('div');mk.style.cssText='position:absolute;left:'+Math.round(m.p*W)+'px;top:0;cursor:pointer;display:flex;flex-direction:column;align-items:center;';mk.innerHTML='<div style="width:7px;height:7px;border-radius:50%;background:'+m.c+'"></div><div style="width:1px;height:105px;background:'+m.c+';opacity:.2;margin-top:0"></div>';mk.title=m.l;mk.onclick=(function(pp){return function(){jt(pp);};})(m.p);row.appendChild(mk);});}
function jt(pct){curPct=pct;var tot=28*60+14,secs=Math.floor(pct*tot);var m=Math.floor(secs/60),s=secs%60;document.getElementById('tcu').textContent=m+':'+String(s).padStart(2,'0');document.getElementById('ci-ts-val').textContent=m+':'+String(s).padStart(2,'0');document.getElementById('spp').style.width=(pct*100)+'%';document.getElementById('spt').style.left=(pct*100)+'%';drawWF(pct);}
function handleScrub(e){var s=document.getElementById('scrub'),r=s.getBoundingClientRect();jt(Math.max(0,Math.min(1,(e.clientX-r.left)/r.width)));}
var playing=false,piv=null;
function togglePlay(){playing=!playing;var ico=document.getElementById('pico');if(playing){ico.innerHTML='<path d="M4 2h3v11H4zm5 0h3v11H9z"/>';piv=setInterval(function(){curPct=Math.min(1,curPct+1/((28*60+14)*10));if(curPct>=1){togglePlay();curPct=0.35;return;}document.getElementById('spp').style.width=(curPct*100)+'%';document.getElementById('spt').style.left=(curPct*100)+'%';var tot=28*60+14,secs=Math.floor(curPct*tot);var m=Math.floor(secs/60),s=secs%60;document.getElementById('tcu').textContent=m+':'+String(s).padStart(2,'0');drawWF(curPct);},100);}else{ico.innerHTML='<path d="M4 2.5l9 5-9 5V2.5z"/>';clearInterval(piv);}}
document.querySelectorAll('.pill').forEach(function(p){p.addEventListener('click',function(){var par=this.parentElement;par.querySelectorAll('.pill').forEach(function(x){x.classList.remove('on');});this.classList.add('on');});});
setTimeout(function(){drawWF(curPct);renderMK();},120);
window.addEventListener('resize',function(){if(document.getElementById('view-player').classList.contains('on')){drawWF(curPct);renderMK();}});
</script>
</body>
</html>
```
---
### Mockup B — Player (Focused, with Avatar Waveform Pins)
Save as `mockup-player.html`. Full interactive player with live avatar pins on waveform, comment compose flow, and playback.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RehearsalHub — Player</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{background:#18181e;display:flex;justify-content:center;padding:24px;font-family:-apple-system,BlinkMacSystemFont,'Helvetica Neue',sans-serif;font-size:13px;color:#e0e0e8}
.player{background:#0f0f12;border-radius:14px;overflow:hidden;width:100%;max-width:820px}
.ph{padding:14px 20px 12px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid rgba(255,255,255,0.05)}
.ph-left{display:flex;align-items:center;gap:10px}
.ph-back{width:28px;height:28px;border-radius:50%;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);display:flex;align-items:center;justify-content:center;cursor:pointer;color:rgba(255,255,255,0.35);transition:all .12s;flex-shrink:0}
.ph-back:hover{background:rgba(255,255,255,0.09);color:rgba(255,255,255,0.7)}
.ph-name{font-size:13px;font-weight:500;color:#d8d8e4;font-family:'SF Mono','Fira Code',monospace}
.ph-meta{display:flex;align-items:center;gap:7px;margin-top:2px}
.ph-date{font-size:11px;color:rgba(255,255,255,0.25)}
.tag{font-size:10px;padding:1px 6px;border-radius:3px}
.tg-f{background:rgba(140,90,220,0.1);color:#a878e8}
.btn{padding:5px 12px;border-radius:6px;cursor:pointer;border:1px solid rgba(255,255,255,0.09);background:transparent;color:rgba(255,255,255,0.38);font-size:12px;font-family:inherit;transition:all .12s}
.btn:hover{background:rgba(255,255,255,0.06);color:rgba(255,255,255,0.72)}
.wzone{padding:20px 20px 0;position:relative}
.pin-layer{position:relative;height:44px;margin-bottom:0}
.apin{position:absolute;transform:translateX(-50%);display:flex;flex-direction:column;align-items:center;cursor:pointer;z-index:10;transition:transform .12s}
.apin:hover{transform:translateX(-50%) scale(1.15)}
.apin-av{width:26px;height:26px;border-radius:50%;border:2px solid transparent;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;color:white;transition:all .12s;box-shadow:0 2px 8px rgba(0,0,0,0.45)}
.apin-stem{width:1.5px;height:14px;opacity:.35;margin-top:2px}
.apin.active .apin-av{transform:scale(1.18);box-shadow:0 0 0 3px rgba(255,255,255,0.12)}
.wc{position:relative;cursor:pointer;border-radius:6px;overflow:visible}
.wc canvas{display:block;width:100%;height:104px;border-radius:6px}
.playhead-line{position:absolute;top:0;bottom:0;width:2px;background:#e8a22a;border-radius:1px;pointer-events:none;z-index:5}
.playhead-dot{position:absolute;top:-4px;width:10px;height:10px;background:#e8a22a;border-radius:50%;transform:translateX(-50%);pointer-events:none;z-index:6}
.scrubber{height:3px;background:rgba(255,255,255,0.07);border-radius:2px;margin-top:8px;cursor:pointer;position:relative}
.scrub-fill{height:100%;background:#e8a22a;border-radius:2px;pointer-events:none}
.timebar{display:flex;justify-content:space-between;margin-top:6px;padding:0 1px}
.timebar span{font-size:10px;font-family:'SF Mono','Fira Code',monospace;color:rgba(255,255,255,0.22)}
.timebar .tcur{color:#e8a22a}
.transport{display:flex;align-items:center;gap:10px;padding:16px 20px 12px;justify-content:center}
.tb{width:34px;height:34px;border-radius:50%;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.07);display:flex;align-items:center;justify-content:center;cursor:pointer;color:rgba(255,255,255,0.35);transition:all .12s}
.tb:hover{background:rgba(255,255,255,0.08);color:rgba(255,255,255,0.7)}
.pbt{width:46px;height:46px;background:#e8a22a;border-radius:50%;border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .15s}
.pbt:hover{background:#f0b740;transform:scale(1.06)}
.vol-wrap{display:flex;align-items:center;gap:7px;margin-left:auto}
.vol-wrap svg{opacity:.28}
input[type=range].vs{-webkit-appearance:none;width:70px;height:3px;background:rgba(255,255,255,0.1);border-radius:2px;outline:none;cursor:pointer}
input[type=range].vs::-webkit-slider-thumb{-webkit-appearance:none;width:11px;height:11px;background:rgba(255,255,255,0.45);border-radius:50%}
.speed{font-size:11px;color:rgba(255,255,255,0.28);background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.07);border-radius:4px;padding:3px 7px;cursor:pointer;font-family:inherit;margin-right:auto}
.cstrip{border-top:1px solid rgba(255,255,255,0.05);background:#0b0b0e}
.compose{padding:12px 16px;border-bottom:1px solid rgba(255,255,255,0.05);display:flex;align-items:flex-start;gap:10px}
.my-av{width:28px;height:28px;border-radius:50%;background:rgba(232,162,42,0.18);border:1.5px solid rgba(232,162,42,0.4);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;color:#e8a22a;flex-shrink:0;margin-top:2px}
.compose-right{flex:1;min-width:0}
.compose-pill{display:flex;align-items:center;gap:7px;margin-bottom:6px}
.ts-pill{font-size:11px;font-family:'SF Mono','Fira Code',monospace;background:rgba(232,162,42,0.1);color:#e8a22a;border:1px solid rgba(232,162,42,0.22);padding:3px 9px;border-radius:20px;display:flex;align-items:center;gap:6px}
.ts-pill-dot{width:6px;height:6px;border-radius:50%;background:#e8a22a;animation:blink 1.1s infinite}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.3}}
.ts-hint{font-size:11px;color:rgba(255,255,255,0.18)}
.cinput{width:100%;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.07);border-radius:8px;padding:9px 12px;color:#e0e0e8;font-size:13px;resize:none;outline:none;font-family:inherit;height:42px;transition:all .18s}
.cinput:focus{height:72px;border-color:rgba(232,162,42,0.35);background:rgba(255,255,255,0.07)}
.cinput::placeholder{color:rgba(255,255,255,0.18)}
.compose-foot{display:flex;justify-content:space-between;align-items:center;margin-top:7px;display:none}
.compose-foot.vis{display:flex}
.cf-tag{font-size:11px;padding:3px 8px;border-radius:4px;cursor:pointer;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.07);color:rgba(255,255,255,0.32);transition:all .12s;font-family:inherit}
.cf-tag:hover{color:rgba(255,255,255,0.6)}
.cf-tag.sel{background:rgba(232,162,42,0.1);border-color:rgba(232,162,42,0.3);color:#e8a22a}
.post-btn{padding:6px 16px;border-radius:6px;background:#e8a22a;border:none;color:#0f0f12;font-size:12px;font-weight:600;font-family:inherit;cursor:pointer}
.post-btn:hover{background:#f0b740}
.post-btn:disabled{opacity:.35;cursor:default}
.clist{max-height:248px;overflow-y:auto}
.clist::-webkit-scrollbar{width:3px}
.clist::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.07);border-radius:2px}
.citem{display:flex;gap:10px;padding:11px 16px;border-bottom:1px solid rgba(255,255,255,0.04);cursor:pointer;transition:background .12s}
.citem:hover{background:rgba(255,255,255,0.025)}
.citem.hl{background:rgba(232,162,42,0.05)}
.ci-av{width:26px;height:26px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;flex-shrink:0;margin-top:1px}
.ci-top{display:flex;align-items:center;gap:7px;margin-bottom:3px}
.ci-auth{font-size:12px;font-weight:500;color:rgba(255,255,255,0.7)}
.ci-ts{font-size:10px;font-family:'SF Mono','Fira Code',monospace;color:#e8a22a;background:rgba(232,162,42,0.09);padding:1px 6px;border-radius:3px;cursor:pointer}
.ci-ts:hover{background:rgba(232,162,42,0.2)}
.ci-tag{font-size:10px;padding:1px 5px;border-radius:3px}
.ci-txt{font-size:12px;color:rgba(255,255,255,0.42);line-height:1.55}
.ci-reply{font-size:11px;color:rgba(255,255,255,0.18);margin-top:4px;cursor:pointer}
.ci-reply:hover{color:rgba(255,255,255,0.5)}
.tooltip{position:absolute;bottom:calc(100% + 8px);left:50%;transform:translateX(-50%);background:#1c1c22;border:1px solid rgba(255,255,255,0.1);border-radius:8px;padding:9px 12px;width:200px;z-index:50;pointer-events:none;opacity:0;transition:opacity .15s}
.tooltip.vis{opacity:1}
.tt-top{display:flex;align-items:center;gap:7px;margin-bottom:5px}
.tt-av{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:8px;font-weight:700}
.tt-auth{font-size:11px;font-weight:500;color:rgba(255,255,255,0.72)}
.tt-ts{font-size:10px;font-family:'SF Mono','Fira Code',monospace;color:#e8a22a;margin-left:auto}
.tt-txt{font-size:11px;color:rgba(255,255,255,0.45);line-height:1.5}
.tt-arrow{position:absolute;bottom:-5px;left:50%;transform:translateX(-50%) rotate(45deg);width:8px;height:8px;background:#1c1c22;border-right:1px solid rgba(255,255,255,0.1);border-bottom:1px solid rgba(255,255,255,0.1)}
</style>
</head>
<body>
<div class="player">
<div class="ph"><div class="ph-left"><div class="ph-back"><svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor"><path d="M7 1L3 5l4 4" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg></div><div><div class="ph-name">full_rehearsal_take2.wav</div><div class="ph-meta"><span class="ph-date">Mar 31, 2026</span><span class="tag tg-f">Full band</span><span class="ph-date">· 28:14</span></div></div></div><button class="btn">Share</button></div>
<div class="wzone">
<div class="pin-layer" id="pinLayer"></div>
<div class="wc" id="wc" onclick="handleWCClick(event)"><canvas id="wfc"></canvas><div class="playhead-line" id="phl" style="left:0"></div><div class="playhead-dot" id="phd" style="left:0"></div></div>
<div class="scrubber" id="scrubber" onclick="handleScrubClick(event)"><div class="scrub-fill" id="scrubFill" style="width:0%"></div></div>
<div class="timebar"><span class="tcur" id="tcur">0:00</span><span>14:07</span><span>28:14</span></div>
</div>
<div class="transport">
<button class="speed" id="spdbtn" onclick="cycleSpeed()">1×</button>
<button class="tb" onclick="skip(-30)"><svg width="13" height="13" viewBox="0 0 13 13" fill="currentColor"><path d="M6.5 2.5a4 4 0 1 0 3.46 2H8a2.5 2.5 0 1 1-1.5-2.28V4L9.5 2 6.5 0v2.5z"/></svg></button>
<button class="pbt" id="pbt" onclick="togglePlay()"><svg id="pico" width="16" height="16" viewBox="0 0 16 16" fill="white"><path d="M4 2l10 6-10 6V2z"/></svg></button>
<button class="tb" onclick="skip(30)"><svg width="13" height="13" viewBox="0 0 13 13" fill="currentColor" style="transform:scaleX(-1)"><path d="M6.5 2.5a4 4 0 1 0 3.46 2H8a2.5 2.5 0 1 1-1.5-2.28V4L9.5 2 6.5 0v2.5z"/></svg></button>
<div class="vol-wrap"><svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor"><path d="M2 5h3l4-3v10l-4-3H2V5z"/><path d="M10.5 4.5C11.5 5.5 11.5 8.5 10.5 9.5" stroke="currentColor" stroke-width="1.2" fill="none" stroke-linecap="round"/></svg><input type="range" class="vs" min="0" max="100" value="80" step="1"></div>
</div>
<div class="cstrip">
<div class="compose"><div class="my-av">ST</div><div class="compose-right"><div class="compose-pill"><div class="ts-pill"><div class="ts-pill-dot"></div><span id="ts-pill-val">0:00</span></div><span class="ts-hint">· comment pins to playhead</span></div><textarea class="cinput" id="cinput" placeholder="What do you hear at this moment…" oninput="onCInput()" onfocus="onCFocus()" onblur="onCBlur()"></textarea><div class="compose-foot" id="cfoot"><div style="display:flex;gap:6px"><button class="cf-tag" onclick="toggleTag(this,'suggestion')">suggestion</button><button class="cf-tag" onclick="toggleTag(this,'issue')">issue</button><button class="cf-tag" onclick="toggleTag(this,'keeper')">keeper</button></div><button class="post-btn" id="postbtn" onclick="postComment()" disabled>Post</button></div></div></div>
<div class="clist" id="clist"></div>
</div>
</div>
<script>
var TOTAL=28*60+14,pct=0,playing=false,piv=null;
var speeds=[0.5,0.75,1,1.25,1.5,2],spdIdx=2,selectedTag='';
var MEMBERS=[{id:'AL',name:'Alex',bg:'rgba(91,156,240,0.18)',bc:'rgba(91,156,240,0.6)',color:'#7aabf0'},{id:'MJ',name:'Maya',bg:'rgba(200,90,180,0.18)',bc:'rgba(200,90,180,0.6)',color:'#d070c0'},{id:'JD',name:'Jordan',bg:'rgba(61,200,120,0.18)',bc:'rgba(61,200,120,0.6)',color:'#4dba85'},{id:'SR',name:'Sam',bg:'rgba(232,162,42,0.18)',bc:'rgba(232,162,42,0.6)',color:'#e8a22a'},{id:'KL',name:'Kim',bg:'rgba(140,90,220,0.18)',bc:'rgba(140,90,220,0.6)',color:'#a878e8'}];
var TAGS={suggestion:{bg:'rgba(91,156,240,0.1)',color:'#7aabf0'},issue:{bg:'rgba(220,80,80,0.1)',color:'#e07070'},keeper:{bg:'rgba(61,200,120,0.1)',color:'#4dba85'}};
var comments=[{pct:.08,mi:0,text:'Transition into the chorus feels rushed.',tag:'suggestion'},{pct:.17,mi:1,text:'Guitar tone has that warmth. Keeping this patch.',tag:'keeper'},{pct:.35,mi:2,text:'Tempo drift starts here — check against click.',tag:'issue'},{pct:.47,mi:3,text:'Keys fill in bar 48 is perfect.',tag:'keeper'},{pct:.72,mi:4,text:'Something clips on the room mic.',tag:'issue'},{pct:.85,mi:2,text:'Outro needs another bar to breathe.',tag:'suggestion'}];
function fmtTime(s){s=Math.round(s);return Math.floor(s/60)+':'+(s%60<10?'0':'')+(s%60);}
function M(i){return MEMBERS[i%MEMBERS.length];}
function drawWF(p){var c=document.getElementById('wfc'),wc=document.getElementById('wc');var dpr=window.devicePixelRatio||1;c.width=wc.offsetWidth*dpr;c.height=104*dpr;c.style.width='100%';c.style.height='104px';var ctx=c.getContext('2d'),W=c.width,H=c.height;ctx.clearRect(0,0,W,H);var bars=Math.floor(W/2.2),s=54321;function r(){s=(s*1664525+1013904223)&0xffffffff;return(s>>>0)/0xffffffff;}for(var i=0;i<bars;i++){var t=i/bars,env=0.38+0.62*Math.sin(t*Math.PI)*(0.6+0.4*Math.sin(t*18+1.2)),h=(0.07+r()*0.86)*env*H,x=i*2.2;ctx.fillStyle=t<p?'#c8861a':'rgba(255,255,255,0.09)';ctx.fillRect(x,(H-h)/2,1.6,h);}}
function updatePlayhead(p){var W=document.getElementById('wc').offsetWidth,px=p*W;document.getElementById('phl').style.left=px+'px';document.getElementById('phd').style.left=px+'px';document.getElementById('scrubFill').style.width=(p*100)+'%';document.getElementById('tcur').textContent=fmtTime(p*TOTAL);document.getElementById('ts-pill-val').textContent=fmtTime(p*TOTAL);}
function setP(val,redraw){pct=Math.max(0,Math.min(1,val));updatePlayhead(pct);if(redraw)drawWF(pct);highlightComment();}
function buildPins(){var layer=document.getElementById('pinLayer');layer.innerHTML='';var W=layer.offsetWidth;comments.forEach(function(cm,idx){var m=M(cm.mi),pin=document.createElement('div');pin.className='apin';pin.style.left=Math.round(cm.pct*W)+'px';pin.innerHTML='<div class="tooltip" id="tt'+idx+'"><div class="tt-top"><div class="tt-av" style="background:'+m.bg+';color:'+m.color+'">'+m.id+'</div><span class="tt-auth">'+m.name+'</span><span class="tt-ts">'+fmtTime(cm.pct*TOTAL)+'</span></div><div class="tt-txt">'+cm.text+'</div><div class="tt-arrow"></div></div><div class="apin-av" style="background:'+m.bg+';border-color:'+m.bc+';color:'+m.color+'">'+m.id+'</div><div class="apin-stem" style="background:'+m.color+'"></div>';pin.addEventListener('mouseenter',function(i){return function(){document.getElementById('tt'+i).classList.add('vis');pin.classList.add('active');};}(idx));pin.addEventListener('mouseleave',function(i){return function(){document.getElementById('tt'+i).classList.remove('vis');pin.classList.remove('active');};}(idx));pin.addEventListener('click',function(cm2,i){return function(e){e.stopPropagation();setP(cm2.pct,true);highlightCItem(i);scrollToCItem(i);};}(cm,idx));layer.appendChild(pin);});}
function highlightCItem(idx){document.querySelectorAll('.citem').forEach(function(el,i){el.classList.toggle('hl',i===idx);});}
function scrollToCItem(idx){var items=document.querySelectorAll('.citem');if(items[idx])items[idx].scrollIntoView({behavior:'smooth',block:'nearest'});}
function highlightComment(){var nearest=-1,minD=0.04;comments.forEach(function(cm,i){var d=Math.abs(cm.pct-pct);if(d<minD){minD=d;nearest=i;}});highlightCItem(nearest);}
function buildList(){var list=document.getElementById('clist');list.innerHTML='';comments.forEach(function(cm,idx){var m=M(cm.mi),tg=cm.tag&&TAGS[cm.tag]?'<span class="ci-tag" style="background:'+TAGS[cm.tag].bg+';color:'+TAGS[cm.tag].color+'">'+cm.tag+'</span>':'';var el=document.createElement('div');el.className='citem';el.innerHTML='<div class="ci-av" style="background:'+m.bg+';color:'+m.color+'">'+m.id+'</div><div style="flex:1;min-width:0"><div class="ci-top"><span class="ci-auth">'+m.name+'</span><span class="ci-ts" data-pct="'+cm.pct+'">'+fmtTime(cm.pct*TOTAL)+'</span>'+tg+'</div><div class="ci-txt">'+cm.text+'</div><span class="ci-reply">↩ Reply</span></div>';el.addEventListener('click',function(cm2,i){return function(){setP(cm2.pct,true);buildPins();highlightCItem(i);};}(cm,idx));list.appendChild(el);});}
function handleWCClick(e){var r=document.getElementById('wc').getBoundingClientRect();setP((e.clientX-r.left)/r.width,true);buildPins();document.getElementById('cinput').focus();}
function handleScrubClick(e){var r=document.getElementById('scrubber').getBoundingClientRect();setP((e.clientX-r.left)/r.width,true);buildPins();}
function togglePlay(){playing=!playing;var ico=document.getElementById('pico');if(playing){ico.innerHTML='<path d="M4 2h3v12H4zm6 0h3v12H10z"/>';var rate=speeds[spdIdx];piv=setInterval(function(){setP(pct+(rate/(TOTAL*10)),true);buildPins();if(pct>=1){togglePlay();pct=0;setP(0,true);}},100);}else{ico.innerHTML='<path d="M4 2l10 6-10 6V2z"/>';clearInterval(piv);}}
function skip(secs){setP(pct+secs/TOTAL,true);buildPins();}
function cycleSpeed(){spdIdx=(spdIdx+1)%speeds.length;document.getElementById('spdbtn').textContent=speeds[spdIdx]+'×';}
function onCInput(){document.getElementById('postbtn').disabled=document.getElementById('cinput').value.trim().length===0;}
function onCFocus(){document.getElementById('cfoot').classList.add('vis');}
function onCBlur(){setTimeout(function(){if(document.getElementById('cinput').value.trim()==='')document.getElementById('cfoot').classList.remove('vis');},200);}
function toggleTag(btn,tag){document.querySelectorAll('.cf-tag').forEach(function(b){b.classList.remove('sel');});if(selectedTag===tag){selectedTag='';}else{selectedTag=tag;btn.classList.add('sel');}}
function postComment(){var txt=document.getElementById('cinput').value.trim();if(!txt)return;comments.push({pct:pct,mi:0,text:txt,tag:selectedTag||''});comments.sort(function(a,b){return a.pct-b.pct;});document.getElementById('cinput').value='';selectedTag='';document.querySelectorAll('.cf-tag').forEach(function(b){b.classList.remove('sel');});document.getElementById('postbtn').disabled=true;document.getElementById('cfoot').classList.remove('vis');buildList();buildPins();drawWF(pct);}
function init(){drawWF(pct);updatePlayhead(pct);setTimeout(function(){buildPins();buildList();},60);}
window.addEventListener('resize',function(){drawWF(pct);setTimeout(function(){buildPins();},30);});
init();
</script>
</body>
</html>
```
---
### Mockup C — Settings (All Panels)
Save as `mockup-settings.html`. Includes band switcher, Profile, Notifications, Audio & Playback, Members, Storage, and Band Settings panels.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RehearsalHub — Settings</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{background:#18181e;display:flex;justify-content:center;padding:24px;font-family:-apple-system,BlinkMacSystemFont,'Helvetica Neue',sans-serif;font-size:13px;color:#e0e0e8}
.app{background:#0f0f12;border-radius:14px;overflow:hidden;width:100%;max-width:820px;display:flex;height:660px}
.sb{width:210px;min-width:210px;background:#0b0b0e;border-right:1px solid rgba(255,255,255,0.05);display:flex;flex-direction:column}
.sb-band{padding:14px 12px 10px;border-bottom:1px solid rgba(255,255,255,0.05)}
.sb-band-lbl{font-size:10px;color:rgba(255,255,255,0.22);text-transform:uppercase;letter-spacing:.7px;font-weight:500;margin-bottom:7px}
.band-sw{display:flex;align-items:center;gap:8px;padding:7px 9px;background:rgba(255,255,255,0.05);border-radius:8px;cursor:pointer;border:1px solid rgba(255,255,255,0.07);transition:all .12s}
.band-sw:hover{background:rgba(255,255,255,0.08)}
.bav{width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;flex-shrink:0}
.band-name{flex:1;font-size:12px;font-weight:500;color:#d8d8e4;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.band-list{display:none;position:absolute;z-index:100;background:#18181e;border:1px solid rgba(255,255,255,0.1);border-radius:10px;padding:6px;min-width:186px;margin-top:4px}
.band-list.vis{display:block}
.bl-item{display:flex;align-items:center;gap:8px;padding:7px 9px;border-radius:6px;cursor:pointer;transition:background .1s}
.bl-item:hover{background:rgba(255,255,255,0.06)}
.bl-item.on{background:rgba(232,162,42,0.08)}
.bl-iname{font-size:12px;color:rgba(255,255,255,0.62)}
.bl-role{font-size:10px;color:rgba(255,255,255,0.25);margin-left:auto}
.bl-add{border-top:1px solid rgba(255,255,255,0.06);margin-top:4px;padding-top:4px}
.sb-nav{flex:1;padding:10px 8px;overflow-y:auto}
.nav-sec{margin-bottom:18px}
.nav-sec-lbl{font-size:10px;color:rgba(255,255,255,0.2);text-transform:uppercase;letter-spacing:.7px;font-weight:500;padding:0 7px;margin-bottom:5px}
.ni{display:flex;align-items:center;gap:9px;padding:7px 9px;border-radius:7px;cursor:pointer;color:rgba(255,255,255,0.35);transition:all .12s;margin-bottom:1px;user-select:none}
.ni:hover{background:rgba(255,255,255,0.04);color:rgba(255,255,255,0.65)}
.ni.on{background:rgba(232,162,42,0.1);color:#e8a22a}
.ni-label{font-size:12px}
.ni-badge{font-size:9px;background:rgba(232,162,42,0.18);color:#e8a22a;padding:1px 5px;border-radius:8px;margin-left:auto}
.sb-foot{padding:10px 10px 14px;border-top:1px solid rgba(255,255,255,0.05)}
.me-row{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:8px;cursor:pointer;transition:background .12s}
.me-row:hover{background:rgba(255,255,255,0.04)}
.me-av{width:28px;height:28px;border-radius:50%;background:rgba(232,162,42,0.18);border:1.5px solid rgba(232,162,42,0.35);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;color:#e8a22a;flex-shrink:0}
.me-name{flex:1;font-size:12px;color:rgba(255,255,255,0.55);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.main{flex:1;overflow-y:auto;min-width:0}
.main::-webkit-scrollbar{width:4px}
.main::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.07);border-radius:2px}
.view{display:none;padding:22px 26px 32px}
.view.on{display:block}
.vtitle{font-size:17px;font-weight:500;color:#eeeef2;margin-bottom:4px}
.vsub{font-size:12px;color:rgba(255,255,255,0.3);margin-bottom:22px}
.vsep{height:1px;background:rgba(255,255,255,0.05);margin:20px 0}
.sec-title{font-size:11px;font-weight:500;color:rgba(255,255,255,0.28);text-transform:uppercase;letter-spacing:.7px;margin-bottom:12px}
.row{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:14px}
.row-left{flex:1;min-width:0}
.row-label{font-size:13px;color:rgba(255,255,255,0.68);margin-bottom:2px}
.row-hint{font-size:11px;color:rgba(255,255,255,0.25)}
.field{background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);border-radius:7px;padding:8px 11px;color:#e0e0e8;font-size:13px;outline:none;font-family:inherit;transition:border-color .15s;width:100%}
.field:focus{border-color:rgba(232,162,42,0.35);background:rgba(255,255,255,0.07)}
.field::placeholder{color:rgba(255,255,255,0.2)}
select.field{cursor:pointer}
.av-row{display:flex;align-items:center;gap:14px;margin-bottom:20px}
.av-big{width:56px;height:56px;border-radius:50%;background:rgba(232,162,42,0.18);border:2px solid rgba(232,162,42,0.35);display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:#e8a22a;flex-shrink:0;cursor:pointer;transition:all .12s}
.av-big:hover{border-color:rgba(232,162,42,0.6)}
.btn{padding:6px 13px;border-radius:6px;cursor:pointer;border:1px solid rgba(255,255,255,0.09);background:transparent;color:rgba(255,255,255,0.42);font-size:12px;font-family:inherit;transition:all .12s;white-space:nowrap}
.btn:hover{background:rgba(255,255,255,0.06);color:rgba(255,255,255,0.78)}
.btn.primary{background:rgba(232,162,42,0.14);border-color:rgba(232,162,42,0.28);color:#e8a22a}
.btn.primary:hover{background:rgba(232,162,42,0.22)}
.btn.danger{background:rgba(220,80,80,0.08);border-color:rgba(220,80,80,0.2);color:#e07070}
.btn.sm{padding:4px 10px;font-size:11px}
.toggle{width:36px;height:20px;background:rgba(255,255,255,0.1);border-radius:10px;cursor:pointer;position:relative;transition:background .2s;flex-shrink:0;border:none}
.toggle.on{background:#e8a22a}
.toggle::after{content:'';position:absolute;top:3px;left:3px;width:14px;height:14px;background:white;border-radius:50%;transition:transform .2s}
.toggle.on::after{transform:translateX(16px)}
.notif-row{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:1px solid rgba(255,255,255,0.04)}
.notif-row:last-child{border-bottom:none}
.member-card{display:flex;align-items:center;gap:10px;padding:10px 12px;background:rgba(255,255,255,0.025);border:1px solid rgba(255,255,255,0.05);border-radius:8px;margin-bottom:6px}
.mc-av{width:30px;height:30px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;flex-shrink:0}
.mc-info{flex:1;min-width:0}
.mc-name{font-size:13px;color:rgba(255,255,255,0.72)}
.mc-email{font-size:11px;color:rgba(255,255,255,0.25)}
.mc-role{font-size:11px;padding:2px 7px;border-radius:3px;background:rgba(255,255,255,0.07);color:rgba(255,255,255,0.38);white-space:nowrap}
.mc-role.admin{background:rgba(232,162,42,0.1);color:#e8a22a}
.invite-row{display:flex;gap:7px;margin-top:10px}
.invite-row .field{flex:1}
.storage-card{display:flex;align-items:center;gap:12px;padding:11px 14px;background:rgba(255,255,255,0.025);border:1px solid rgba(255,255,255,0.05);border-radius:9px;margin-bottom:7px}
.storage-card.connected{border-color:rgba(61,200,120,0.18)}
.sc-icon{width:34px;height:34px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0;border:1px solid rgba(255,255,255,0.07);background:rgba(255,255,255,0.04)}
.sc-info{flex:1;min-width:0}
.sc-name{font-size:13px;color:rgba(255,255,255,0.72)}
.sc-detail{font-size:11px;color:rgba(255,255,255,0.25);margin-top:1px}
.sc-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}
.sc-dot.on{background:#4dba85}
.sc-dot.off{background:rgba(255,255,255,0.15)}
.storage-add{display:flex;gap:7px;flex-wrap:wrap;margin-top:10px}
.add-chip{display:flex;align-items:center;gap:6px;padding:6px 11px;border-radius:7px;background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.07);cursor:pointer;color:rgba(255,255,255,0.35);font-size:12px;transition:all .12s}
.add-chip:hover{background:rgba(255,255,255,0.06);color:rgba(255,255,255,0.65)}
.danger-box{border:1px solid rgba(220,80,80,0.18);border-radius:9px;padding:14px 16px;background:rgba(220,80,80,0.04)}
.danger-title{font-size:13px;color:#e07070;margin-bottom:3px}
.danger-hint{font-size:11px;color:rgba(220,80,80,0.45);margin-bottom:10px}
</style>
</head>
<body>
<div class="app">
<div class="sb">
<div class="sb-band">
<div class="sb-band-lbl">Active Band</div>
<div style="position:relative">
<div class="band-sw" onclick="toggleBandList()"><div class="bav" style="background:rgba(232,162,42,0.15);color:#e8a22a">LH</div><span class="band-name">Loud Hands</span><svg style="opacity:.3" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 5l3 3 3-3"/></svg></div>
<div class="band-list" id="bandList">
<div class="bl-item on" onclick="switchBand('Loud Hands','LH','rgba(232,162,42,0.15)','#e8a22a',this)"><div class="bav" style="background:rgba(232,162,42,0.15);color:#e8a22a;width:22px;height:22px;font-size:9px;border-radius:5px">LH</div><span class="bl-iname">Loud Hands</span><span class="bl-role">Admin</span></div>
<div class="bl-item" onclick="switchBand('Static Moon','SM','rgba(91,156,240,0.15)','#7aabf0',this)"><div class="bav" style="background:rgba(91,156,240,0.15);color:#7aabf0;width:22px;height:22px;font-size:9px;border-radius:5px">SM</div><span class="bl-iname">Static Moon</span><span class="bl-role">Member</span></div>
<div class="bl-item" onclick="switchBand('The Haze','TH','rgba(200,90,180,0.15)','#d070c0',this)"><div class="bav" style="background:rgba(200,90,180,0.15);color:#d070c0;width:22px;height:22px;font-size:9px;border-radius:5px">TH</div><span class="bl-iname">The Haze</span><span class="bl-role">Member</span></div>
<div class="bl-add"><div class="bl-item"><span style="font-size:14px;opacity:.4;margin-right:2px">+</span><span class="bl-iname">Create new band</span></div></div>
</div>
</div>
</div>
<div class="sb-nav">
<div class="nav-sec">
<div class="nav-sec-lbl">Account</div>
<div class="ni on" data-v="profile"><svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor"><circle cx="7" cy="5" r="2.5"/><path d="M2 12c0-2.76 2.24-4 5-4s5 1.24 5 4"/></svg><span class="ni-label">Profile</span></div>
<div class="ni" data-v="notifs"><svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor"><path d="M7 1.5a4 4 0 0 1 4 4v2.5l1 1.5H2L3 8V5.5a4 4 0 0 1 4-4z"/><path d="M5.5 11.5a1.5 1.5 0 0 0 3 0"/></svg><span class="ni-label">Notifications</span></div>
<div class="ni" data-v="audio"><svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor"><rect x="2" y="4" width="2" height="6" rx="1"/><rect x="6" y="2" width="2" height="10" rx="1"/><rect x="10" y="5" width="2" height="4" rx="1"/></svg><span class="ni-label">Audio &amp; Playback</span></div>
</div>
<div class="nav-sec">
<div class="nav-sec-lbl" id="band-nav-lbl">Band — Loud Hands</div>
<div class="ni" data-v="band"><svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor"><path d="M1 11c0-2 1.5-3 3.5-3h5c2 0 3.5 1 3.5 3"/><circle cx="7" cy="5" r="2.5"/></svg><span class="ni-label">Members</span><span class="ni-badge">5</span></div>
<div class="ni" data-v="storage"><svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor"><rect x="1" y="3" width="12" height="3.5" rx="1.5"/><rect x="1" y="8" width="12" height="3.5" rx="1.5"/><circle cx="11" cy="4.75" r=".8" fill="#0b0b0e"/><circle cx="11" cy="9.75" r=".8" fill="#0b0b0e"/></svg><span class="ni-label">Storage</span></div>
<div class="ni" data-v="bandset"><svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.3"><circle cx="7" cy="7" r="2"/><path d="M7 1v1.5M7 11.5V13M1 7h1.5M11.5 7H13"/></svg><span class="ni-label">Band Settings</span></div>
</div>
</div>
<div class="sb-foot"><div class="me-row" onclick="sv('profile')"><div class="me-av">ST</div><span class="me-name">Steffen</span></div></div>
</div>
<div class="main">
<!-- PROFILE -->
<div class="view on" id="view-profile">
<div class="vtitle">Profile</div><div class="vsub">How you appear to your bandmates across all bands</div>
<div class="av-row"><div class="av-big">ST</div><div style="display:flex;flex-direction:column;gap:5px"><button class="btn sm primary">Upload photo</button><button class="btn sm">Remove</button></div></div>
<div class="sec-title">Display</div>
<div class="row"><div class="row-left"><div class="row-label">Display name</div><div class="row-hint">Shown to all bands you're in</div></div></div>
<input class="field" style="margin-bottom:14px" type="text" value="Steffen">
<div class="row"><div class="row-left"><div class="row-label">Instrument / role</div><div class="row-hint">Shown on your comments and pins</div></div></div>
<input class="field" style="margin-bottom:14px" type="text" value="Guitar, Production">
<div class="row"><div class="row-left"><div class="row-label">Bio</div></div></div>
<textarea class="field" style="height:62px;resize:none;margin-bottom:14px" placeholder="A few words about you…"></textarea>
<div class="vsep"></div>
<div class="sec-title">Account</div>
<div class="row"><div class="row-left"><div class="row-label">Email</div></div></div>
<input class="field" style="margin-bottom:14px" type="email" value="steffen@example.com">
<div class="row"><div class="row-left"><div class="row-label">Password</div></div><button class="btn sm">Change password</button></div>
<div class="vsep"></div>
<div class="sec-title">Danger zone</div>
<div class="danger-box"><div class="danger-title">Delete account</div><div class="danger-hint">Removes you from all bands and permanently deletes your comments. Storage files are NOT deleted.</div><button class="btn danger sm">Delete my account</button></div>
</div>
<!-- NOTIFICATIONS -->
<div class="view" id="view-notifs">
<div class="vtitle">Notifications</div><div class="vsub">Choose what pulls you back into the app</div>
<div class="sec-title">Email</div>
<div class="notif-row"><div class="row-left"><div class="row-label">New comment on a recording</div><div class="row-hint">Someone comments on a recording you've interacted with</div></div><button class="toggle on" onclick="this.classList.toggle('on')"></button></div>
<div class="notif-row"><div class="row-left"><div class="row-label">Mentions</div><div class="row-hint">Someone @mentions you in a comment</div></div><button class="toggle on" onclick="this.classList.toggle('on')"></button></div>
<div class="notif-row"><div class="row-left"><div class="row-label">New recording uploaded</div><div class="row-hint">A bandmate uploads to a shared session</div></div><button class="toggle" onclick="this.classList.toggle('on')"></button></div>
<div class="notif-row"><div class="row-left"><div class="row-label">Band invite</div></div><button class="toggle on" onclick="this.classList.toggle('on')"></button></div>
<div class="vsep"></div>
<div class="sec-title">In-app</div>
<div class="notif-row"><div class="row-left"><div class="row-label">Replies to my comments</div></div><button class="toggle on" onclick="this.classList.toggle('on')"></button></div>
<div class="notif-row"><div class="row-left"><div class="row-label">Daily session activity digest</div></div><button class="toggle" onclick="this.classList.toggle('on')"></button></div>
<div class="notif-row"><div class="row-left"><div class="row-label">Nearby comment placed</div><div class="row-hint">When someone comments within 5s of your timestamp</div></div><button class="toggle on" onclick="this.classList.toggle('on')"></button></div>
</div>
<!-- AUDIO -->
<div class="view" id="view-audio">
<div class="vtitle">Audio &amp; Playback</div><div class="vsub">Defaults for how recordings play back</div>
<div class="sec-title">Playback</div>
<div class="row"><div class="row-left"><div class="row-label">Default playback speed</div></div><select class="field" style="width:100px"><option>0.75×</option><option selected>1×</option><option>1.25×</option><option>1.5×</option><option>2×</option></select></div>
<div class="row"><div class="row-left"><div class="row-label">Skip interval</div><div class="row-hint">Amount skipped with ±30s buttons</div></div><select class="field" style="width:100px"><option>10s</option><option selected>30s</option><option>60s</option></select></div>
<div class="notif-row"><div class="row-left"><div class="row-label">Auto-advance to next track</div></div><button class="toggle on" onclick="this.classList.toggle('on')"></button></div>
<div class="notif-row"><div class="row-left"><div class="row-label">Show waveform avatar pins</div></div><button class="toggle on" onclick="this.classList.toggle('on')"></button></div>
<div class="notif-row"><div class="row-left"><div class="row-label">Pause when adding a comment</div></div><button class="toggle" onclick="this.classList.toggle('on')"></button></div>
<div class="vsep"></div>
<div class="sec-title">Waveform colour</div>
<div style="display:flex;gap:7px;margin-top:4px"><div style="width:22px;height:22px;border-radius:50%;background:#e8a22a;cursor:pointer;border:2px solid white"></div><div style="width:22px;height:22px;border-radius:50%;background:#7aabf0;cursor:pointer"></div><div style="width:22px;height:22px;border-radius:50%;background:#4dba85;cursor:pointer"></div><div style="width:22px;height:22px;border-radius:50%;background:#d070c0;cursor:pointer"></div></div>
</div>
<!-- MEMBERS -->
<div class="view" id="view-band">
<div class="vtitle" id="v-band-title">Members · Loud Hands</div><div class="vsub">Manage who has access to this band's recordings</div>
<div class="member-card"><div class="mc-av" style="background:rgba(232,162,42,0.18);color:#e8a22a">ST</div><div class="mc-info"><div class="mc-name">Steffen (you)</div><div class="mc-email">steffen@example.com · Guitar</div></div><span class="mc-role admin">Admin</span></div>
<div class="member-card"><div class="mc-av" style="background:rgba(91,156,240,0.18);color:#7aabf0">AL</div><div class="mc-info"><div class="mc-name">Alex</div><div class="mc-email">alex@example.com · Bass</div></div><span class="mc-role admin">Admin</span><button class="btn sm" style="margin-left:6px">···</button></div>
<div class="member-card"><div class="mc-av" style="background:rgba(200,90,180,0.18);color:#d070c0">MJ</div><div class="mc-info"><div class="mc-name">Maya</div><div class="mc-email">maya@example.com · Vocals</div></div><span class="mc-role">Member</span><button class="btn sm" style="margin-left:6px">···</button></div>
<div class="member-card"><div class="mc-av" style="background:rgba(61,200,120,0.18);color:#4dba85">JD</div><div class="mc-info"><div class="mc-name">Jordan</div><div class="mc-email">jordan@example.com · Drums</div></div><span class="mc-role">Member</span><button class="btn sm" style="margin-left:6px">···</button></div>
<div class="member-card"><div class="mc-av" style="background:rgba(140,90,220,0.18);color:#a878e8">KL</div><div class="mc-info"><div class="mc-name">Kim</div><div class="mc-email">kim@example.com · Keys</div></div><span class="mc-role">Member</span><button class="btn sm" style="margin-left:6px">···</button></div>
<div class="vsep"></div>
<div class="sec-title">Invite someone</div>
<div class="invite-row"><input class="field" type="email" placeholder="name@email.com"><select class="field" style="width:110px"><option>Member</option><option>Admin</option></select><button class="btn primary">Send invite</button></div>
<div style="font-size:11px;color:rgba(255,255,255,0.2);margin-top:8px">No account needed to accept the invite.</div>
<div class="vsep"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
<div style="padding:10px 12px;background:rgba(255,255,255,0.025);border:1px solid rgba(255,255,255,0.05);border-radius:8px"><div style="font-size:12px;color:#e8a22a;margin-bottom:4px">Admin</div><div style="font-size:11px;color:rgba(255,255,255,0.28);line-height:1.55">Upload, delete, manage members and storage</div></div>
<div style="padding:10px 12px;background:rgba(255,255,255,0.025);border:1px solid rgba(255,255,255,0.05);border-radius:8px"><div style="font-size:12px;color:rgba(255,255,255,0.55);margin-bottom:4px">Member</div><div style="font-size:11px;color:rgba(255,255,255,0.28);line-height:1.55">Listen, comment, annotate — no upload or management</div></div>
</div>
</div>
<!-- STORAGE -->
<div class="view" id="view-storage">
<div class="vtitle" id="v-storage-title">Storage · Loud Hands</div><div class="vsub">RehearsalHub reads your recordings directly — files are never copied to our servers.</div>
<div class="sec-title">Connected locations</div>
<div class="storage-card connected"><div class="sc-icon">📁</div><div class="sc-info"><div class="sc-name">Google Drive</div><div class="sc-detail">loud-hands-rehearsals/ · 14.2 GB · steffen@gmail.com</div></div><div class="sc-dot on"></div><button class="btn sm">Manage</button></div>
<div class="storage-card connected"><div class="sc-icon">☁️</div><div class="sc-info"><div class="sc-name">Nextcloud</div><div class="sc-detail">rehearsals.loudhandsnc.de/Music/Sessions · 8.1 GB</div></div><div class="sc-dot on"></div><button class="btn sm">Manage</button></div>
<div class="storage-card"><div class="sc-icon">🔷</div><div class="sc-info"><div class="sc-name">OneDrive</div><div class="sc-detail">Not connected</div></div><div class="sc-dot off"></div><button class="btn sm primary">Connect</button></div>
<div class="vsep"></div>
<div class="sec-title">Add storage location</div>
<div class="storage-add"><div class="add-chip">📁 Google Drive</div><div class="add-chip">🔷 OneDrive</div><div class="add-chip">📦 Dropbox</div><div class="add-chip">☁️ Nextcloud</div><div class="add-chip">🪣 S3 / Compatible</div><div class="add-chip">📂 Local folder</div></div>
<div class="vsep"></div>
<div class="sec-title">Upload behaviour</div>
<div class="row"><div class="row-left"><div class="row-label">Default upload destination</div></div><select class="field" style="width:140px"><option>Google Drive</option><option>Nextcloud</option></select></div>
<div class="notif-row"><div class="row-left"><div class="row-label">Auto-organise by date</div><div class="row-hint">Group files into YYYY-MM-DD folders</div></div><button class="toggle on" onclick="this.classList.toggle('on')"></button></div>
</div>
<!-- BAND SETTINGS -->
<div class="view" id="view-bandset">
<div class="vtitle" id="v-bandset-title">Band Settings · Loud Hands</div><div class="vsub">Only admins can change these.</div>
<div class="av-row"><div style="width:52px;height:52px;border-radius:10px;background:rgba(232,162,42,0.15);color:#e8a22a;border:2px solid rgba(232,162,42,0.3);display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;cursor:pointer">LH</div><div style="display:flex;flex-direction:column;gap:5px"><button class="btn sm primary">Upload logo</button><button class="btn sm">Use initials</button></div></div>
<div class="sec-title">Identity</div>
<input class="field" style="margin-bottom:14px" type="text" value="Loud Hands">
<input class="field" style="margin-bottom:14px" type="text" value="Post-rock · Vienna" placeholder="Genre, location…">
<div class="vsep"></div>
<div class="sec-title">Privacy</div>
<div class="notif-row"><div class="row-left"><div class="row-label">Recordings visible to members only</div><div class="row-hint">Shared links require login</div></div><button class="toggle on" onclick="this.classList.toggle('on')"></button></div>
<div class="notif-row"><div class="row-left"><div class="row-label">Allow members to invite others</div></div><button class="toggle" onclick="this.classList.toggle('on')"></button></div>
<div class="notif-row"><div class="row-left"><div class="row-label">Public band page</div><div class="row-hint">Shows band name and session count only</div></div><button class="toggle" onclick="this.classList.toggle('on')"></button></div>
<div class="vsep"></div>
<div class="danger-box"><div class="danger-title">Delete this band</div><div class="danger-hint">Removes members, deletes comments. Storage files are NOT deleted.</div><button class="btn danger sm">Delete band</button></div>
</div>
</div>
</div>
<script>
function sv(name){document.querySelectorAll('.view').forEach(function(v){v.classList.remove('on');});document.querySelectorAll('.ni').forEach(function(n){n.classList.remove('on');});var el=document.getElementById('view-'+name);if(el)el.classList.add('on');var ni=document.querySelector('.ni[data-v="'+name+'"]');if(ni)ni.classList.add('on');}
document.querySelectorAll('.ni').forEach(function(n){n.addEventListener('click',function(){sv(this.dataset.v);});});
var bandOpen=false;
function toggleBandList(){bandOpen=!bandOpen;document.getElementById('bandList').classList.toggle('vis',bandOpen);}
document.addEventListener('click',function(e){if(!e.target.closest('.sb-band')){bandOpen=false;document.getElementById('bandList').classList.remove('vis');}});
function switchBand(name,initials,bg,color,item){
document.querySelector('.band-name').textContent=name;
var bav=document.querySelector('.band-sw .bav');bav.style.background=bg;bav.style.color=color;bav.textContent=initials;
document.querySelectorAll('.bl-item').forEach(function(b){b.classList.remove('on');});item.classList.add('on');
document.getElementById('band-nav-lbl').textContent='Band — '+name;
['v-band-title','v-storage-title','v-bandset-title'].forEach(function(id){var el=document.getElementById(id);if(el)el.textContent=el.textContent.replace(/·.*/,'· '+name);});
bandOpen=false;document.getElementById('bandList').classList.remove('vis');
}
</script>
</body>
</html>
```
---
## Component Checklist for Implementation
Use this to track what has been designed vs what needs building:
| Component | Designed | Notes |
|---|---|---|
| App shell (sidebar + main) | ✅ | See Mockup A |
| Band switcher dropdown | ✅ | See Mockup C |
| Library view | ✅ | Date-grouped, filter pills |
| Player — waveform | ✅ | Canvas-rendered, amber playhead |
| Player — avatar pins | ✅ | Per-member colour, hover tooltips |
| Player — comment compose | ✅ | Auto-timestamp pill, tag buttons |
| Player — comment list | ✅ | Click timestamp to seek |
| Player — transport | ✅ | Speed selector, skip, volume |
| Discover / search | ✅ | Results grouped by session |
| Settings — Profile | ✅ | Avatar, name, instrument, email |
| Settings — Notifications | ✅ | Email + in-app toggles |
| Settings — Audio & Playback | ✅ | Speed, skip interval, toggles |
| Settings — Members | ✅ | List, invite form, role cards |
| Settings — Storage | ✅ | Provider cards, add chips |
| Settings — Band Settings | ✅ | Identity, privacy, danger zone |
| Range annotations (waveform drag) | ⬜ | See Open Design Decisions |
| Comment clustering / overlap | ⬜ | See Open Design Decisions |
| Mobile layout | ⬜ | Major rethink needed |
| Keyboard shortcuts | ⬜ | See Open Design Decisions |
| Session rename flow | ⬜ | Trigger on upload |
| Invite link (vs email) | ⬜ | See Open Design Decisions |