Faire des jauges de ce type

Je viens de réussir à faire une version ultra compacte pour répondre à mes souhaits de visualisation simultanés et de superposition, j’espère ne pas avoir fait d’erreur sur la position du curseur

type: custom:button-card
entity: sensor.netatmo_salon_dioxyde_de_carbone
show_state: false
show_icon: false
show_name: false
tap_action: none
double_tap_action: none
hold_action: none
custom_fields:
  icon:
    card:
      type: custom:button-card
      icon: mdi:molecule-co2
      styles:
        card:
          - aspect-ratio: 1/1
          - width: 20px
          - padding: 0
          - border: 1px dashed white
          - border-radius: 50%
          - background: none
        icon:
          - width: 90%
          - color: white
  name:
    card:
      type: custom:button-card
      name: Netatmo Salon dioxyde de carbone
      styles:
        card:
          - width: 300px
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1rem
          - align-self: start
          - justify-self: start
          - color: white
          - font-weight: 500
  separation:
    card:
      type: custom:button-card
      styles:
        card:
          - width: 600px
          - height: 0px
          - padding: 0
          - border: 0px solid rgba(54,56,68,1.0)
          - border-radius: 0
          - background: none
  value:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: true
      state_display: |
        [[[
          return parseFloat(entity.state).toFixed(0);
        ]]]
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        state:
          - color: white
          - font-size: 2rem
          - font-weight: 700
  unit:
    card:
      type: custom:button-card
      name: ppm
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.4rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 800
  min_val:
    card:
      type: custom:button-card
      name: "400"
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  max_val:
    card:
      type: custom:button-card
      name: "2000"
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  bar:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - height: 6px
          - width: 250px
          - border-radius: 999px
          - border: 0
          - padding: 0px
          - background: >-
              linear-gradient(to right, rgba(0,255,24,1.0) 0%,
              rgba(152,255,0,1.0) 40%, rgba(255,252,0,1.0) 55%,
              rgba(255,174,0,1.0) 75%, rgba(255,0,0,1.0) 100%)
  cursor:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - width: 12px
          - height: 25px
          - border-radius: 999px
          - border: 4px solid rgba(42,45,54,1.0)
          - background-color: |
              [[[
                const ppm = Number(entity.state) || 0;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((ppm - 400) / 1600) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [0,   255, 24]  },
                  { p: 40,  c: [152, 255, 0]   },
                  { p: 55,  c: [255, 252, 0]   },
                  { p: 75,  c: [255, 174, 0]   },
                  { p: 100, c: [255, 0,   0]   }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},1.0)`;
              ]]]
  comment:
    card:
      type: custom:button-card
      show_name: true
      show_icon: false
      show_state: false
      name: |
        [[[
          const ppm = Number(entity.state) || 0;
          if (ppm < 900) {
            return "Excellent";
          }
          if (ppm >= 900 && ppm <= 1150) {
            return "Bon";
          }
          if (ppm >= 1151 && ppm <= 1400) {
            return "Acceptable";
          }
          if (ppm >= 1401 && ppm <= 1600) {
            return "Mauvais";
          }
          if (ppm > 1600) {
            return "Malsain";
          }
        ]]]
      styles:
        card:
          - height: 8px
          - width: auto
          - height: 1px
          - border-radius: 999px
          - border: 0
          - padding: 8px
          - background: white
          - background-color: |
              [[[
                const ppm = Number(entity.state) || 0;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((ppm - 400) / 1600) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [0,   255, 24]  },
                  { p: 40,  c: [152, 255, 0]   },
                  { p: 55,  c: [255, 252, 0]   },
                  { p: 75,  c: [255, 174, 0]   },
                  { p: 100, c: [255, 0,   0]   }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},0.5)`;
              ]]]
        name:
          - font-size: 0.9rem
          - font-weight: 600
          - color: |
              [[[
                const ppm = Number(entity.state) || 0;
                if (ppm < 900) {
                  return "rgba(0,255,24,1.0)";
                }
                if (ppm >= 900 && ppm <= 1150) {
                  return "rgba(152,255,0,1.0)";
                }
                if (ppm >= 1151 && ppm <= 1400) {
                  return "rgba(255,252,0,1.0)";
                }
                if (ppm >= 1401 && ppm <= 1600) {
                  return "rgba(255,174,0,1.0)";
                }
                if (ppm > 1600) {
                  return "rgba(255,113,0,1.0)";
                }
              ]]]
