SPB Git forge

spb/qc-election

Public
20commits 1branches 0releases
4.9 MBsize
maindefault branch
20 days agolast push
Python 66.6% HTML 24.8% CSS 4.9% JavaScript 3.6%
8.3 KB

# QC Signal — design system de QC Élection Forecast

Version 4 (2026-08-30). Direction : editorial intelligence + election command center + scientific forecasting + identité québécoise. La sensation recherchée : « je regarde un instrument de mesure de l'élection ». À proscrire : dashboard SaaS générique, admin panel, esthétique crypto.

Tout vit dans deux fichiers partagés :

  • frontend/assets/style.css — tokens + composants (source de vérité visuelle)
  • frontend/assets/app.js — coquille (initShell), composants JS, thème ECharts « qc-signal »

# 1. Tokens

# Couleurs de surface (palettes dédiées, le dark n'est PAS une inversion)

Token Dark Light Rôle
--bg #0a0d14 #eef0f4 fond de page (bleu-noir profond / gris papier)
--surface #0f131c #f5f6fa fonds encaissés (meter, pistes)
--card #131824 #ffffff cartes
--card-2 #1a2030 #edeff5 surfaces secondaires (champs, chips, hover)
--ink #edeff5 #161b26 texte principal (blanc cassé / encre)
--ink-2 / --muted #aab2c5 / #7d8699 #414d68 / #67718a texte secondaire / tertiaire
--line / --border rgba(160,178,210,.10) rgba(38,54,92,.10) bordure discrète par défaut
--border-strong rgba(170,188,218,.22) rgba(38,54,92,.22) bordure appuyée (hover, th)
--accent / --accent-strong #94a9d3 / #5c7ec2 #3d5a94 / #35507e bleu institutionnel politiquement neutre
--good / --warn / --danger états sémantiques SEULEMENT (statut pipeline, badges) — jamais pour un mouvement de parti

Principe : espace / contraste / surface avant bordure. Les bordures sont des rgba faibles; l'appui visuel vient des surfaces (--card vs --bg) et des ombres discrètes --shadow-sm / --shadow-md (jamais de gros box-shadow).

# Couleurs des partis — DONNÉES SEULEMENT

--caq #0097cf · --plq #e5484d · --pq #3f7ad6 · --qs #e06c1f · --pcq #7069d6 · --aut #ad8226

Validées daltonisme/contraste sur les deux surfaces. Interdiction de les réutiliser pour le chrome du site, et interdiction de modifier ces valeurs. Toujours accompagnées du sigle texte (jamais la couleur seule pour identifier un parti). Accès JS : partyColor(p).

# Radius, espacement, motion

  • Radius : --r-8, --r-12, --r-16, --r-20, --r-24, --r-full (8/12/16/20/24/999). --radius = 16 (cartes), --radius-sm = 8 (champs, boutons).
  • Espacement : grille 4/8 px — --s-1…--s-12 (4, 8, 12, 16, 20, 24, 32, 40, 48).
  • Motion : --t-fast 130 ms, --t-med 200 ms, --ease cubic-bezier(.25,.6,.3,1). Aucune transition > 220 ms. prefers-reduced-motion coupe tout (CSS global + ECharts via baseChart).

# 2. Typographie

Inter uniquement (400–900). font-variant-numeric: tabular-nums sur tout chiffre (kv, tables, metrics, deltas, strip, tooltips ECharts).

Classe Usage Taille
.t-display titre display clamp ~27→40 px, 800
.hero h1 / .headline H1 de page / manchette éditoriale clamp ~23→32 / ~24→40 px
h2.section titre de section 1.16 rem, 750
.card h3 / .label-up étiquette de carte .76 rem, uppercase, letterspacing .09 em
.metric-xl métrique héroïque clamp → 64 px, 800, tabular
.bignum / .pcard .p-main .v métrique de carte 2.25 / 2.4 rem
corps .sub, .kv, tables .84–.98 rem
.caption / .note / .chart-note légendes .76–.78 rem

# 3. Coquille partagée (initShell(active))

