La V2C et la borne simulée, toutes deux en `ev`, recevaient exactement le même violet : indiscernables dans l'empilement, et la légende répétait la même pastille. Vu à l'écran dès qu'une seconde borne est apparue. `EtmTokens.nuance` décline sur le RANG dans le domaine, donc une troisième borne prend sa teinte sans qu'on déclare quoi que ce soit. Le décalage porte sur la LUMINOSITÉ, pas sur la teinte : glisser la teinte finirait par heurter le domaine voisin, l'éclaircir garde la famille lisible. ⚠️ La série est bornée à quatre, et c'est explicite plutôt que subi : au-delà, l'écart ne se voit plus, et prétendre distinguer huit nuances d'un même violet serait une promesse que l'œil ne tient pas. Le rang 3 et le rang 9 rendent la même couleur — le signe qu'il faut alors autre chose que la couleur pour identifier, et c'est un choix d'écran, pas un réglage de palette. Un test le fige. La couleur est calculée à UN endroit, partagé par le tracé et la légende : deux sources de couleur, c'est une légende qui finit par désigner la mauvaise bande. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
206 lines
10 KiB
Dart
206 lines
10 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
|
|
/// Tokens de marque ETM PowerSync — source de vérité unique.
|
|
class EtmTokens {
|
|
EtmTokens._();
|
|
|
|
// ── Couleurs de marque (spec Dashboard v4) ────────────────────────────────
|
|
static const Color brand = Color(0xFF31A3DD);
|
|
static const Color solar = Color(0xFFFEC113);
|
|
static const Color solarDark = Color(0xFFFFCB33);
|
|
static const Color eco = Color(0xFF28A06A);
|
|
static const Color ecoDark = Color(0xFF33B87A);
|
|
static const Color heat = Color(0xFFEF8B2A);
|
|
static const Color water = Color(0xFF31A3DD);
|
|
static const Color gridColor = Color(0xFF8A98A3);
|
|
static const Color importColor = Color(0xFFE8806F);
|
|
|
|
// ── Legacy (compatibilité existante) ─────────────────────────────────────
|
|
static const Color navy = Color(0xFF0D2B3B);
|
|
static const Color navy2 = Color(0xFF10384C);
|
|
static const Color blue = Color(0xFF31A3DD);
|
|
static const Color amber = Color(0xFFFEC113);
|
|
static const Color green = Color(0xFF1DB86A);
|
|
static const Color greenDark= Color(0xFF159C58);
|
|
static const Color orange = Color(0xFFE8923A);
|
|
static const Color danger = Color(0xFFE8423F);
|
|
|
|
// ── Couleurs de flux ──────────────────────────────────────────────────────
|
|
static const Color solToHouse = Color(0xFFB8C34D);
|
|
static const Color solToEcs = Color(0xFF31A3DD);
|
|
static const Color solToVe = Color(0xFF28A06A);
|
|
static const Color solToBatt = Color(0xFF7FCF72);
|
|
static const Color solToGrid = Color(0xFFFEC113);
|
|
static const Color battToHouse = Color(0xFF2F6FB0);
|
|
static const Color gridToHouse = Color(0xFFE8806F);
|
|
|
|
// ── Neutres light ─────────────────────────────────────────────────────────
|
|
static const Color ink = navy;
|
|
static const Color muted= Color(0xFF5D6F7B);
|
|
static const Color faint= Color(0xFF8B9AA4);
|
|
static const Color line = Color(0xFFE4EAEE);
|
|
static const Color bg = Color(0xFFEEF2F5);
|
|
static const Color bg2 = Color(0xFFE4EAEE);
|
|
static const Color card = Color(0xFFFFFFFF);
|
|
|
|
// ── Neutres dark ──────────────────────────────────────────────────────────
|
|
static const Color bgDark = Color(0xFF06141D);
|
|
static const Color bg2Dark = Color(0xFF0A1F2B);
|
|
static const Color surfaceDark = Color(0xFF0D2B3B);
|
|
static const Color inkDark = Color(0xFFEAF3F7);
|
|
static const Color mutedDark = Color(0xFF9BB0BC);
|
|
static const Color faintDark = Color(0xFF6C8493);
|
|
|
|
// ── Soft fills (legacy light) ─────────────────────────────────────────────
|
|
static const Color greenSoft = Color(0xFFE7F8EF);
|
|
static const Color blueSoft = Color(0xFFE9F5FB);
|
|
static const Color amberSoft = Color(0xFFFFF6DD);
|
|
|
|
// ── Rayons ────────────────────────────────────────────────────────────────
|
|
static const double radius = 16;
|
|
static const double radiusLg = 22;
|
|
static const double radiusCard = 20;
|
|
static const double radiusCtrl = 13;
|
|
static const double radiusPill = 30;
|
|
|
|
// ── Context-aware ─────────────────────────────────────────────────────────
|
|
static bool _isDark(BuildContext c) =>
|
|
Theme.of(c).brightness == Brightness.dark;
|
|
|
|
static Color bgOf(BuildContext c) => _isDark(c) ? bgDark : bg;
|
|
static Color bg2Of(BuildContext c) => _isDark(c) ? bg2Dark : bg2;
|
|
static Color surfaceOf(BuildContext c) => _isDark(c) ? surfaceDark : card;
|
|
static Color inkOf(BuildContext c) => _isDark(c) ? inkDark : ink;
|
|
static Color mutedOf(BuildContext c) => _isDark(c) ? mutedDark : muted;
|
|
static Color faintOf(BuildContext c) => _isDark(c) ? faintDark : faint;
|
|
static Color ecoOf(BuildContext c) => _isDark(c) ? ecoDark : eco;
|
|
static Color solarOf(BuildContext c) => _isDark(c) ? solarDark : solar;
|
|
|
|
// ── Couleurs par CONSOMMATEUR ─────────────────────────────────────────────
|
|
//
|
|
// Distinctes des couleurs de FLUX (solaire, maison, réseau, batterie), qui disent d'où
|
|
// vient l'énergie. Celles-ci disent **qui la prend**, et il en fallait de nouvelles :
|
|
// le thème n'en portait aucune, et réutiliser une couleur de flux ferait lire une
|
|
// origine là où il n'y a qu'une destination.
|
|
//
|
|
// Trois teintes choisies pour ne heurter aucune couleur de flux existante, et validées
|
|
// par Patrick le 2026-09-08 sur la maquette `docs/mockups/ecran_energie_refonte.html`.
|
|
static const Color pac = Color(0xFFE88C30); // pompe à chaleur — ambre chaud
|
|
static const Color pacDark = Color(0xFFF09A45);
|
|
static const Color ecs = Color(0xFF2AA7A0); // eau chaude — sarcelle
|
|
static const Color ecsDark = Color(0xFF37BDB5);
|
|
static const Color ev = Color(0xFF7C6CE4); // bornes — indigo
|
|
static const Color evDark = Color(0xFF8E80EA);
|
|
|
|
/// **L'autoconsommé**, dans l'empilement du graphe de flux — orange, sous le jaune de
|
|
/// l'exporté dont le sommet donne la production totale.
|
|
///
|
|
/// ⚠️ Voisine de [pac] (ambre-orange), et c'est assumé : les deux n'apparaissent jamais
|
|
/// dans le même graphe — celle-ci décompose la PRODUCTION, celle-là un CONSOMMATEUR. Si
|
|
/// la proximité gêne à l'écran sur la page complète, c'est [pac] qui bougera : la paire
|
|
/// jaune/orange a été choisie par Patrick pour ce graphe-ci.
|
|
static const Color selfUse = Color(0xFFF4661B);
|
|
static const Color selfUseDark = Color(0xFFFA7A34);
|
|
static Color selfUseOf(BuildContext c) => _isDark(c) ? selfUseDark : selfUse;
|
|
|
|
static Color pacOf(BuildContext c) => _isDark(c) ? pacDark : pac;
|
|
static Color ecsOf(BuildContext c) => _isDark(c) ? ecsDark : ecs;
|
|
static Color evOf(BuildContext c) => _isDark(c) ? evDark : ev;
|
|
|
|
/// **Décliner la couleur d'un domaine quand il porte plusieurs charges.**
|
|
///
|
|
/// Deux bornes dans le domaine `ev` recevaient exactement le même violet : indiscernables
|
|
/// dans l'empilement, et la légende répétait la même pastille deux fois. Vu à l'écran le
|
|
/// 2026-09-08, dès qu'une seconde borne est apparue.
|
|
///
|
|
/// La nuance se calcule sur le RANG dans le domaine, donc une troisième borne prend sa
|
|
/// teinte toute seule — rien à déclarer. On joue sur la **luminosité** en HSL et pas sur
|
|
/// la teinte : glisser la teinte finirait par heurter le domaine voisin, alors que
|
|
/// l'éclaircir garde la famille lisible.
|
|
///
|
|
/// ⚠️ **La série est bornée à quatre.** Au-delà, l'écart de luminosité devient trop fin
|
|
/// pour être vu, et prétendre distinguer huit nuances d'un même violet serait une
|
|
/// promesse que l'œil ne tient pas. À partir de la cinquième charge d'un même domaine, la
|
|
/// couleur cesse d'identifier — il faudra autre chose (étiquette au survol, séparation
|
|
/// des domaines), et c'est un vrai choix d'écran, pas un réglage de palette.
|
|
static Color nuance(Color base, int rang) {
|
|
if (rang <= 0) return base;
|
|
final h = HSLColor.fromColor(base);
|
|
final pas = 0.11 * (rang.clamp(0, 3));
|
|
return h.withLightness((h.lightness + pas).clamp(0.0, 0.85)).toColor();
|
|
}
|
|
|
|
/// La couleur d'un domaine de charge, ou `null` s'il n'en a pas.
|
|
///
|
|
/// `null` n'est pas un défaut à combler : une charge sans domaine déclaré n'a pas de
|
|
/// couleur, et lui en attribuer une par défaut la ferait passer pour classée.
|
|
static Color? domaineOf(BuildContext c, String? domaine) => switch (domaine) {
|
|
'heating' => pacOf(c),
|
|
'ecs' => ecsOf(c),
|
|
'ev' => evOf(c),
|
|
_ => null,
|
|
};
|
|
|
|
static Color lineOf(BuildContext c) => _isDark(c)
|
|
? const Color(0xFFFFFFFF).withValues(alpha: 0.09)
|
|
: navy.withValues(alpha: 0.10);
|
|
|
|
static Color lineStrongOf(BuildContext c) => _isDark(c)
|
|
? const Color(0xFFFFFFFF).withValues(alpha: 0.16)
|
|
: navy.withValues(alpha: 0.16);
|
|
|
|
// ── Typographie ───────────────────────────────────────────────────────────
|
|
static TextStyle sans({
|
|
double size = 14,
|
|
FontWeight weight = FontWeight.w400,
|
|
Color color = ink,
|
|
double? height,
|
|
double? letterSpacing,
|
|
}) =>
|
|
GoogleFonts.ibmPlexSans(
|
|
fontSize: size,
|
|
fontWeight: weight,
|
|
color: color,
|
|
height: height,
|
|
letterSpacing: letterSpacing,
|
|
);
|
|
|
|
static TextStyle mono({
|
|
double size = 14,
|
|
FontWeight weight = FontWeight.w600,
|
|
Color color = ink,
|
|
}) =>
|
|
GoogleFonts.ibmPlexMono(
|
|
fontSize: size,
|
|
fontWeight: weight,
|
|
color: color,
|
|
fontFeatures: const [FontFeature.tabularFigures()],
|
|
);
|
|
|
|
static TextStyle sectionLabel() => sans(
|
|
size: 12,
|
|
weight: FontWeight.w600,
|
|
color: faint,
|
|
letterSpacing: 1.2,
|
|
);
|
|
|
|
// ── Ombres ────────────────────────────────────────────────────────────────
|
|
static List<BoxShadow> get cardShadow => [
|
|
BoxShadow(
|
|
color: navy.withValues(alpha: 0.04),
|
|
blurRadius: 2,
|
|
offset: const Offset(0, 1),
|
|
),
|
|
BoxShadow(
|
|
color: navy.withValues(alpha: 0.06),
|
|
blurRadius: 30,
|
|
offset: const Offset(0, 10),
|
|
),
|
|
];
|
|
|
|
static List<BoxShadow> cardShadowOf(BuildContext c) => _isDark(c)
|
|
? []
|
|
: cardShadow;
|
|
}
|