styles:
  card:
    - background-color: rgba(42,45,54,1.0)
    - aspect-ratio: 7/1
    - cursor: default
  custom_fields:
    icon:
      - position: absolute
      - top: 25%
      - left: 1%
    name:
      - position: absolute
      - top: 3%
      - left: 8%
    separation:
      - position: absolute
      - top: 17%
      - left: 0%
    value:
      - position: absolute
      - top: 26%
      - right: 15%
      - margin-right: 1%
      - transform: translateY(-50%)
    unit:
      - position: absolute
      - left: 83.5%
      - top: 20%
      - margin-left: 1%
      - transform: translateY(-50%)
    min_val:
      - position: absolute
      - top: 65%
      - left: 28%
    max_val:
      - position: absolute
      - top: 65%
      - right: 1%
    comment:
      - position: absolute
      - left: 17%
      - top: 65%
      - transform: translate(-50%, -50%)
    bar:
      - position: absolute
      - left: 63%
      - top: 55%
      - transform: translate(-50%, -50%)
    cursor:
      - position: absolute
      - top: "[[[ return 'calc(55% - 12.5px)'; ]]]"
      - left: |
          [[[
            const ppm = Number(entity.state) || 0;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((ppm - 400) / 1600) * 100, 0, 100);
            const barWidth = 250;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(63% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]

Ca m’a l’air pas mal. Pour vérifier le « cursor », tu créer une entrée de type input_number avec en valeur mini 400 et 2000 en maxi puis tu la définis comme entité dans ta carte. Tu testes avec une valeur de 400, une valeur de 2000 et une valeur de 1200 et tu vois comment réagi le curseur

Parfait j’ai testé et ça fonctionne. Plus qu’à faire la même chose et adapter cela pour l’humidité, la température et le bruit :slight_smile: je vais avoir de quoi m’occuper :slight_smile:

Tu sais si pour la température il sera possible d’ajouter le min et le max de la journée ?

merci

Si j’ai le temps, je te ferai un exemple pour humidité ou température mais avec les deux exemples postés, tu devrais t’en tirer tout seul maintenant. Prends la carte pour le CO2 pour faire une pour température et humidité et sers toi du linear_gradient de l’exemple pour humidité que j’avais posté en premier. Si tu coinces, reviens vers moi

1 « J'aime »

Bonsoir,

j’ai franchement adoré le style alors je me suis permis de le piquer pour mes dashboards.

J’ai fait une rapide adaptation pour afficher la température extérieure avec le min et le max si cela vous intéresse :

type: custom:button-card
entity: sensor.temperature_exterieure
show_state: false
show_icon: false
show_name: false
tap_action: none
double_tap_action: none
hold_action: none
custom_fields:
  icon:
    card:
      type: custom:button-card
      icon: mdi:thermometer
      styles:
        card:
          - aspect-ratio: 1/1
          - width: 20px
          - padding: 0
          - border: 1px dashed white
          - border-radius: 50%
          - background: none
        icon:
          - width: 90%
          - color: white
  name:
    card:
      type: custom:button-card
      name: Température extérieure
      styles:
        card:
          - width: 300px
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1rem
          - align-self: start
          - justify-self: start
          - color: white
          - font-weight: 500
  separation:
    card:
      type: custom:button-card
      styles:
        card:
          - width: 600px
          - height: 0px
          - padding: 0
          - border: 0px solid rgba(54,56,68,1.0)
          - border-radius: 0
          - background: none
  value:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: true
      state_display: |
        [[[
          return entity.state;
        ]]]
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        state:
          - color: white
          - font-size: 2rem
          - font-weight: 700
  unit:
    card:
      type: custom:button-card
      name: °C
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.4rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 800
  min_val:
    card:
      type: custom:button-card
      entity: sensor.temperature_min_du_jour
      show_name: false
      show_icon: false
      show_state: true
      state_display: |
        [[[
          return states['sensor.temperature_min_du_jour'].state + "°c";
        ]]]
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        state:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  max_val:
    card:
      type: custom:button-card
      entity: sensor.temperature_max_du_jour
      show_name: false
      show_icon: false
      show_state: true
      state_display: |
        [[[
          return states['sensor.temperature_max_du_jour'].state + "°c";
        ]]]
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        state:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  bar:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - height: 6px
          - width: 250px
          - border-radius: 999px
          - border: 0
          - padding: 0px
          - background: >-
              linear-gradient(to right, rgba(4, 10, 252, 1.0) 0%,
              rgba(4, 246, 252, 1.0) 25%, rgba(4, 254, 29, 1.0) 50%,
              rgba(244, 254, 4, 1.0) 75%, rgba(252, 19, 4, 1.0) 100%)
  cursor:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - width: 12px
          - height: 25px
          - border-radius: 999px
          - border: 4px solid rgba(42,45,54,1.0)
          - background-color: |
              [[[
                const temperature = Number(entity.state) || 0;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((temperature - Number(states['sensor.temperature_min_du_jour'].state)) / Number(states['sensor.temperature_max_du_jour'].state)) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [4, 10, 252]  },
                  { p: 25,  c: [4, 246, 252]   },
                  { p: 50,  c: [4, 254, 29]   },
                  { p: 75,  c: [244, 254, 4]   },
                  { p: 100, c: [252, 19, 4]   }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},1.0)`;
              ]]]
styles:
  card:
    - background-color: rgba(42,45,54,1.0)
    - aspect-ratio: 7/1
    - cursor: default
  custom_fields:
    icon:
      - position: absolute
      - top: 25%
      - left: 1%
    name:
      - position: absolute
      - top: 3%
      - left: 8%
    separation:
      - position: absolute
      - top: 17%
      - left: 0%
    value:
      - position: absolute
      - top: 26%
      - right: 15%
      - margin-right: 1%
      - transform: translateY(-50%)
    unit:
      - position: absolute
      - left: 83.5%
      - top: 20%
      - margin-left: 1%
      - transform: translateY(-50%)
    min_val:
      - position: absolute
      - top: 65%
      - left: 28%
    max_val:
      - position: absolute
      - top: 65%
      - right: 1%
    bar:
      - position: absolute
      - left: 63%
      - top: 55%
      - transform: translate(-50%, -50%)
    cursor:
      - position: absolute
      - top: "[[[ return 'calc(55% - 12.5px)'; ]]]"
      - left: |
          [[[
            const temperature = Number(entity.state) || 0;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((temperature - Number(states['sensor.temperature_min_du_jour'].state)) / Number(states['sensor.temperature_max_du_jour'].state)) * 100, 0, 100);
            const barWidth = 250;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(63% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]

Note : pour les cards min-val et max-val le champs entity ne sert à rien, c’est plus par souci de relecture que je l’ai mis.

Voici le rendu :
image

2 « J'aime »

Oui c’est ce que je pensais faire en effet. Avec celui que tu m’as donné je peux faire les autres en changeant les valeurs.

Je ferai en sorte de vous montrer le résultat.

Merci beaucoup pour l’aide

Salut, peux tu me dire où tu as trouvé les tableaux suivants :


Une petite évolution des cartes : la valeur et l’unité se déplacent avec le curseur, ajout de la valeur min. et de la valeur max. relevées sur la semaine pour la carte température.

Code de la carte « Température intérieure » :

type: custom:button-card
entity: sensor.salle_a_manger_climatisation_ble_temp
show_state: false
show_icon: false
show_name: false
tap_action: none
double_tap_action: none
hold_action: none
custom_fields:
  icon_and_name:
    card:
      type: horizontal-stack
      cards:
        - type: custom:button-card
          icon: mdi:thermometer
          styles:
            card:
              - aspect-ratio: 1/1
              - width: 50px
              - padding: 0
              - border: 4px solid rgba(167,176,205,1.0)
              - border-radius: 50%
              - background: none
            icon:
              - width: 90%
              - color: white
        - type: custom:button-card
          name: Température intérieure - Salle à manger
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
            name:
              - font-size: 1.4rem
              - justify-self: start
              - color: white
              - font-weight: 500
              - padding-top: 6%
  dividing_line:
    card:
      type: custom:button-card
      styles:
        card:
          - width: 600px
          - height: 1px
          - padding: 0
          - border: 1px solid rgba(54,56,68,1.0)
          - border-radius: 0
          - background: none
          - overflow: hidden
  value_and_unit:
    card:
      type: horizontal-stack
      cards:
        - type: custom:button-card
          show_name: false
          show_icon: false
          show_state: true
          state_display: |
            [[[
              return parseFloat(entity.state).toFixed(1);
            ]]]
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
              - margin-right: "-10%"
            state:
              - color: white
              - font-size: 1.8rem
              - font-weight: 800
        - type: custom:button-card
          name: °C
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
              - margin-left: "-10%"
            name:
              - font-size: 1.2rem
              - align-self: start
              - justify-self: start
              - color: rgba(167,176,205,1.0)
              - font-weight: 800
              - padding-top: 40%
  bar:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - height: 12px
          - width: 360px
          - border-radius: 999px
          - border: 0
          - padding: 0px
          - background: >-
              linear-gradient(to right, rgba(0,0,255,1.0) 0%,
              rgba(0,165,255,1.0) 25%, rgba(0,255,0,1.0) 50%,
              rgba(255,255,0,1.0) 62.5%, rgba(255,165,0,1.0) 75%,
              rgba(255,0,0,1.0) 100%)
  cursor:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - width: 16px
          - height: 32px
          - border-radius: 999px
          - border: 4px solid rgba(42,45,54,1.0)
          - background-color: |
              [[[
                const minTemp = 14; // MIN CHANGÉ
                const maxTemp = 30; // MAX CHANGÉ
                const temp = Number(entity.state) || minTemp;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((temp - minTemp) / (maxTemp - minTemp)) * 100, 0, 100); 
                const stops = [
                  { p: 0,   c: [0, 0, 255]    },
                  { p: 25,  c: [0, 165, 255]  },
                  { p: 50,  c: [0, 255, 0]    },
                  { p: 62.5,  c: [255, 255, 0]  },
                  { p: 75,  c: [255, 165, 0]  },
                  { p: 100, c: [255, 0, 0]    }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},1.0)`;
              ]]]
  min_val:
    card:
      type: custom:button-card
      name: 14°C
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  max_val:
    card:
      type: custom:button-card
      name: 30°C
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  comment:
    card:
      type: custom:button-card
      show_name: true
      show_icon: false
      show_state: false
      name: |
        [[[
          const temp = Number(entity.state) || 0;
          if (temp < 18) { // < 18°C
            return "Trop froid";
          }
          if (temp >= 18 && temp < 20) {
            return "Frais mais acceptable";
          }
          if (temp >= 20 && temp < 22) {
            return "Confort idéal";
          }
          if (temp >= 22 && temp < 24) {
            return "Légèrement chaud";
          }
          if (temp >= 24) {
            return "Trop chaud";
          }
          return "Indisponible";
        ]]]
      styles:
        card:
          - width: auto
          - height: auto
          - border-radius: 999px
          - border: 0
          - padding: 5px 10px 5px 10px
          - background: white
          - background-color: |
              [[[
                const minTemp = 14; 
                const maxTemp = 30;
                const temp = Number(entity.state) || minTemp;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((temp - minTemp) / (maxTemp - minTemp)) * 100, 0, 100); 
                const stops = [
                  { p: 0,   c: [0, 0, 255]    }, // 14°C
                  { p: 25,  c: [0, 165, 255]  }, // 18°C
                  { p: 50,  c: [0, 255, 0]    }, // 22°C
                  { p: 62.5,  c: [255, 255, 0]  }, // 24°C
                  { p: 75,  c: [255, 165, 0]  }, // 26°C
                  { p: 100, c: [255, 0, 0]    }  // 30°C
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},0.5)`;
              ]]]
        name:
          - font-size: 1.0rem
          - font-weight: 600
          - color: |
              [[[
                const minTemp = 14; 
                const maxTemp = 30;
                const temp = Number(entity.state) || minTemp;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((temp - minTemp) / (maxTemp - minTemp)) * 100, 0, 100); 
                const stops = [
                  { p: 0,   c: [0, 0, 255]    },
                  { p: 25,  c: [0, 165, 255]  },
                  { p: 50,  c: [0, 255, 0]    },
                  { p: 62.5,  c: [255, 255, 0]  },
                  { p: 75,  c: [255, 165, 0]  },
                  { p: 100, c: [255, 0, 0]    }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},1.0)`;
              ]]]
  min_temp_week:
    card:
      type: custom:button-card
      icon: m3of:vertical-align-bottom
      show_name: false
      show_state: true
      layout: icon_state
      state_display: |
        [[[
          return parseFloat(states['sensor.salle_a_manger_temperature_min_semaine'].state).toFixed(1) + "°";
        ]]]
      styles:
        card:
          - width: 80px
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        state:
          - font-size: 1.2rem
          - align-self: end
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 600
        icon:
          - width: 20px
          - color: rgba(167,176,205,1.0)
  max_temp_week:
    card:
      type: custom:button-card
      icon: m3of:vertical-align-top
      show_name: false
      show_state: true
      layout: state_icon
      state_display: |
        [[[
          return parseFloat(states['sensor.salle_a_manger_temperature_max_semaine'].state).toFixed(1) + "°";
        ]]]
      styles:
        card:
          - width: 80px
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        state:
          - font-size: 1.2rem
          - align-self: end
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 600
          - padding-right: 100px
        icon:
          - width: 20px
          - color: rgba(167,176,205,1.0)
