diff --git a/src/renderer/components/Feedback/FeedbackPanel.css b/src/renderer/components/Feedback/FeedbackPanel.css index 9014e54..0f00091 100644 --- a/src/renderer/components/Feedback/FeedbackPanel.css +++ b/src/renderer/components/Feedback/FeedbackPanel.css @@ -628,3 +628,33 @@ text-align: right; flex-shrink: 0; } + +.pm-dim-info { + display: inline-block; + margin-left: 4px; + font-size: 9px; + opacity: 0.4; + cursor: default; + vertical-align: middle; + line-height: 1; +} + +.pm-dim-info:hover { + opacity: 0.9; +} + +.pm-tooltip-fixed { + position: fixed; + transform: translate(-50%, -100%); + width: 220px; + background: var(--bg-panel, #1a1a1a); + border: 1px solid var(--border); + border-radius: 5px; + padding: 7px 9px; + font-size: 11px; + line-height: 1.5; + color: var(--text-primary); + white-space: normal; + z-index: 9999; + pointer-events: none; +} diff --git a/src/renderer/components/Feedback/FeedbackPanel.tsx b/src/renderer/components/Feedback/FeedbackPanel.tsx index cdd060d..7642574 100644 --- a/src/renderer/components/Feedback/FeedbackPanel.tsx +++ b/src/renderer/components/Feedback/FeedbackPanel.tsx @@ -274,8 +274,26 @@ function scoreColor(score: number): string { return 'var(--polish-low)' } +const DIM_DESCRIPTIONS: Record = { + passiveVoice: 'Flags "was/were + past participle" constructions. Penalises above 12 per 1,000 words.', + weaselWords: 'Intensifiers like very, quite, clearly that weaken prose. Penalises above 10 per 1,000 words.', + adverbDensity: '–ly adverbs per 1,000 words. Strong verbs rarely need adverb support. Penalises above 20 per 1,000.', + filterWords: '"She felt", "he noticed" — constructions that put a layer between reader and action. Penalises above 8 per 1,000 words.', + showTell: '"Was/were + emotion adjective" (was angry, were afraid). Suggests telling the reader rather than dramatising the moment.', + sentenceRhythm: 'Coefficient of variation in sentence lengths. High variance = good pacing. Also flags runs of 3+ sentences within 2 words of each other in length.', + paragraphRhythm: 'Variation in paragraph lengths. Flags walls of text over 150 words and measures overall length variance.', + repeatedStarters: 'Penalises 3+ consecutive sentences opening with the same word, and chapters heavy with pronoun openers (he/she/they).', + wordVariety: 'Type-token ratio over 100-word windows of content words. Low ratio = repetitive vocabulary. Also flags words repeated 3+ times within 50 words.', + cliches: 'Matches against a list of common literary clichés (heart pounding, blood ran cold, etc.). Penalises above 6 per 1,000 words.', + overusedWords: 'Drafting filler: just, suddenly, somehow, began to, started to. Penalises above 25 per 1,000 words.', + dialogueTags: 'Said-bookisms (hissed, snarled, barked) and adverb-modified tags (said softly). Penalises above 8 per 1,000 words.', +} + +interface TooltipState { text: string; x: number; y: number } + function PolishMeter({ score }: { score: PolishScore }): JSX.Element { const [activeKey, setActiveKey] = useState(null) + const [tooltip, setTooltip] = useState(null) const { setAnnotations, clearAnnotations } = useEditorStore() const dims = Object.entries(score.dimensions) as [string, PolishDimension][] @@ -325,7 +343,20 @@ function PolishMeter({ score }: { score: PolishScore }): JSX.Element { onClick={() => clickable && handleDimClick(key, dim)} title={clickable ? `Click to highlight ${dim.issueCount} instance${dim.issueCount !== 1 ? 's' : ''}` : undefined} > - {dim.label} + + {dim.label} + {DIM_DESCRIPTIONS[key] && ( + e.stopPropagation()} + onMouseEnter={e => { + const r = (e.currentTarget as HTMLElement).getBoundingClientRect() + setTooltip({ text: DIM_DESCRIPTIONS[key], x: r.left + r.width / 2, y: r.top - 8 }) + }} + onMouseLeave={() => setTooltip(null)} + >ⓘ + )} +
+ {tooltip && ( +
+ {tooltip.text} +
+ )}
) }