Files
decision/web/src/lib/components/FormulaSettings.svelte
T
YvvandClaude Opus 5.5 db64405e1e v3 : inertie à cinq crans — interface (L10)
L'inertie se lit en crans, « 3 / 5 · moyenne » (très basse, basse,
moyenne, haute, très haute ; fr, en, es). Le sélecteur offre les cinq
crans, l'actuel marqué ; le changement se vote au cran de l'article.
L'article dit le passé de son inertie (vote ou réglage de départ).

« Réglages de la formule », en lecture seule, depuis l'en-tête du doc et
depuis le vote d'un article (onglet « Réglages » du panneau, dans
l'URL) : W et sa source, C, B, la participation habituelle (médiane ou
référence, sur combien de propositions, quand), et les cinq crans : M,
G, part exigée, voix pour exigées à la participation de la version
regardée (ou habituelle depuis l'en-tête).

Composants à part (InertiaPicker, FormulaSettings), branchés par
quelques lignes dans VoteView. La formule dépliée ne fait plus déborder
le panneau. e2e : C4 (cran de l'article dans la formule), C20 (réglages
et cinq crans), M1 (réglages lisibles sur mobile).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:42:11 +01:00

196 lines
5.7 KiB
Svelte

<script lang="ts">
// The settings of the formula, read only: W and its source, C, B, the usual
// turnout of the group (median or reference) and the five levels on its
// scale. From the head of the document, the *for* votes asked are those of
// the usual turnout; from a version, those of its own turnout.
import { api } from '$lib/api';
import type { DocState } from '$lib/doc.svelte';
import { i18n, t } from '$lib/i18n/index.svelte';
import type { Inertia, LevelInfo } from '$lib/types';
let { doc, proposalId = null }: { doc: DocState; proposalId?: string | null } = $props();
interface Version {
levels: LevelInfo[];
proposal: { votedAtInertia: Inertia };
}
let version = $state<Version | null>(null);
$effect(() => {
const id = proposalId;
void doc.view;
version = null;
if (!id) return;
api
.get<Version>(`/proposals/${id}`)
.then((d) => {
if (proposalId === id) version = d;
})
.catch(() => {
version = null;
});
});
const view = $derived(doc.view);
const p = $derived(view?.doc.participation);
const levels = $derived(version?.levels ?? view?.levels ?? []);
const cast = $derived(levels[0]?.cast ?? 0);
const current = $derived(version?.proposal.votedAtInertia ?? null);
const pct = (ratio: number) => t('common.pct', { n: i18n.number(Math.round(ratio * 10000) / 100) });
const whole = (n: number) => t('common.pct', { n: i18n.number(n) });
const gradient = (g: number) => i18n.number(Math.round(g * 1000) / 1000);
</script>
{#if view && p}
<section class="settings" aria-labelledby="settings-title">
<h3 id="settings-title">{t('settings.title')}</h3>
<p class="muted">{t('settings.intro')}</p>
<ul class="values">
<li>{t('formula.W', { n: i18n.number(view.doc.electorate), source: t(view.doc.electorateSource === 'wot' ? 'formula.W.wot' : 'formula.W.group') })}</li>
<li>{t('formula.C', { n: i18n.number(view.doc.voteFloor) })}</li>
<li>{t('formula.B', { n: i18n.number(view.doc.formula.base) })}</li>
</ul>
<div class="usual">
<h4>{t('settings.usual')}</h4>
<p>
{#if p.source === 'median'}
{i18n.plural('settings.usual.median', p.voters, { electorate: i18n.number(view.doc.electorate), pct: pct(p.ratio), sample: p.sample })}
{:else}
{i18n.plural('settings.usual.reference', p.voters, { electorate: i18n.number(view.doc.electorate), pct: pct(p.ratio), min: p.minSample, sample: p.sample })}
{/if}
</p>
{#if p.bounded}<p class="faint small">{t('settings.usual.bounded', { lo: pct(p.bounds[0]), hi: pct(p.bounds[1]) })}</p>{/if}
<p class="faint small">{p.measuredAt ? t('settings.usual.when', { date: i18n.date(p.measuredAt) }) : t('settings.usual.never')}</p>
</div>
<h4>{t('settings.levels')}</h4>
<div class="scroll">
<table>
<thead>
<tr>
<th scope="col">{t('settings.col.level')}</th>
<th scope="col">M</th>
<th scope="col">G</th>
<th scope="col">{t('settings.col.share')}</th>
<th scope="col">{t('settings.col.required')}</th>
</tr>
</thead>
<tbody>
{#each levels as l (l.level)}
<tr class:current={l.level === current}>
<th scope="row">
<span class="lvl">
<span class="k">{l.level}</span>
<span class="name">{t(`inertia.name.${l.level}`)}</span>
{#if l.level === current}<span class="tag water">{t('settings.current')}</span>{/if}
</span>
</th>
<td>{whole(l.majorityPct)}</td>
<td>{gradient(l.gradient)}</td>
<td>{whole(l.sharePct)}</td>
<td class="required">{l.required === null ? '—' : i18n.number(l.required)}</td>
</tr>
{/each}
</tbody>
</table>
</div>
<p class="faint small">{i18n.plural('settings.share.note', p.voters)}</p>
<p class="faint small">{i18n.plural(version ? 'settings.required.version' : 'settings.required.usual', cast)}</p>
<p class="faint small">{t('settings.reading')}</p>
</section>
{/if}
<style>
.settings {
display: grid;
gap: 0.8rem;
min-width: 0;
}
h3 {
font-family: var(--font-text);
font-weight: 500;
font-size: 1.35rem;
}
h4 {
margin: 0;
font-size: 0.92rem;
font-weight: 800;
}
.small {
font-size: 0.84rem;
}
.values {
margin: 0;
padding-left: 1.1rem;
font-size: 0.92rem;
display: grid;
gap: 0.15rem;
}
.usual {
display: grid;
gap: 0.3rem;
padding: 0.8rem 1rem;
border-radius: var(--r-card);
background: var(--water-wash);
font-size: 0.92rem;
}
.scroll {
overflow-x: auto;
border-radius: var(--r-input);
background: var(--surface-2);
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.88rem;
font-variant-numeric: tabular-nums;
}
th,
td {
padding: 0.45rem 0.55rem;
text-align: right;
white-space: nowrap;
}
thead th {
font-size: 0.74rem;
font-weight: 800;
letter-spacing: 0.04em;
color: var(--ink-soft);
}
th[scope='row'],
thead th:first-child {
text-align: left;
}
th[scope='row'] {
font-weight: 600;
}
.lvl {
display: inline-flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0.35rem;
}
tbody tr:nth-child(odd) {
background: color-mix(in srgb, var(--surface) 55%, transparent);
}
tr.current {
background: var(--water-wash) !important;
}
.k {
font-weight: 800;
font-size: 1rem;
}
.name {
color: var(--ink-soft);
font-size: 0.8rem;
}
.required {
font-weight: 800;
}
.tag {
font-size: 0.68rem;
}
</style>