styles:
  card:
    - background-color: rgba(42,45,54,1.0)
    - aspect-ratio: 2.8/1
    - cursor: default
  custom_fields:
    icon_and_name:
      - position: absolute
      - top: 2%
      - left: 1%
    dividing_line:
      - position: absolute
      - top: 35%
      - left: 0%
    value_and_unit:
      - position: absolute
      - top: 38%
      - left: |
          [[[
            const value = Number(entity.state) || 0;
            const min = 14;
            const max = 30;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = 360;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
      - transform: translateX(-50%)
    bar:
      - position: absolute
      - left: 50%
      - top: 65%
      - transform: translate(-50%, -50%)
    cursor:
      - position: absolute
      - top: "[[[ return 'calc(65% - 16px)'; ]]]"
      - left: |
          [[[
            const value = Number(entity.state) || 0;
            const min = 14;
            const max = 30;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = 360;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
    min_val:
      - position: absolute
      - bottom: 17%
      - left: 12%
    max_val:
      - position: absolute
      - bottom: 17%
      - right: 10%
    comment:
      - position: absolute
      - left: 50%
      - bottom: "-3%"
      - transform: translate(-50%, -50%)
    min_temp_week:
      - position: absolute
      - top: 40%
      - left: 5%
    max_temp_week:
      - position: absolute
      - top: 40%
      - right: 2%

Code de la carte « Niveau de CO2 » :

type: custom:button-card
entity: input_number.ppm
show_state: false
show_icon: false
show_name: false
tap_action: none
double_tap_action: none
hold_action: none
custom_fields:
  icon_and_name:
    card:
      type: horizontal-stack
      cards:
        - type: custom:button-card
          icon: mdi:molecule-co2
          styles:
            card:
              - aspect-ratio: 1/1
              - width: 50px
              - padding: 0
              - border: 4px solid rgba(167,176,205,1.0)
              - border-radius: 50%
              - background: none
            icon:
              - width: 90%
              - color: white
        - type: custom:button-card
          name: Niveau de CO<sub style="font-size:1.0rem;">2</sub> intérieur
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
            name:
              - font-size: 1.4rem
              - justify-self: start
              - color: white
              - font-weight: 500
              - padding-top: 10%
  dividing_line:
    card:
      type: custom:button-card
      styles:
        card:
          - width: 600px
          - height: 1px
          - padding: 0
          - border: 1px solid rgba(54,56,68,1.0)
          - border-radius: 0
          - background: none
          - overflow: hidden
  value_and_unit:
    card:
      type: horizontal-stack
      cards:
        - type: custom:button-card
          show_name: false
          show_icon: false
          show_state: true
          state_display: |
            [[[
              return parseFloat(entity.state).toFixed(0);
            ]]]
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
              - margin-right: "-10%"
            state:
              - color: white
              - font-size: 1.8rem
              - font-weight: 800
        - type: custom:button-card
          name: ppm
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
              - margin-left: "-10%"
            name:
              - font-size: 1.2rem
              - align-self: start
              - justify-self: start
              - color: rgba(167,176,205,1.0)
              - font-weight: 800
              - padding-top: 20%
  bar:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - height: 12px
          - width: 360px
          - border-radius: 999px
          - border: 0
          - padding: 0px
          - background: >-
              linear-gradient(to right, rgba(0,255,0,1.0) 0%,
              rgba(165,255,0,1.0) 35%, rgba(255,255,0,1.0) 55%,
              rgba(255,165,0,1.0) 85%, rgba(255,0,0,1.0) 100%)
  cursor:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - width: 16px
          - height: 32px
          - border-radius: 999px
          - border: 4px solid rgba(42,45,54,1.0)
          - background-color: |
              [[[
                const ppm = Number(entity.state) || 0;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((ppm - 400) / 1600) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [0,   255, 0]  },
                  { p: 40,  c: [165, 255, 0]   },
                  { p: 55,  c: [255, 255, 0]   },
                  { p: 75,  c: [255, 165, 0]   },
                  { p: 100, c: [255, 0,   0]   }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},1.0)`;
              ]]]
  min_val:
    card:
      type: custom:button-card
      name: "400"
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  max_val:
    card:
      type: custom:button-card
      name: "2000"
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  comment:
    card:
      type: custom:button-card
      show_name: true
      show_icon: false
      show_state: false
      name: |
        [[[
          const ppm = Number(entity.state) || 0;
          if (ppm < 900) {
            return "Excellent";
          }
          if (ppm >= 900 && ppm <= 1150) {
            return "Bon";
          }
          if (ppm >= 1151 && ppm <= 1400) {
            return "Acceptable";
          }
          if (ppm >= 1401 && ppm <= 1600) {
            return "Mauvais";
          }
          if (ppm > 1600) {
            return "Malsain";
          }
        ]]]
      styles:
        card:
          - width: auto
          - height: auto
          - border-radius: 999px
          - border: 0
          - padding: 5px 10px 5px 10px
          - background: white
          - background-color: |
              [[[
                const ppm = Number(entity.state) || 0;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((ppm - 400) / 1600) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [0,   255, 0]  },
                  { p: 40,  c: [165, 255, 0]   },
                  { p: 55,  c: [255, 255, 0]   },
                  { p: 75,  c: [255, 165, 0]   },
                  { p: 100, c: [255, 0,   0]   }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},0.5)`;
              ]]]
        name:
          - font-size: 1.0rem
          - font-weight: 600
          - color: |
              [[[
                const ppm = Number(entity.state) || 0;
                if (ppm < 900) {
                  return "rgba(0,255,0,1.0)";
                }
                if (ppm >= 900 && ppm <= 1150) {
                  return "rgba(165,255,0,1.0)";
                }
                if (ppm >= 1151 && ppm <= 1400) {
                  return "rgba(255,255,0,1.0)";
                }
                if (ppm >= 1401 && ppm <= 1600) {
                  return "rgba(255,165,0,1.0)";
                }
                if (ppm > 1600) {
                  return "rgba(255,0,0,1.0)";
                }
              ]]]
styles:
  card:
    - background-color: rgba(42,45,54,1.0)
    - aspect-ratio: 2.8/1
    - cursor: default
  custom_fields:
    icon_and_name:
      - position: absolute
      - top: 2%
      - left: 1%
    dividing_line:
      - position: absolute
      - top: 35%
      - left: 0%
    value_and_unit:
      - position: absolute
      - top: 38%
      - left: |
          [[[
            const ppm = Number(entity.state) || 0;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((ppm - 400) / 1600) * 100, 0, 100);
            const barWidth = 360;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
      - transform: translateX(-50%)
    bar:
      - position: absolute
      - left: 50%
      - top: 65%
      - transform: translate(-50%, -50%)
    cursor:
      - position: absolute
      - top: "[[[ return 'calc(65% - 16px)'; ]]]"
      - left: |
          [[[
            const ppm = Number(entity.state) || 0;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((ppm - 400) / 1600) * 100, 0, 100);
            const barWidth = 360;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
    min_val:
      - position: absolute
      - bottom: 17%
      - left: 10%
    max_val:
      - position: absolute
      - bottom: 17%
      - right: 9%
    comment:
      - position: absolute
      - left: 50%
      - bottom: "-3%"
      - transform: translate(-50%, -50%)

Code de la carte « Humidité intérieure » :

type: custom:button-card
entity: sensor.salle_a_manger_climatisation_ble_humidity
show_state: false
show_icon: false
show_name: false
tap_action: none
double_tap_action: none
hold_action: none
custom_fields:
  icon_and_name:
    card:
      type: horizontal-stack
      cards:
        - type: custom:button-card
          icon: mdi:water
          styles:
            card:
              - aspect-ratio: 1/1
              - width: 50px
              - padding: 0
              - border: 4px solid rgba(167,176,205,1.0)
              - border-radius: 50%
              - background: none
            icon:
              - width: 90%
              - color: white
        - type: custom:button-card
          name: Humidité intérieure - Salle à manger
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
            name:
              - font-size: 1.4rem
              - justify-self: start
              - color: white
              - font-weight: 500
              - padding-top: 7%
  dividing_line:
    card:
      type: custom:button-card
      styles:
        card:
          - width: 600px
          - height: 1px
          - padding: 0
          - border: 1px solid rgba(54,56,68,1.0)
          - border-radius: 0
          - background: none
          - overflow: hidden
  value_and_unit:
    card:
      type: horizontal-stack
      cards:
        - type: custom:button-card
          show_name: false
          show_icon: false
          show_state: true
          state_display: |
            [[[
              return parseFloat(entity.state).toFixed(1);
            ]]]
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
              - margin-right: "-10%"
            state:
              - color: white
              - font-size: 1.8rem
              - font-weight: 800
        - type: custom:button-card
          name: "%"
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
              - margin-left: "-10%"
            name:
              - font-size: 1.2rem
              - align-self: start
              - justify-self: start
              - color: rgba(167,176,205,1.0)
              - font-weight: 800
              - padding-top: 50%
  bar:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - height: 12px
          - width: 360px
          - border-radius: 999px
          - border: 0
          - padding: 0px
          - background: >-
              linear-gradient(to right, rgba(255,0,0,1.0) 0%,
              rgba(255,165,0,1.0) 10%, rgba(255,255,0,1.0) 30%,
              rgba(0,255,0,1.0) 50%, rgba(255,255,0,1.0) 70%,
              rgba(255,165,0,1.0) 90%, rgba(255,0,0,1.0) 100%)
  cursor:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - width: 16px
          - height: 32px
          - border-radius: 999px
          - border: 4px solid rgba(42,45,54,1.0)
          - background-color: |
              [[[
                const humidity = Number(entity.state) || 0;
                const minHumidity = 0;
                const maxHumidity = 100;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((humidity - minHumidity) / (maxHumidity - minHumidity)) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [255, 0,   0]  },
                  { p: 30,  c: [255, 165, 0]  },
                  { p: 40,  c: [255, 255, 0]  },
                  { p: 50,  c: [0,   255, 0]  },
                  { p: 60,  c: [255, 255, 0]  },
                  { p: 70,  c: [255, 165, 0]  },
                  { p: 100, c: [255, 0,   0]  }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},1.0)`;
              ]]]
  min_val:
    card:
      type: custom:button-card
      name: "0"
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  max_val:
    card:
      type: custom:button-card
      name: "100"
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  comment:
    card:
      type: custom:button-card
      show_name: true
      show_icon: false
      show_state: false
      name: |
        [[[
          const humidity = Number(entity.state) || 0;
          if (humidity < 30) {
            return "Trop sec";
          }
          if (humidity >= 30 && humidity < 40) {
            return "Sec mais acceptable";
          }
          if (humidity >= 40 && humidity < 60) {
            return "Zone idéale de confort";
          }
          if (humidity >= 60 && humidity < 70) {
            return "Humide mais tolérable";
          }
          if (humidity >= 70) {
            return "Trop humide";
          }
        ]]]
      styles:
        card:
          - width: auto
          - height: auto
          - border-radius: 999px
          - border: 0
          - padding: 5px 10px 5px 10px
          - background: white
          - background-color: |
              [[[
                const humidity = Number(entity.state) || 0;
                const minHumidity = 0;
                const maxHumidity = 100;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((humidity - minHumidity) / (maxHumidity - minHumidity)) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [255, 0,   0]  },
                  { p: 30,  c: [255, 165, 0]  },
                  { p: 40,  c: [255, 255, 0]  },
                  { p: 50,  c: [0,   255, 0]  },
                  { p: 60,  c: [255, 255, 0]  },
                  { p: 70,  c: [255, 165, 0]  },
                  { p: 100, c: [255, 0,   0]  }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},0.5)`;
              ]]]
        name:
          - font-size: 1.0rem
          - font-weight: 600
          - color: |
              [[[
                const humidity = Number(entity.state) || 0;
                const minHumidity = 0;
                const maxHumidity = 100;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((humidity - minHumidity) / (maxHumidity - minHumidity)) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [255, 0,   0]  },
                  { p: 30,  c: [255, 165, 0]  },
                  { p: 40,  c: [255, 255, 0]  },
                  { p: 50,  c: [0,   255, 0]  },
                  { p: 60,  c: [255, 255, 0]  },
                  { p: 70,  c: [255, 165, 0]  },
                  { p: 100, c: [255, 0,   0]  }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},1.0)`;
              ]]]
styles:
  card:
    - background-color: rgba(42,45,54,1.0)
    - aspect-ratio: 2.8/1
    - cursor: default
  custom_fields:
    icon_and_name:
      - position: absolute
      - top: 2%
      - left: 1%
    dividing_line:
      - position: absolute
      - top: 35%
      - left: 0%
    value_and_unit:
      - position: absolute
      - top: 38%
      - left: |
          [[[
            const value = Number(entity.state) || 0;
            const min = 0;
            const max = 100;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = 360;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
      - transform: translateX(-50%)
    bar:
      - position: absolute
      - left: 50%
      - top: 65%
      - transform: translate(-50%, -50%)
    cursor:
      - position: absolute
      - top: "[[[ return 'calc(65% - 16px)'; ]]]"
      - left: |
          [[[
            const value = Number(entity.state) || 0;
            const min = 0;
            const max = 100;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = 360;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
    min_val:
      - position: absolute
      - bottom: 17%
      - left: 12%
    max_val:
      - position: absolute
      - bottom: 17%
      - right: 10%
    comment:
      - position: absolute
      - left: 50%
      - bottom: "-3%"
      - transform: translate(-50%, -50%)
5 « J'aime »

Bonjour,

Merci pour ces belles cartes, sur PC, ça en jette! Mais sur smartphone, les cartes sont “écrasées” comment y remédier ?

Bonjour,

Tu trouveras comment faire dans cet article : [Article] Aller plus loin avec la carte custom:button-card
Je te ferai un exemple plus tard dans l’après-midi si tu veux.

1 « J'aime »

bonjour,

ou as tu recupéré les entitées temperature min et température max?

je ne les ai plus avec l’integration openweathermap et météo france non plus

merci

C’est bon, j’ai simplifié un max pour l’adapter

voici la version responsive (adaptation en fonction de la largeur de l’écran sur lequel sont affichées les cartes) :

Code carte « Humidité intérieure » :

type: custom:button-card
entity: sensor.salle_a_manger_climatisation_ble_humidity
show_state: false
show_icon: false
show_name: false
tap_action: none
double_tap_action: none
hold_action: none
custom_fields:
  icon_and_name:
    card:
      type: horizontal-stack
      cards:
        - type: custom:button-card
          icon: mdi:water
          styles:
            card:
              - aspect-ratio: 1/1
              - width: 50px
              - padding: 0
              - border: 4px solid rgba(167,176,205,1.0)
              - border-radius: 50%
              - background: none
            icon:
              - width: 90%
              - color: white
        - type: custom:button-card
          name: |
            [[[
              if (window.innerWidth <= 600) {
                return 'Humidité intérieure<br>&nbsp;&nbsp;Salle à manger';
              }
              return 'Humidité intérieure - Salle à manger';
            ]]]
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
            name:
              - font-size: 1.4rem
              - justify-self: start
              - text-align: left
              - color: white
              - font-weight: 500
              - padding-top: "[[[ return window.innerWidth <= 600 ? '2.5%' : '7%' ]]]"
  dividing_line:
    card:
      type: custom:button-card
      styles:
        card:
          - width: 600px
          - height: 1px
          - padding: 0
          - border: 1px solid rgba(54,56,68,1.0)
          - border-radius: 0
          - background: none
          - overflow: hidden
  value_and_unit:
    card:
      type: horizontal-stack
      cards:
        - type: custom:button-card
          show_name: false
          show_icon: false
          show_state: true
          state_display: |
            [[[
              return parseFloat(entity.state).toFixed(1);
            ]]]
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
              - margin-right: "-10%"
            state:
              - color: white
              - font-size: 1.8rem
              - font-weight: 800
        - type: custom:button-card
          name: "%"
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
              - margin-left: "-10%"
            name:
              - font-size: 1.2rem
              - align-self: start
              - justify-self: start
              - color: rgba(167,176,205,1.0)
              - font-weight: 800
              - padding-top: 50%
  bar:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - height: 12px
          - width: "[[[ return window.innerWidth <= 600 ? '300px' : '360px' ]]]"
          - border-radius: 999px
          - border: 0
          - padding: 0px
          - background: >-
              linear-gradient(to right, rgba(255,0,0,1.0) 0%,
              rgba(255,165,0,1.0) 10%, rgba(255,255,0,1.0) 30%,
              rgba(0,255,0,1.0) 50%, rgba(255,255,0,1.0) 70%,
              rgba(255,165,0,1.0) 90%, rgba(255,0,0,1.0) 100%)
  cursor:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - width: 16px
          - height: 32px
          - border-radius: 999px
          - border: 4px solid rgba(42,45,54,1.0)
          - background-color: |
              [[[
                const humidity = Number(entity.state) || 0;
                const minHumidity = 0;
                const maxHumidity = 100;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((humidity - minHumidity) / (maxHumidity - minHumidity)) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [255, 0,   0]  },
                  { p: 30,  c: [255, 165, 0]  },
                  { p: 40,  c: [255, 255, 0]  },
                  { p: 50,  c: [0,   255, 0]  },
                  { p: 60,  c: [255, 255, 0]  },
                  { p: 70,  c: [255, 165, 0]  },
                  { p: 100, c: [255, 0,   0]  }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},1.0)`;
              ]]]
  min_val:
    card:
      type: custom:button-card
      name: "0"
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  max_val:
    card:
      type: custom:button-card
      name: "100"
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  comment:
    card:
      type: custom:button-card
      show_name: true
      show_icon: false
      show_state: false
      name: |
        [[[
          const humidity = Number(entity.state) || 0;
          if (humidity < 30) {
            return "Trop sec";
          }
          if (humidity >= 30 && humidity < 40) {
            return "Sec mais acceptable";
          }
          if (humidity >= 40 && humidity < 60) {
            return "Zone idéale de confort";
          }
          if (humidity >= 60 && humidity < 70) {
            return "Humide mais tolérable";
          }
          if (humidity >= 70) {
            return "Trop humide";
          }
        ]]]
      styles:
        card:
          - width: auto
          - height: auto
          - border-radius: 999px
          - border: 0
          - padding: 5px 10px 5px 10px
          - background: white
          - background-color: |
              [[[
                const humidity = Number(entity.state) || 0;
                const minHumidity = 0;
                const maxHumidity = 100;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((humidity - minHumidity) / (maxHumidity - minHumidity)) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [255, 0,   0]  },
                  { p: 30,  c: [255, 165, 0]  },
                  { p: 40,  c: [255, 255, 0]  },
                  { p: 50,  c: [0,   255, 0]  },
                  { p: 60,  c: [255, 255, 0]  },
                  { p: 70,  c: [255, 165, 0]  },
                  { p: 100, c: [255, 0,   0]  }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},0.5)`;
              ]]]
        name:
          - font-size: 1.0rem
          - font-weight: 600
          - color: |
              [[[
                const humidity = Number(entity.state) || 0;
                const minHumidity = 0;
                const maxHumidity = 100;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((humidity - minHumidity) / (maxHumidity - minHumidity)) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [255, 0,   0]  },
                  { p: 30,  c: [255, 165, 0]  },
                  { p: 40,  c: [255, 255, 0]  },
                  { p: 50,  c: [0,   255, 0]  },
                  { p: 60,  c: [255, 255, 0]  },
                  { p: 70,  c: [255, 165, 0]  },
                  { p: 100, c: [255, 0,   0]  }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},1.0)`;
              ]]]
