📝 explanatory tooltips
This commit is contained in:
@@ -628,3 +628,33 @@
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
flex-shrink: 0;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -274,8 +274,26 @@ function scoreColor(score: number): string {
|
|||||||
return 'var(--polish-low)'
|
return 'var(--polish-low)'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DIM_DESCRIPTIONS: Record<string, string> = {
|
||||||
|
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 {
|
function PolishMeter({ score }: { score: PolishScore }): JSX.Element {
|
||||||
const [activeKey, setActiveKey] = useState<string | null>(null)
|
const [activeKey, setActiveKey] = useState<string | null>(null)
|
||||||
|
const [tooltip, setTooltip] = useState<TooltipState | null>(null)
|
||||||
const { setAnnotations, clearAnnotations } = useEditorStore()
|
const { setAnnotations, clearAnnotations } = useEditorStore()
|
||||||
|
|
||||||
const dims = Object.entries(score.dimensions) as [string, PolishDimension][]
|
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)}
|
onClick={() => clickable && handleDimClick(key, dim)}
|
||||||
title={clickable ? `Click to highlight ${dim.issueCount} instance${dim.issueCount !== 1 ? 's' : ''}` : undefined}
|
title={clickable ? `Click to highlight ${dim.issueCount} instance${dim.issueCount !== 1 ? 's' : ''}` : undefined}
|
||||||
>
|
>
|
||||||
<span className="pm-dim-label">{dim.label}</span>
|
<span className="pm-dim-label">
|
||||||
|
{dim.label}
|
||||||
|
{DIM_DESCRIPTIONS[key] && (
|
||||||
|
<span
|
||||||
|
className="pm-dim-info"
|
||||||
|
onClick={e => 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)}
|
||||||
|
>ⓘ</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
<div className="pm-bar-track">
|
<div className="pm-bar-track">
|
||||||
<div
|
<div
|
||||||
className="pm-bar-fill"
|
className="pm-bar-fill"
|
||||||
@@ -337,6 +368,14 @@ function PolishMeter({ score }: { score: PolishScore }): JSX.Element {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
{tooltip && (
|
||||||
|
<div
|
||||||
|
className="pm-tooltip-fixed"
|
||||||
|
style={{ left: tooltip.x, top: tooltip.y }}
|
||||||
|
>
|
||||||
|
{tooltip.text}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user