diff --git a/.gitignore b/.gitignore
index 0b745e2..b014bd0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/target
-.env
\ No newline at end of file
+.env
+.superpowers/
\ No newline at end of file
diff --git a/docs/superpowers/specs/2026-04-09-chord-diagram-design.md b/docs/superpowers/specs/2026-04-09-chord-diagram-design.md
new file mode 100644
index 0000000..b2a996c
--- /dev/null
+++ b/docs/superpowers/specs/2026-04-09-chord-diagram-design.md
@@ -0,0 +1,134 @@
+# Chord Diagram Feature Design
+
+**Date:** 2026-04-09
+
+## Overview
+
+A chord diagram feature for the song detail page that shows users how to play each chord on piano or guitar. The core component is dumb — it receives only a chord name string and renders the diagram. All music theory and voicing logic lives in a separate library layer.
+
+## Architecture
+
+Three cleanly separated layers:
+
+```
+chord name string ("Cmaj7")
+ │
+ ▼
+ [theory layer] — tonal parses name → root + note set {C, E, G, B}
+ │
+ ▼
+ [voicing layer] — maps note set → renderable positions
+ ├── Piano: note set → highlight keys on 1-octave keyboard
+ └── Guitar: chord quality + root → transpose moveable shape template
+ │
+ ▼
+ [render layer] — dumb components, no music theory knowledge
+ ├──
+ └──
+```
+
+## Files
+
+```
+app/app/
+ lib/
+ chord-voicing.ts # theory layer: tonal → notes + guitar voicing
+ guitar-voicings.ts # data: ~25 quality templates
+ components/
+ chord-diagram/
+ piano-keys.tsx # dumb renderer: string[] of note names → keyboard
+ guitar-fretboard.tsx # dumb renderer: frets[] + baseFret → fretboard grid
+ chord-diagram.tsx # entry point: chord+instrument → voicing → renderer
+ chord-grid.tsx # wrapped grid of ChordDiagram cards for all song chords
+```
+
+## Component API
+
+### ``
+
+```tsx
+
+
+```
+
+Renders nothing (graceful empty) if the chord cannot be parsed or has no voicing.
+
+### ``
+
+```tsx
+
+```
+
+Owns the `instrument` state (`"piano" | "guitar"`), persisted to `localStorage` as `chordDiagramInstrument`. Renders a global piano/guitar toggle and a `flex-wrap` grid of `` cards.
+
+## Diagram Styles
+
+- **Piano:** dot notation — white keys with filled circles on pressed keys, black keys overlaid. 1 fixed octave shown (C to B); notes are matched by name regardless of octave.
+- **Guitar:** standard vertical fretboard — nut at top, 4 frets shown, dots on finger positions, O/X above strings for open/muted. Barre indicator where applicable.
+
+## Theory Layer (`chord-voicing.ts`)
+
+Uses `@tonaljs/tonal` (already in npm, tree-shakeable):
+
+```ts
+export function getPianoNotes(chord: string): string[]
+// "Cmaj7" → ["C", "E", "G", "B"]
+// Returns [] if unparseable
+
+export function getGuitarVoicing(chord: string): GuitarVoicing | null
+// "Am" → { frets: [0,0,2,2,1,0], baseFret: 1, barre: null }
+// Returns null if quality not in voicing map
+```
+
+## Guitar Voicing Data (`guitar-voicings.ts`)
+
+~25 moveable barre-chord templates keyed by `tonal` chord type name. Each template is a barre shape (no open strings) so it can be transposed by shifting `baseFret`. Two shape families are used: E-shapes (root on 6th string) and A-shapes (root on 5th string). `baseFret` is computed as the semitone distance from the template shape's root string pitch (E or A) to the target chord root.
+
+```ts
+interface GuitarVoicingTemplate {
+ frets: (number | null)[] // 6 strings; null = muted; fret numbers relative to baseFret
+ baseFret: number // 1 in template; shifted when transposing to target root
+ barre: number | null // fret (relative to baseFret) to draw barre, or null
+ rootString: 'E' | 'A' // which string carries the root (determines transposition offset)
+}
+```
+
+Quality names match `tonal`'s `Chord.get(name).type` output (e.g. `"major"`, `"minor"`, `"major seventh"`, `"dominant seventh"`, `"minor seventh"`, `"diminished"`, `"augmented"`, `"suspended fourth"`, `"suspended second"`, `"half-diminished"`, `"dominant seventh flat five"`, etc.). ~25 entries total.
+
+If `tonal` returns a quality name not in the map, `getGuitarVoicing` returns `null` and the diagram renders a "no guitar voicing" placeholder.
+
+## Layout & Integration
+
+### Breakpoint
+
+`lg` (Tailwind) divides mobile from desktop layout.
+
+### Mobile
+
+- Below `lg`: lyrics and diagrams in a single column.
+- **Inline popup:** tapping a chord name in `chord-chart.tsx` sets `activeChord` state in `songs.$id.tsx`. An inline `` panel appears immediately below the tapped line. It closes when the scroll container fires a `scroll` event.
+- **Bottom grid:** `` rendered after `` in the scroll column. Not sticky — scrolls with content.
+
+### Desktop
+
+- At `lg` and above: `songs.$id.tsx` switches to a two-column layout.
+- Left column: `` (existing).
+- Right column: `` showing all unique chords in the song, wrapped. No inline popup on desktop (side column is always visible).
+
+### Chord list source
+
+`songs.$id.tsx` derives `uniqueChords: string[]` from `displayed.sections` — all unique chord names in order of first appearance, deduplicated. This list is passed to `` and also used to determine which chord names in `` are tappable.
+
+### Instrument toggle
+
+Global piano/guitar toggle lives in ``. State persisted to `localStorage` as `chordDiagramInstrument`. Switching updates all visible diagrams at once.
+
+## Error / Unknown Chord Handling
+
+- `getPianoNotes` returns `[]` → `` renders with no dots highlighted and a subtle "?" label.
+- `getGuitarVoicing` returns `null` → `` renders an empty fretboard with a "no voicing" label.
+- Unparseable chord name (garbage string) → same fallback as above.
+
+## Dependency
+
+Add `tonal` to `app/package.json`. It is tree-shakeable; only chord parsing and note utilities will be bundled.