# MixBoard Design System ## Design Intent & Feel **This app should feel like a well-organized DJ booth** — everything within reach, nothing hidden, both decks visible at once. Pro density with craft and care. ### Feel Attributes 1. **Professional** — information-dense, respects the user's expertise 2. **Unhurried** — generous padding, no cramped toolbars, content breathes 3. **Grounded** — playlist is always visible, never yanked away by navigation 4. **Responsive** — immediate feedback on interactions, micro-animations on state changes 5. **Tactile** — hover states, spatial grouping, controls feel like physical objects ### Anti-Goals - NOT a streaming app (no oversized artwork cards dominating the screen) - NOT a spreadsheet (no raw data grids without typographic hierarchy) - NOT retro-first (skins change colors, not layout structure) ### Motion Preferences - Panel slide: 200ms ease-out - State transitions (play/pause, selection): 150ms ease-in-out - Toast messages: 300ms ease-in-out with opacity - Reduced-motion: all animations replaced with instant cuts ## Token Registry (Designer-managed) ### Spacing | Token | Value | Usage | |-------|-------|-------| | `spacing.xs` | 4pt | Inline icon gaps | | `spacing.sm` | 8pt | Between related elements | | `spacing.md` | 12pt | Section padding, control groups | | `spacing.lg` | 16pt | Panel margins, major separators | | `spacing.xl` | 24pt | Section breaks, panel insets | ### Typography | Token | Spec | Usage | |-------|------|-------| | `type.trackTitle` | .system(size: 13, weight: .medium) | Track title in list | | `type.trackMeta` | .system(size: 11) + .secondary | Artist, album in list | | `type.dataPill` | .system(size: 10, design: .monospaced) | BPM, key, duration | | `type.sectionHeader` | .system(size: 11, weight: .semibold) + .tertiary | UPPERCASE sidebar/list sections | | `type.playerTitle` | .system(size: 13, weight: .semibold) | Now-playing track in player bar | | `type.playerArtist` | .system(size: 11) + .secondary | Now-playing artist in player bar | | `type.time` | .system(size: 11, design: .monospaced) | Elapsed / remaining time | ### Sizing | Token | Value | Usage | |-------|-------|-------| | `playerBar.height` | 64pt | Bottom player bar | | `playerBar.artwork` | 44pt | Artwork thumbnail in player bar | | `waveform.height` | 48pt | Waveform display | | `browsePanel.minWidth` | 280pt | Cloud browser panel minimum | | `browsePanel.idealWidth` | 340pt | Cloud browser panel default | | `browsePanel.maxWidth` | 420pt | Cloud browser panel maximum | | `trackRow.height` | 28pt | Playlist track row | | `sidebar.minWidth` | 180pt | Sidebar minimum | ### Corner Radii | Token | Value | Usage | |-------|-------|-------| | `radius.sm` | 4pt | Buttons, pills, metadata badges | | `radius.md` | 8pt | Cards, panels, popovers | | `radius.lg` | 12pt | Artwork, modal sheets | ### Colors Managed by AppTheme skin system. No hardcoded colors in views — always reference `theme.*` properties. ## Navigation & Flow ### Layout Architecture Three-region layout with slide-out panel: ``` ┌─────────┬──────────────────────────┬──────────────────┐ │ Sidebar │ Detail (always playlist) │ Browse Panel │ │ 180-300 │ (flex) │ 280-420 (toggle) │ │ │ │ │ │ │ │ │ │ ├──────────────────────────┴──────────────────┤ │ │ Player Bar (64pt, full width) │ └─────────┴─────────────────────────────────────────────┘ ``` ### Panel Behavior - **Primary toggle**: `cloud.fill` button in playlist header toolbar, **rightmost position**, larger than other toolbar buttons (20pt icon, prominent style) - **No sidebar Cloud entry** — removed entirely. Panel is toggled only from the toolbar button or ⌘B. - **Sidebar "Queue" click** → opens browse panel with Queue tab selected (kept in sidebar) - **⌘B** → toggles browse panel - **Panel persists state** — scroll position, navigation depth preserved when hidden - **Small windows (<1200px)** → panel overlays as sheet instead of inline - **Panel slides** from trailing edge, 200ms ease-out ### Chad Music Icon - **Toolbar button**: `cloud.fill` SF Symbol at 20pt, accent-colored when panel is open, secondary when closed - **Boar asset** (`chadmusic-boar`): kept in Assets.xcassets for future use (e.g. panel header branding), not used in toolbar - **Toolbar button style**: Larger than neighboring buttons (Add, Group, Settings, Export). Should visually stand out — use `.controlSize(.regular)` or explicit frame while other buttons use `.controlSize(.small)`. ### Sidebar Decisions - **No "Chad Music" row** — removed from sidebar. Cloud browsing is accessed via toolbar button only. - Clicking a playlist always shows it in the detail area - Active sidebar item gets rounded-rect highlight background ### Player Bar Decisions - No vertical dividers — spatial grouping only - Three zones: Transport (left) | Track Info with artwork (center, flex) | Time + Volume (right) - Waveform sits above player bar at 48pt height - Small artwork thumbnail (44pt) in player bar when track is playing ## Decision Log (append-only) ### 2026-03-17 — UI Revamp Direction - **Options**: (A) Modern Streaming, (B) Pro Player Refined, (C) Hybrid density modes - **Choice**: B — Pro Player, Refined - **Reason**: MixBoard is a music tool (mix targets, cue points, BPM, DAW export). Stripping density would betray power users. Instead: better typography, spatial hierarchy, subtle animation, eliminate visual noise. - **Learned pattern**: Density is the feature, not the problem. The craft applied to the density is what was missing. ### 2026-03-17 — Cloud Browser Navigation - **Options**: (1) Tabs at top of detail, (2) Slide-out right panel, (3) Dual split panes - **Choice**: 2 — Slide-out browse panel - **Reason**: The browse workflow is asymmetric — user spends most time in playlists, occasionally dips into cloud. A panel respects this asymmetry. Playlist never disappears. Direct drag-and-drop between panel and playlist. - **Learned pattern**: Asymmetric workflows need asymmetric layouts. Don't give equal space to unequal tasks. ## Compromises (None yet) ### 2026-03-18 — Panel Toggle Spatial Fix - **Problem**: Sidebar button (left) opens panel (right) — spatially disconnected, unintuitive - **Options**: (A) Toggle button in playlist header toolbar right-aligned, (B) Move panel to left side, (C) Animation cue from sidebar to panel - **Choice**: A — Boar icon button in playlist toolbar, right-aligned - **Reason**: Trigger and effect become spatially adjacent. Sidebar "Chad Music" stays as secondary entry point for discovery. Button uses кабанчик (boar) logo matching Chad Music branding. - **Learned pattern**: Trigger and effect must be spatially connected. If UI action X opens panel Y, the button for X should be adjacent to Y. ### 2026-03-18 — Chad Music Icon - **Choice**: Replace `cloud.fill` with custom кабанчик (boar) image asset - **Reason**: Brand identity — Chad Music has its own logo, generic cloud icon doesn't convey what it is - **Revised 2026-03-18**: Reverted to `cloud.fill` for toolbar button. Boar asset kept for future panel branding. ### 2026-03-18 — Remove Sidebar Cloud Entry - **Choice**: Remove "Chad Music" from sidebar entirely. Panel toggle lives only in playlist toolbar (rightmost, larger button) + ⌘B. - **Reason**: Sidebar button was spatially disconnected from the panel (left→right). Having the toggle in the toolbar right next to where the panel appears is more intuitive. Also fixes the SwiftUI List deselection bug (clicking non-tagged rows inside `List(selection:)` clears `selectedPlaylist`). - **Learned pattern**: If a UI element's only purpose is toggling a panel, put the toggle where the panel is — not in a navigation list. ## Backlog ### Drop cloud tracks between playlist rows (positional insert) - **Current**: Cloud tracks/albums can only be dropped onto the playlist header bar — appends to end - **Wanted**: Drop between specific tracks in the track list to insert at a position (like reordering, but from an external source) - **Interaction**: Drag from Cloud panel → hover between rows in playlist → drop indicator line appears → release inserts at that position - **Complexity**: Medium — requires per-row drop targets with insertion index calculation, not just a single playlist-level drop zone