diff --git a/src/renderer/components/Toolbar/AnalysisToolbar.tsx b/src/renderer/components/Toolbar/AnalysisToolbar.tsx index 6e8d076..7e58227 100644 --- a/src/renderer/components/Toolbar/AnalysisToolbar.tsx +++ b/src/renderer/components/Toolbar/AnalysisToolbar.tsx @@ -435,24 +435,147 @@ export function AnalysisToolbar(): JSX.Element { ≡ {activeFilePath && paragraphRhythm.length > 0 && ( - + <> + + {rhythmOpen && rhythmButtonRef.current && createPortal( + (() => { + const rect = rhythmButtonRef.current!.getBoundingClientRect() + return ( +
+
+ Paragraph Rhythm + +
+
+ {paragraphRhythm.length} paragraphs + avg {Math.round(paragraphRhythm.reduce((s, p) => s + p.sentences, 0) / paragraphRhythm.length * 10) / 10} sentences +
+
+ {paragraphRhythm.map((p, i) => { + const barW = Math.max(12, Math.round((p.words / maxParaWords) * 180)) + const color = sentenceCountColor(p.sentences) + const preview = p.text.length > 80 ? p.text.slice(0, 80) + '…' : p.text + return ( +
+
+ {p.sentences} +
+ ) + })} +
+
+ {[1, 2, 3, 4, 5, 6, 7, 8].map(n => ( +
+
+ {n}{n === 8 ? '+' : ''} +
+ ))} +
+
+ ) + })(), + document.body + )} + )} {activeFilePath && sentenceStats && ( - + <> + + {statsOpen && statsButtonRef.current && createPortal( + (() => { + const rect = statsButtonRef.current!.getBoundingClientRect() + const maxBin = Math.max(...sentenceStats.bins, 1) + const CHART_H = 60 + return ( +
+
+ Sentence Lengths + +
+
+ avg {Math.round(sentenceStats.avg * 10) / 10}w + σ {Math.round(sentenceStats.stdDev * 10) / 10} + {sentenceStats.totalSentences} sentences +
+
+ {sentenceStats.bins.map((count, i) => { + const wordCount = i + 1 + const isTarget = wordCount >= 11 && wordCount <= 16 + const barH = count === 0 ? 0 : Math.max(2, Math.round((count / maxBin) * CHART_H)) + return ( +
+ ) + })} +
+
+ 1 + 8 + 11–16 ● + 22 + 31+ +
+ {sentenceStats.outliers.length > 0 && ( +
+
+ Flagged (≤4 or ≥26 words) +
+
+ {sentenceStats.outliers.slice(0, 8).map((o, i) => ( +
+ {o.wordCount}w + + {o.text.length > 55 ? o.text.slice(0, 55) + '…' : o.text} + +
+ ))} + {sentenceStats.outliers.length > 8 && ( +
+ +{sentenceStats.outliers.length - 8} more +
+ )} +
+
+ )} +
+ ) + })(), + document.body + )} + )} -
-
- {paragraphRhythm.length} paragraphs - avg {Math.round(paragraphRhythm.reduce((s, p) => s + p.sentences, 0) / paragraphRhythm.length * 10) / 10} sentences -
-
- {paragraphRhythm.map((p, i) => { - const barW = Math.max(12, Math.round((p.words / maxParaWords) * 180)) - const color = sentenceCountColor(p.sentences) - const preview = p.text.length > 80 ? p.text.slice(0, 80) + '…' : p.text - return ( -
-
- {p.sentences} -
- ) - })} -
-
- {[1, 2, 3, 4, 5, 6, 7, 8].map(n => ( -
-
- {n}{n === 8 ? '+' : ''} -
- ))} -
-
- ) - })(), - document.body - )} - - {activeFilePath && sentenceStats && statsOpen && statsButtonRef.current && createPortal( - (() => { - const rect = statsButtonRef.current!.getBoundingClientRect() - const maxBin = Math.max(...sentenceStats.bins, 1) - const CHART_H = 60 - return ( -
-
- Sentence Lengths - -
-
- avg {Math.round(sentenceStats.avg * 10) / 10}w - σ {Math.round(sentenceStats.stdDev * 10) / 10} - {sentenceStats.totalSentences} sentences -
-
- {sentenceStats.bins.map((count, i) => { - const wordCount = i + 1 - const isTarget = wordCount >= 11 && wordCount <= 16 - const barH = count === 0 ? 0 : Math.max(2, Math.round((count / maxBin) * CHART_H)) - return ( -
- ) - })} -
-
- 1 - 8 - 11–16 ● - 22 - 31+ -
- {sentenceStats.outliers.length > 0 && ( -
-
- Flagged (≤4 or ≥26 words) -
-
- {sentenceStats.outliers.slice(0, 8).map((o, i) => ( -
- {o.wordCount}w - - {o.text.length > 55 ? o.text.slice(0, 55) + '…' : o.text} - -
- ))} - {sentenceStats.outliers.length > 8 && ( -
- +{sentenceStats.outliers.length - 8} more -
- )} -
-
- )} -
- ) - })(), - document.body - )} {activeFilePath && (