diff --git a/src/components/TryThis.jsx b/src/components/TryThis.jsx
index 90a9c28..4ef167a 100644
--- a/src/components/TryThis.jsx
+++ b/src/components/TryThis.jsx
@@ -5,16 +5,23 @@ import { pianoVoicing } from '../lib/piano'
import ChordDiagram from './ChordDiagram'
import MiniPiano from './MiniPiano'
-// ─── TryThis — side-by-side substitution cards, each with a mini diagram (L-75) ─
+// ─── TryThis — stable 3-slot rows-of-shapes substitution rail (L-78 / D-76) ─────
//
-// For the chord under the playhead, in the detected key, show ALL curated
-// substitutions (from theory.js `suggestSubstitutions`, the L-73 engine) SIDE BY
-// SIDE — one card per idea — each carrying a mini instrument diagram of that
-// chord (guitar ChordDiagram / piano MiniPiano / bass root caption), following
-// the global instrument. Reverses L-74's rotating one-at-a-time card: the user
-// saw the rotation and asked for all-visible ("put them next to each other, we
-// have enough space"). Spec: docs/design/related-area-layout.md §1–3.
-// Sibling of RelatedProgressions; same micro-header + chip idiom.
+// For the chord under the playhead, in the detected key, show up to THREE curated
+// substitutions (from theory.js `suggestSubstitutions`, the L-73 engine) as
+// stacked ROWS — turned 90° from L-75's cards-across. Each row = a left identity
+// block (tappable chord chip + category tag + why) and a right "ways to play it"
+// block that follows the global instrument:
+// guitar → up to 3 ChordDiagram thumbs (the 3×3 grid), the genuinely different
+// grips from getGuitarVoicings(label).slice(0, 3)
+// piano → ONE MiniPiano ("for piano it can be just one thats okay")
+// bass → a root · {note} caption (no honest compact bass-chord renderer)
+//
+// The layout is a FIXED 3-slot frame: present subs render SubRow, absent slots
+// render a subtle EmptySlot that holds the exact row height — so sub #1 and #2
+// never shift position whether the chord yields 2 or 3 subs (the user's anti-jump
+// ask: "annoying when the layout changes then u dont know where to look").
+// Spec: docs/design/related-area-v2.md §2. Sibling of RelatedProgressions.
//
// Props:
// loop : string[] | null — the detected repeating progression (chord names)
@@ -54,6 +61,14 @@ const CATEGORY_TAG = {
secondary_dominant: 'V7',
}
+// Per-instrument uniform row height (§2.2/§3). All 3 slots — filled or empty —
+// share the current instrument's height so the frame never reflows.
+const ROW_MIN_H = {
+ guitar: 'min-h-[100px]',
+ piano: 'min-h-[72px]',
+ bass: 'min-h-[64px]',
+}
+
// Compute the substitution set for a chord NAME at loop index `pos` (−1 when the
// chord is not a loop station). The next station's root pc gates Rule D (secondary
// dominant of the next chord). Returns { name, pos, subs } or null when the name
@@ -89,22 +104,26 @@ function pickSubject(loopArr, keyInfo, currentChord) {
return null
}
-// The mini diagram drawn inside a SubCard, chosen by the global instrument (§2):
-// guitar → first resolved guitar shape (absolute frets) via ChordDiagram thumb;
-// no shape available → no diagram (honest, chip + why only).
-// piano → pianoVoicing for the chord, rootPc spread back in so the "R" badge
-// lands correctly (VoicingBrowser:317-319 caveat), MiniPiano size mini.
-// bass → no diagram (no honest compact bass-chord renderer); a small
-// root · {note} caption instead.
-function SubDiagram({ sub, instrument }) {
+// The "ways to play" block on the right of a SubRow, chosen by the global
+// instrument (§2.3):
+// guitar → up to 3 ChordDiagram thumbs (the 3×3), each captioned with its shape
+// name so the three read as genuinely different grips. Fewer than 3
+// shapes exist (e.g. add9 → 2 for most roots) → show what exists, never
+// pad with fakes (honest, §2.4). None → nothing (chip + why carry it).
+// piano → ONE MiniPiano; rootPc spread back in so the "R" badge lands right.
+// bass → root · {note} caption (no honest compact bass-chord renderer).
+function WaysToPlay({ sub, instrument }) {
if (instrument === 'piano') {
return (
-
+ {sub.why} +
- {sub.why} -
+// A reserved-but-empty slot — holds the exact SubRow height so the present subs +// never move when the chord yields fewer than 3 (§2.2, the anti-jump). +function EmptySlot({ instrument }) { + return ( + ) } @@ -157,16 +208,7 @@ export default function TryThis({ loop, keyInfo, currentChord, onChordClick, ins if (!subject) return null const subjectChord = subject.name - const subs = subject.subs - - // Piano keyboards are up to ~199px wide (C-anchored window), so 4 cannot share a - // 720px row — lay them out 2×2. Guitar (75px cell) and bass (no diagram) sit - // 4-across in one flex-wrap row; with 1–3 subs the cards grow to fill (§1.1/§3). - const isPiano = instrument === 'piano' - const listClass = isPiano - ? 'grid grid-cols-1 sm:grid-cols-2 gap-2' - : 'flex flex-wrap gap-2' - const cardBasis = isPiano ? '' : 'basis-[168px] grow min-w-[152px]' + const subs = subject.subs.slice(0, 3) // cap at 3 (§2.1) return (