diff --git a/app/app/components/chord-chart.tsx b/app/app/components/chord-chart.tsx index 7d50539..a81a722 100644 --- a/app/app/components/chord-chart.tsx +++ b/app/app/components/chord-chart.tsx @@ -1,4 +1,8 @@ -import type { Section } from "~/lib/types"; +import type { LyricLine, Section } from "~/lib/types"; + +// Max characters per rendered line. 38 fits comfortably on a 375px phone +// at 14px monospace (≈8.4px per char with padding). +const MAX_WIDTH = 38; interface Props { sections: Section[]; @@ -13,26 +17,71 @@ function buildChordRow(chords: { offset: number; chord: string }[]): string { return row; } +/** Split one LyricLine into segments that each fit within maxWidth characters. */ +function segmentLine(line: LyricLine, maxWidth: number): LyricLine[] { + const { text, chords } = line; + + if (text.length <= maxWidth) return [line]; + + const segments: LyricLine[] = []; + let start = 0; + + while (start < text.length) { + let end = start + maxWidth; + + if (end < text.length) { + // Break at last space before maxWidth to avoid splitting mid-word + const breakAt = text.lastIndexOf(" ", end); + if (breakAt > start) end = breakAt + 1; + } else { + end = text.length; + } + + const segText = text.slice(start, end).trimEnd(); + + // Include chords whose offset falls within this segment; re-map offset + const segChords = chords + .filter((cp) => cp.offset >= start && cp.offset < end) + .map((cp) => ({ ...cp, offset: cp.offset - start })); + + segments.push({ text: segText, chords: segChords }); + + // Advance past the break point, skipping leading spaces for next segment + start = end; + while (start < text.length && text[start] === " ") start++; + } + + return segments; +} + +function LineBlock({ line }: { line: LyricLine }) { + return ( +
+ {buildChordRow(line.chords)}
+
+ )}
+ {line.text && (
+
+ {line.text}
+
+ )}
+ [{section.label}]
)} - {section.lines.map((line, i) => ( -
- {buildChordRow(line.chords)}
-
- )}
- {line.text && (
-
- {line.text}
-
- )}
-