ux: add proposal voting explainer

This commit is contained in:
Marco Allegretti 2026-01-29 18:24:39 +01:00
parent cc57fa094b
commit 22d932c4fc

View file

@ -63,6 +63,72 @@ const proposalId = id ?? '';
return hints[method] || hints['approval'];
}
function getVotingExplainer(method) {
const m = normalizeMethod(method);
if (m === 'ranked_choice') {
return {
hint: 'Ranked-choice voting',
lede: 'You rank options in order of preference. If your top choice cannot win, your vote transfers to your next choice.',
bullets: [
'Click options in order: 1st choice, then 2nd, then 3rd.',
'Click an already-ranked option to remove it and re-number the ranks.',
'Add as many ranks as you like—more ranks can make your intent clearer.',
],
note: 'Tip: if you only rank one option, your vote will not transfer.'
};
}
if (m === 'quadratic') {
return {
hint: 'Quadratic voting',
lede: 'You allocate credits across options. Extra support costs more (1 vote costs 1 credit, 2 votes cost 4, 3 votes cost 9, etc.).',
bullets: [
'Use + / to allocate votes (credits) per option.',
'Watch the total credits used so you do not exceed the limit.',
'Spread credits to express nuance, or concentrate them to signal priority.'
],
note: 'Tip: quadratic cost makes it expensive to “pile on” many votes for one option.'
};
}
if (m === 'star') {
return {
hint: 'Star voting',
lede: 'You rate each option from 0 to 5 stars. Higher ratings show stronger support across multiple options.',
bullets: [
'Click stars on each option to set your rating.',
'You can rate multiple options highly if you like several outcomes.',
'Use low ratings (or 0) to express opposition or disinterest.'
],
note: 'Tip: star voting captures intensity without forcing a single pick.'
};
}
if (m === 'schulze') {
return {
hint: 'Schulze (Condorcet) method',
lede: 'This method compares options pairwise to find the strongest overall winner based on collective preferences.',
bullets: [
'Think in terms of which option you prefer over another, not just a single favorite.',
'If you are asked to rank options, include as many as you can for clarity.',
'Results aim to reflect broad preference strength across matchups.'
],
note: 'Tip: ranking more options helps express your true preferences.'
};
}
return {
hint: 'Approval voting',
lede: 'You can approve multiple options. Each approved option gets one vote from you.',
bullets: [
'Click to select one or more options you can live with.',
'Approving several options signals you are open to compromise.',
'Submit when your selections match your intent.'
],
note: 'Tip: approval voting is great when there are several acceptable outcomes.'
};
}
async function loadProposal() {
const container = document.getElementById('proposal-content');
if (!container) return;
@ -173,6 +239,25 @@ const proposalId = id ?? '';
</div>
</details>
<details class="panel ui-card" open>
<summary>
<span>How voting works</span>
<span class="panel-hint">${escapeHtml(getVotingExplainer(methodKey).hint)}</span>
</summary>
<div class="panel-body">
${(() => {
const explainer = getVotingExplainer(methodKey);
return `
<p class="explainer-lede">${escapeHtml(explainer.lede)}</p>
<ul class="explainer-list">
${explainer.bullets.map((b) => `<li>${escapeHtml(b)}</li>`).join('')}
</ul>
${explainer.note ? `<p class="explainer-note">${escapeHtml(explainer.note)}</p>` : ''}
`;
})()}
</div>
</details>
${canStartDiscussion ? `
<div class="author-actions ui-card">
<div class="author-actions-row">
@ -699,6 +784,27 @@ const proposalId = id ?? '';
margin-bottom: 1rem;
}
.explainer-lede {
margin: 0;
color: var(--color-text);
line-height: 1.6;
}
.explainer-list {
margin: 0.75rem 0 0;
padding-left: 1.1rem;
color: var(--color-text-muted);
line-height: 1.55;
display: grid;
gap: 0.35rem;
}
.explainer-note {
margin: 0.75rem 0 0;
color: var(--color-text-muted);
font-size: 0.9rem;
}
.comment-actions {
margin-top: 0.5rem;
display: flex;