# UI Audit — refonte « QC Signal » (2026-08-30) État des lieux avant la refonte du frontend (design v3 « sharp ») et dettes retirées par la migration vers le design system **QC Signal** (voir `docs/DESIGN_SYSTEM.md`). Refonte UI/UX pure : **aucun changement statistique, aucun contrat d'API modifié, backend intact.** ## État avant - Design v3 « sharp » : bon socle (thème dark/light, palette partis validée CVD, drawer mobile correct), mais tokens incomplets (pas d'échelle de radius/spacing formalisée, ombres composites lourdes `--shadow-lift`), deux polices (Inter + JetBrains Mono), logo = carré dégradé bleu/violet avec ⚜ emoji et ombre portée. - Accueil : liste de cartes partis chargées (gros portraits + logos), baromètre « meter » comme seul visuel de sièges, aucun hemicycle, aucune hiérarchie éditoriale (pas de verdict principal), intervalles `[a–b]` en texte brut partout. - « Le point du jour » : deltas **verts/rouges** (`delta-up`/`delta-down`) — une hausse du PQ affichée « positive » universellement, en contradiction avec la neutralité de la plateforme. - Intelligence / simulateur : momentum et « vs référence » également en vert (`--good`) / rouge (`--plq`!) — le rouge d'un parti utilisé comme sémantique négative. - Pas d'indicateur global du forecast hors de l'accueil (aucun « strip »). - Tables longues (127 circonscriptions, ~2000 sondages) sans en-tête sticky ni hauteur bornée. - /carte : carte à ~66 % de largeur, hauteur fixe 560 px, panneau 340 px figé. - Emojis décoratifs dans les cartes de couches (🗳️✅🏛️📰📈) et libellés d'état. - Bascule de thème = emoji ☀️/🌙. - États vides hétérogènes (``, `.loading` partout). ## Dettes retirées | Dette | Correction | |---|---| | Deltas vert/rouge (aujourdhui, intelligence, simulateur) | helper unique `deltaHTML` : flèche + valeur, couleur du parti ou neutre — jamais de valence universelle | | `var(--plq)` (rouge PLQ) utilisé comme couleur d'erreur (admin, intelligence, simulateur) | `--danger` sémantique (admin) ou couleur du parti (données) | | Intervalles texte `[24–86]` sans lecture visuelle | composant `rangeHTML` (barre lo─●─hi avec repère majorité) sur accueil + cartes partis | | Aucune vue de composition des sièges | `hemicycleSVG` réutilisable (127 sièges, marqueur majorité 64, hover parti/sièges/IC) | | Pas de résumé du forecast inter-pages | forecast strip sticky sous la nav sur les 11 pages (via `initShell`, `/api/today` + repli) | | Logo pastille dégradée + emoji ⚜ | fleur-de-lys SVG nette monochrome accent, wordmark simple | | Emoji ☀️/🌙 et emojis de couches | icônes SVG soleil/lune; couches numérotées 1-5 | | 2 webfonts | Inter seul (code → pile monospace système) | | Ombres/gradients lourds sur cartes | surfaces planes + `--shadow-sm/--shadow-md` discrets, bordures rgba faibles | | Tables longues illisibles | `.table-scroll` (max-height 70 vh) + `th` sticky (circonscriptions, sondages) | | /carte : carte à l'étroit | `.map-layout` ~75/25, hauteur `min(72vh, 700px)` | | Débordement horizontal mobile (390 px) : tiroir hors-écran comptait dans scrollWidth; canvas ECharts verrouillait la colonne de grille (/intelligence à 824 px); table marchés sans conteneur | `html,body{overflow-x:clip}`, `.grid>*{min-width:0}`, `overflow-wrap:anywhere` sur les verbatims, conteneur défilant sur la table marchés — scrollWidth = 390 sur les 11 pages | | Étiquette « majorité 64 » de l'hémicycle coupée hors viewBox | marge haute du SVG (+52 px) | | Nav qui tronquait « Méthodologie » à 1440 px | paliers 1560/1250/1120 px (densité, masquage MAJ, masquage « Forecast ») | | États vides ad hoc / risque de `undefined` | `emptyStateHTML(...)` uniforme; audit Playwright vérifie l'absence de `undefined`/`NaN`/`[object Object]` dans le texte rendu | | Animations non bornées | ≤220 ms, `prefers-reduced-motion` respecté aussi par ECharts (`baseChart`) | ## Langage uniformisé « Chance d'arriver premier » · « Sièges attendus » · « Vote projeté » · « Majorité » — appliqué à l'accueil, au point du jour, au strip et aux cartes partis. ## Validation `~/ka-screenshots/qcsignal-audit.mjs` : 11 pages × {1440, 1024, 768, 390} px, collecte `pageerror` + `console.error`, détection de texte `undefined`/`NaN`, captures `/tmp/qcsignal_*.png` (accueil aussi en clair 1440 + 390). Résultat final : **zéro erreur JS, zéro débordement horizontal, aucun texte cassé** sur les 44 combinaisons. ## Contrats préservés (fragiles — ne pas renommer) - IDs DOM consommés par les scripts de page (inchangés partout) : notamment `hdr-updated`, `drawer-updated`, `nav-burger`, `fstrip` (coquille); `party-cards`, `trend-chart`, `seat-hist`, `prob-hist`, `nowfc`, `events`, `latest-polls`, `beyond-*` (accueil); tous les IDs de carte/circonscriptions/ sondages/signaux/simulateur/intelligence/backtest/admin inchangés. - Helpers globaux d'app.js réutilisés par toutes les pages : `initShell`, `getJSON`, `API`, `partyColor/partyName/partyLeader`, `avatarHTML`, `initialsAvatar`, `partyLogo`, `fmtPct/fmtDate/sortParties`, `baseChart`, `AXIS_STYLE/TOOLTIP/LEGEND`, `areaGrad/lineGlow/hexA`, `DARKMODE/GRID_COLOR/SPLIT_COLOR/MUTED/INK2/CHART_FONT` — signatures intactes. - La probabilité « premier » affichée reste la valeur **publiée** (`seats.ensemble.blended` avec repli `prob_most`) — aucune probabilité recalculée côté client; l'outcome bar n'utilise que `prob_majority`, `prob_minority_win` et leur complément.