Sur chaque page : <header class="site"> + <footer class="site"> vides, puis initShell("/route") monte :

  1. Navbar — fleur-de-lys SVG (FLEUR_SVG, courant = --accent-strong), wordmark « QC Élection Forecast », liens desktop, indicateur « Dernière MAJ hh:mm » (#hdr-updated, ≥1250 px), bascule de thème SVG (aria-label), hamburger ≤980 px (tiroir avec verrou de scroll, Escape, backdrop).
  2. Forecast strip (#fstrip) — fine barre sticky sous la nav, toutes pages. Alimentée par /api/today (repli /api/forecast/latest) : P(PREMIER) · PQ 78 ▼9.9 · CAQ 15 ▲9.7 … — sigle en couleur du parti, valeur = P(1er) publiée, flèche neutre = variation vs veille en points. Défilement horizontal en mobile, Scrutin 5 oct. · J−n à droite (desktop).
  3. Footer — mission de neutralité + crédits.

Le thème est lu avant le premier paint par le one-liner en tête de chaque page (localStorage["qce-theme"] → data-theme sur <html>). toggleTheme() recharge la page (les canvas ECharts reprennent les couleurs).

# 4. Grammaire des données (langage + composants JS)

Vocabulaire uniforme : « Chance d'arriver premier » · « Sièges attendus » · « Vote projeté » · « Majorité ». Jamais « X va gagner ».

Helper (app.js) Rendu Règles
deltaHTML(d, {unit, digits, color, thresh}) ▲ 4.1 pt / ▼ … / · stable JAMAIS vert/rouge : couleur du parti ou neutre (--muted). Une hausse d'un parti n'est pas « positive » universellement.
rangeHTML({lo, hi, val, min, max, mark, color}) 24 ───●─── 86 intervalle visuel; mark = repère (majorité 64); dot = valeur centrale; aria-label complet
hemicycleSVG(seats, meta) hémicycle 127 sièges arc demi-cercle 6 rangées (sièges ∝ rayon), allocation entière sur p50 (reste au plus fort), marqueur pointillé « majorité 64 » à l'angle du 64ᵉ siège, hover = parti/sièges/intervalle + dim des autres, légende sigle+valeur+IC
emptyStateHTML(msg) état vide propre « Données temporairement indisponibles. » — jamais de undefined/NaN à l'écran
avatarHTML / initialsAvatar / partyLogo visuels secondaires petits, jamais porteurs d'information seuls

Autres composants CSS : .outcome (main outcome 2 colonnes), .outcome-bar (issues segmentées — uniquement des probabilités du modèle : prob_majority, prob_minority_win, complément), .pcard (carte parti compacte data-first), .meter (baromètre sièges), .table-scroll (tables hautes, en-tête sticky — table.data th { position: sticky }), .skel / .loading (squelettes shimmer), .chip / .badge-aux, .map-layout (~75 % carte / 25 % panneau, empilé ≤980 px).

# 5. Conventions charts (thème « qc-signal », ECharts CDN uniquement)

Toujours partir des exports d'app.js — jamais de styles ad hoc :

  • baseChart(el) — Inter, animation 420 ms (0 si reduced motion), auto-resize.
  • AXIS_STYLE — pas d'axisLine/tick; labels MUTED 11 px; grille SPLIT_COLOR (recessive, plus claire que le texte).
  • TOOLTIP — fond solide arrondi 12 px, bordure token, ombre portée, chiffres tabulaires, axisPointer en croix pointillée.
  • LEGEND — puces rondes 10 px, texte INK2.
  • Séries : lineGlow(color, 2–2.5) (trait arrondi + halo de sa propre couleur), areaGrad(color, .10–.28) (dégradé vertical, jamais d'aplat), hexA(hex, a).
  • Couleur de série = TOUJOURS partyColor(p); séries neutres : #8fa3c8 / GRID_COLOR. Repères (majorité 64, aujourd'hui) : markLine pointillée MUTED.
  • Constantes de thème : DARKMODE, GRID_COLOR, SPLIT_COLOR, INK2, MUTED, CHART_FONT.

# 6. Accessibilité & robustesse

  • Focus visible global (:focus-visible 2 px --accent-strong).
  • aria-label sur burger, bascule de thème, hémicycle, ranges; aria-current="page" sur le lien actif; drawer role="dialog".
  • Parti = sigle texte + couleur, jamais couleur seule; flèches de variation doublées du signe/valeur.
  • Anti-zoom iOS : tout champ à 16 px min sous 640 px.
  • html, body { overflow-x: clip } (le tiroir hors-écran ne crée jamais de scroll) et .grid > * { min-width: 0 } (les canvas ECharts ne verrouillent jamais une colonne de grille).
  • Breakpoints : 1560 (nav compacte), 1250 (MAJ masquée), 1120 (nav dense), 980 (burger + colonnes empilées), 900, 640 (mobile), 400 (cartes 1 colonne).

# 7. Interdits

  • Toucher aux couleurs des partis, ou les utiliser hors données.
  • Vert/rouge pour un mouvement de parti.
  • Nouvelle dépendance JS (ECharts CDN seulement), nouvelle police.
  • Réordonner le DOM entre breakpoints (order:, column-reverse).
  • Modifier un contrat d'API ou recalculer une probabilité côté client.