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 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 cardShadowOf(BuildContext c) => _isDark(c) ? [] : cardShadow; }