styles:
  card:
    - background-color: rgba(42,45,54,1.0)
    - aspect-ratio: "[[[ return window.innerWidth <= 600 ? '2.2/1' : '2.8/1' ]]]"
    - cursor: default
  custom_fields:
    icon_and_name:
      - position: absolute
      - top: 2%
      - left: 1%
    dividing_line:
      - position: absolute
      - top: 35%
      - left: 0%
    value_and_unit:
      - position: absolute
      - top: 38%
      - left: |
          [[[
            const value = Number(entity.state) || 0;
            const min = 0;
            const max = 100;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = 360;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
      - transform: translateX(-50%)
    bar:
      - position: absolute
      - left: 50%
      - top: 65%
      - transform: translate(-50%, -50%)
    cursor:
      - position: absolute
      - top: "[[[ return 'calc(65% - 16px)'; ]]]"
      - left: |
          [[[
            const value = Number(entity.state) || 0;
            const min = 0;
            const max = 100;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = 360;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
    min_val:
      - position: absolute
      - bottom: 17%
      - left: "[[[ return window.innerWidth <= 600 ? '9%' : '12%' ]]]"
    max_val:
      - position: absolute
      - bottom: 17%
      - right: "[[[ return window.innerWidth <= 600 ? '7%' : '10%' ]]]"
    comment:
      - position: absolute
      - left: 50%
      - bottom: "-3%"
      - transform: translate(-50%, -50%)

Code carte « Température intérieure » :

type: custom:button-card
entity: sensor.salle_a_manger_climatisation_ble_temp
show_state: false
show_icon: false
show_name: false
tap_action: none
double_tap_action: none
hold_action: none
custom_fields:
  icon_and_name:
    card:
      type: horizontal-stack
      cards:
        - type: custom:button-card
          icon: mdi:thermometer
          styles:
            card:
              - aspect-ratio: 1/1
              - width: 50px
              - padding: 0
              - border: 4px solid rgba(167,176,205,1.0)
              - border-radius: 50%
              - background: none
            icon:
              - width: 90%
              - color: white
        - type: custom:button-card
          name: |
            [[[
              if (window.innerWidth <= 600) {
                return 'Température intérieure<br>&nbsp;&nbsp;Salle à manger';
              }
              return 'Température intérieure - Salle à manger';
            ]]]
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
            name:
              - font-size: 1.4rem
              - justify-self: start
              - text-align: left
              - color: white
              - font-weight: 500
              - padding-top: "[[[ return window.innerWidth <= 600 ? '2%' : '6%' ]]]"
  dividing_line:
    card:
      type: custom:button-card
      styles:
        card:
          - width: 600px
          - height: 1px
          - padding: 0
          - border: 1px solid rgba(54,56,68,1.0)
          - border-radius: 0
          - background: none
          - overflow: hidden
  value_and_unit:
    card:
      type: horizontal-stack
      cards:
        - type: custom:button-card
          show_name: false
          show_icon: false
          show_state: true
          state_display: |
            [[[
              return parseFloat(entity.state).toFixed(1);
            ]]]
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
              - margin-right: "-10%"
            state:
              - color: white
              - font-size: 1.8rem
              - font-weight: 800
        - type: custom:button-card
          name: °C
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
              - margin-left: "-10%"
            name:
              - font-size: 1.2rem
              - align-self: start
              - justify-self: start
              - color: rgba(167,176,205,1.0)
              - font-weight: 800
              - padding-top: 40%
  bar:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - height: 12px
          - width: "[[[ return window.innerWidth <= 600 ? '300px' : '360px' ]]]"
          - border-radius: 999px
          - border: 0
          - padding: 0px
          - background: >-
              linear-gradient(to right, rgba(0,0,255,1.0) 0%,
              rgba(0,165,255,1.0) 25%, rgba(0,255,0,1.0) 50%,
              rgba(255,255,0,1.0) 62.5%, rgba(255,165,0,1.0) 75%,
              rgba(255,0,0,1.0) 100%)
  cursor:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - width: 16px
          - height: 32px
          - border-radius: 999px
          - border: 4px solid rgba(42,45,54,1.0)
          - background-color: |
              [[[
                const minTemp = 14; // MIN CHANGÉ
                const maxTemp = 30; // MAX CHANGÉ
                const temp = Number(entity.state) || minTemp;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((temp - minTemp) / (maxTemp - minTemp)) * 100, 0, 100); 
                const stops = [
                  { p: 0,   c: [0, 0, 255]    },
                  { p: 25,  c: [0, 165, 255]  },
                  { p: 50,  c: [0, 255, 0]    },
                  { p: 62.5,  c: [255, 255, 0]  },
                  { p: 75,  c: [255, 165, 0]  },
                  { p: 100, c: [255, 0, 0]    }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},1.0)`;
              ]]]
  min_val:
    card:
      type: custom:button-card
      name: 14°C
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  max_val:
    card:
      type: custom:button-card
      name: 30°C
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  comment:
    card:
      type: custom:button-card
      show_name: true
      show_icon: false
      show_state: false
      name: |
        [[[
          const temp = Number(entity.state) || 0;
          if (temp < 18) { // < 18°C
            return "Trop froid";
          }
          if (temp >= 18 && temp < 20) {
            return "Frais mais acceptable";
          }
          if (temp >= 20 && temp < 22) {
            return "Confort idéal";
          }
          if (temp >= 22 && temp < 24) {
            return "Légèrement chaud";
          }
          if (temp >= 24) {
            return "Trop chaud";
          }
          return "Indisponible";
        ]]]
      styles:
        card:
          - width: auto
          - height: auto
          - border-radius: 999px
          - border: 0
          - padding: 5px 10px 5px 10px
          - background: white
          - background-color: |
              [[[
                const minTemp = 14; 
                const maxTemp = 30;
                const temp = Number(entity.state) || minTemp;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((temp - minTemp) / (maxTemp - minTemp)) * 100, 0, 100); 
                const stops = [
                  { p: 0,   c: [0, 0, 255]    }, // 14°C
                  { p: 25,  c: [0, 165, 255]  }, // 18°C
                  { p: 50,  c: [0, 255, 0]    }, // 22°C
                  { p: 62.5,  c: [255, 255, 0]  }, // 24°C
                  { p: 75,  c: [255, 165, 0]  }, // 26°C
                  { p: 100, c: [255, 0, 0]    }  // 30°C
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},0.5)`;
              ]]]
        name:
          - font-size: 1.0rem
          - font-weight: 600
          - color: |
              [[[
                const minTemp = 14; 
                const maxTemp = 30;
                const temp = Number(entity.state) || minTemp;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((temp - minTemp) / (maxTemp - minTemp)) * 100, 0, 100); 
                const stops = [
                  { p: 0,   c: [0, 0, 255]    },
                  { p: 25,  c: [0, 165, 255]  },
                  { p: 50,  c: [0, 255, 0]    },
                  { p: 62.5,  c: [255, 255, 0]  },
                  { p: 75,  c: [255, 165, 0]  },
                  { p: 100, c: [255, 0, 0]    }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},1.0)`;
              ]]]
  min_temp_week:
    card:
      type: custom:button-card
      icon: m3of:vertical-align-bottom
      show_name: false
      show_state: true
      layout: icon_state
      state_display: |
        [[[
          return parseFloat(states['sensor.salle_a_manger_temperature_min_semaine'].state).toFixed(1) + "°";
        ]]]
      styles:
        card:
          - width: 80px
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        state:
          - font-size: 1.2rem
          - align-self: end
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 600
        icon:
          - width: 20px
          - color: rgba(167,176,205,1.0)
  max_temp_week:
    card:
      type: custom:button-card
      icon: m3of:vertical-align-top
      show_name: false
      show_state: true
      layout: state_icon
      state_display: |
        [[[
          return parseFloat(states['sensor.salle_a_manger_temperature_max_semaine'].state).toFixed(1) + "°";
        ]]]
      styles:
        card:
          - width: 80px
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        state:
          - font-size: 1.2rem
          - align-self: end
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 600
          - padding-right: 100px
        icon:
          - width: 20px
          - color: rgba(167,176,205,1.0)
styles:
  card:
    - background-color: rgba(42,45,54,1.0)
    - aspect-ratio: "[[[ return window.innerWidth <= 600 ? '2.2/1' : '2.8/1' ]]]"
    - cursor: default
  custom_fields:
    icon_and_name:
      - position: absolute
      - top: 2%
      - left: 1%
    dividing_line:
      - position: absolute
      - top: 35%
      - left: 0%
    value_and_unit:
      - position: absolute
      - top: 38%
      - left: |
          [[[
            const value = Number(entity.state) || 0;
            const min = 14;
            const max = 30;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = 360;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
      - transform: translateX(-50%)
    bar:
      - position: absolute
      - left: 50%
      - top: 65%
      - transform: translate(-50%, -50%)
    cursor:
      - position: absolute
      - top: "[[[ return 'calc(65% - 16px)'; ]]]"
      - left: |
          [[[
            const value = Number(entity.state) || 0;
            const min = 14;
            const max = 30;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = 360;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
    min_val:
      - position: absolute
      - bottom: 17%
      - left: "[[[ return window.innerWidth <= 600 ? '8%' : '12%' ]]]"
    max_val:
      - position: absolute
      - bottom: 17%
      - right: "[[[ return window.innerWidth <= 600 ? '7%' : '10%' ]]]"
    comment:
      - position: absolute
      - left: 50%
      - bottom: "-3%"
      - transform: translate(-50%, -50%)
    min_temp_week:
      - position: absolute
      - top: 40%
      - left: 5%
    max_temp_week:
      - position: absolute
      - top: 40%
      - right: 2%

Code carte « Niveau de CO2 » :

type: custom:button-card
entity: input_number.ppm
show_state: false
show_icon: false
show_name: false
tap_action: none
double_tap_action: none
hold_action: none
custom_fields:
  icon_and_name:
    card:
      type: horizontal-stack
      cards:
        - type: custom:button-card
          icon: mdi:molecule-co2
          styles:
            card:
              - aspect-ratio: 1/1
              - width: 50px
              - padding: 0
              - border: 4px solid rgba(167,176,205,1.0)
              - border-radius: 50%
              - background: none
            icon:
              - width: 90%
              - color: white
        - type: custom:button-card
          name: Niveau de CO<sub style="font-size:1.0rem;">2</sub> intérieur
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
            name:
              - font-size: 1.4rem
              - justify-self: start
              - color: white
              - font-weight: 500
              - padding-top: 10%
  dividing_line:
    card:
      type: custom:button-card
      styles:
        card:
          - width: 600px
          - height: 1px
          - padding: 0
          - border: 1px solid rgba(54,56,68,1.0)
          - border-radius: 0
          - background: none
          - overflow: hidden
  value_and_unit:
    card:
      type: horizontal-stack
      cards:
        - type: custom:button-card
          show_name: false
          show_icon: false
          show_state: true
          state_display: |
            [[[
              return parseFloat(entity.state).toFixed(0);
            ]]]
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
              - margin-right: "-10%"
            state:
              - color: white
              - font-size: 1.8rem
              - font-weight: 800
        - type: custom:button-card
          name: ppm
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - border-radius: 0
              - background: none
              - margin-left: "-10%"
            name:
              - font-size: 1.2rem
              - align-self: start
              - justify-self: start
              - color: rgba(167,176,205,1.0)
              - font-weight: 800
              - padding-top: 20%
  bar:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - height: 12px
          - width: "[[[ return window.innerWidth <= 600 ? '300px' : '360px' ]]]"
          - border-radius: 999px
          - border: 0
          - padding: 0px
          - background: >-
              linear-gradient(to right, rgba(0,255,0,1.0) 0%,
              rgba(165,255,0,1.0) 35%, rgba(255,255,0,1.0) 55%,
              rgba(255,165,0,1.0) 85%, rgba(255,0,0,1.0) 100%)
  cursor:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - width: 16px
          - height: 32px
          - border-radius: 999px
          - border: 4px solid rgba(42,45,54,1.0)
          - background-color: |
              [[[
                const ppm = Number(entity.state) || 0;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((ppm - 400) / 1600) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [0,   255, 0]  },
                  { p: 40,  c: [165, 255, 0]   },
                  { p: 55,  c: [255, 255, 0]   },
                  { p: 75,  c: [255, 165, 0]   },
                  { p: 100, c: [255, 0,   0]   }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},1.0)`;
              ]]]
  min_val:
    card:
      type: custom:button-card
      name: "400"
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  max_val:
    card:
      type: custom:button-card
      name: "2000"
      styles:
        card:
          - width: auto
          - padding: 0
          - border: none
          - border-radius: 0
          - background: none
        name:
          - font-size: 1.0rem
          - align-self: start
          - justify-self: start
          - color: rgba(167,176,205,1.0)
          - font-weight: 400
  comment:
    card:
      type: custom:button-card
      show_name: true
      show_icon: false
      show_state: false
      name: |
        [[[
          const ppm = Number(entity.state) || 0;
          if (ppm < 900) {
            return "Excellent";
          }
          if (ppm >= 900 && ppm <= 1150) {
            return "Bon";
          }
          if (ppm >= 1151 && ppm <= 1400) {
            return "Acceptable";
          }
          if (ppm >= 1401 && ppm <= 1600) {
            return "Mauvais";
          }
          if (ppm > 1600) {
            return "Malsain";
          }
        ]]]
      styles:
        card:
          - width: auto
          - height: auto
          - border-radius: 999px
          - border: 0
          - padding: 5px 10px 5px 10px
          - background: white
          - background-color: |
              [[[
                const ppm = Number(entity.state) || 0;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((ppm - 400) / 1600) * 100, 0, 100);
                const stops = [
                  { p: 0,   c: [0,   255, 0]  },
                  { p: 40,  c: [165, 255, 0]   },
                  { p: 55,  c: [255, 255, 0]   },
                  { p: 75,  c: [255, 165, 0]   },
                  { p: 100, c: [255, 0,   0]   }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1];
                const b = stops[i];
                const t = (percent - a.p) / (b.p - a.p);
                const lerp = (x, y, t) => Math.round(x + (y - x) * t);
                const r = lerp(a.c[0], b.c[0], t);
                const g = lerp(a.c[1], b.c[1], t);
                const bcol = lerp(a.c[2], b.c[2], t);
                return `rgba(${r},${g},${bcol},0.5)`;
              ]]]
        name:
          - font-size: 1.0rem
          - font-weight: 600
          - color: |
              [[[
                const ppm = Number(entity.state) || 0;
                if (ppm < 900) {
                  return "rgba(0,255,0,1.0)";
                }
                if (ppm >= 900 && ppm <= 1150) {
                  return "rgba(165,255,0,1.0)";
                }
                if (ppm >= 1151 && ppm <= 1400) {
                  return "rgba(255,255,0,1.0)";
                }
                if (ppm >= 1401 && ppm <= 1600) {
                  return "rgba(255,165,0,1.0)";
                }
                if (ppm > 1600) {
                  return "rgba(255,0,0,1.0)";
                }
              ]]]
styles:
  card:
    - background-color: rgba(42,45,54,1.0)
    - aspect-ratio: "[[[ return window.innerWidth <= 600 ? '2.2/1' : '2.8/1' ]]]"
    - cursor: default
  custom_fields:
    icon_and_name:
      - position: absolute
      - top: 2%
      - left: 1%
    dividing_line:
      - position: absolute
      - top: 35%
      - left: 0%
    value_and_unit:
      - position: absolute
      - top: 38%
      - left: |
          [[[
            const ppm = Number(entity.state) || 0;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((ppm - 400) / 1600) * 100, 0, 100);
            const barWidth = 360;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
      - transform: translateX(-50%)
    bar:
      - position: absolute
      - left: 50%
      - top: 65%
      - transform: translate(-50%, -50%)
    cursor:
      - position: absolute
      - top: "[[[ return 'calc(65% - 16px)'; ]]]"
      - left: |
          [[[
            const ppm = Number(entity.state) || 0;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((ppm - 400) / 1600) * 100, 0, 100);
            const barWidth = 360;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
    min_val:
      - position: absolute
      - bottom: 17%
      - left: "[[[ return window.innerWidth <= 600 ? '7.5%' : '10%' ]]]"
    max_val:
      - position: absolute
      - bottom: 17%
      - right: "[[[ return window.innerWidth <= 600 ? '6.5%' : '9%' ]]]"
    comment:
      - position: absolute
      - left: 50%
      - bottom: "-3%"
      - transform: translate(-50%, -50%)
6 « J'aime »

J’ai demandé à Copilot de me donner les mêmes infos que le tableau du CO2 de Netatmo, il m’a dit ne pas avoir trouvé mais que Netatmo se basait sur des « normes » internationale d’après les pages qu’il avait consulté.

Wahou c’est top. Merci beaucoup, je vais regarder ça, je ne pouvais pas aujourd’hui.

Merci à vous tous pour vos partages, ça fait plein de trucs à utiliser et adapter ! :+1::+1::+1:

2 « J'aime »

Super travail et merci pour le partage.

J’ai un lien sur les normes et la graduation des polluants dans l’air si ça peut aider

https://enless-wireless.fr/mesure-qualite-air-interieur-batiment/

bonjour, superbe travail collaboratif? je retiens ces jauges , pour un futur.

Je vais me servir de l’intégration AtmoFrance pour faire une jauge « IQA » et une jauge « Pollens ». Si tu as des besoins spécifiques, n’hésites pas à demander :wink:

1 « J'aime »

Oui, @Xris a demandé et j’ai fait :grin:

5 « J'aime »