- {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 (
-
- )
- })}
-
-
- {[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 && (