Validé par Patrick. Sa proposition — production en jaune, autoconso en orange,
et des traits pleins — partait d'un bon constat, mais la couleur ne pouvait pas
régler le problème : superposées, autoconsommation et production sont égales au
watt près dès que rien n'est exporté, et deux traits pleins superposés n'en
laissent voir qu'un, quelles que soient leurs teintes. Le défaut est
géométrique, pas chromatique — et le pointillé posé avant n'était qu'un
contournement.
Empilées, le recouvrement devient impossible :
jaune PV -> réseau (exporté) ┐
orange PV -> maison (autoconsommé) ┘ sommet = production totale
La production cesse d'être une courbe : elle EST le haut de la pile. Quand rien
n'est exporté la bande jaune a une hauteur nulle — vrai, et lisible d'un coup
d'œil. L'export devient une SURFACE au lieu d'un écart à mesurer à l'œil entre
deux traits.
Vérifié à l'écran : l'orange porte le fond de la journée, le jaune ne sort que
sur l'épisode d'export de fin d'après-midi.
Deux détails qui vont avec :
- `_aire` ne courbe pas (`isCurved: false`) — une spline peut passer sous zéro
entre deux points, et une bande d'énergie sous l'axe affiche une grandeur qui
n'existe pas ;
- les lignes n'ont plus de remplissage par défaut : un voile à 7 % recouvrait
les bandes et faussait leurs teintes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
183 lines
8.8 KiB
Dart
183 lines
8.8 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;
|
|
|
|
/// 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;
|
|
}
|