Faire des jauges de ce type

Je suis en train d’adapter pour mon capteur température, mais je crois que j’ai un problème au niveau du curseur, je ne dois pas avoir tout bien modifié pour correspondre à ce que je fais, je teste avec un input pour l’instant, je ne vois pas où j’ai raté, j’ai modifié pour être un peu plus proche de mon Netatmo et de chez moi, l’exemple d’affichage ici est celui sur mon ordinateur mais sur mon smartphone la mise en page correspond bien, par contre je n’arrive pas à intégrer le min et max, mais je crois que netatmo ne le remonte pas :

type: custom:button-card
entity: input_number.test
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 solid white
          - border-radius: 50%
          - background: none
        icon:
          - width: 90%
          - color: white
  name:
    card:
      type: custom:button-card
      name: Netatmo Salon Température
      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
  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(255,0,0,1.0) 0%,
              rgba(255,165,0,1.0) 5%, rgba(255,255,0,1.0) 10%, rgba(0,255,0,1.0)
              50%, rgba(255,255,0,1.0) 90%, rgba(255,165,0,1.0) 95%,
              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 minTemp = 0; // MIN CHANGÉ
                const maxTemp = 35; // 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)`;
              ]]]
  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: °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
      name: 0°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: 35°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 < 15) { // < 15°C
            return "Trop froid";
          }
          if (temp >= 15 && temp < 18.5) {
            return "Frais";
          }
          if (temp >= 18.5 && 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:
          - height: 8px
          - width: auto
          - height: 1px
          - border-radius: 999px
          - border: 0
          - padding: 8px
          - background: white
          - background-color: |
              [[[
                const minTemp = 0; 
                const maxTemp = 35;
                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]    }, // 0°C
                  { p: 25,  c: [0, 165, 255]  }, // 18.5°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]    }  // 35°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: 0.9rem
          - font-weight: 600
          - color: |
              [[[
                const minTemp = 0; 
                const maxTemp = 35;
                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)`;
              ]]]
styles:
  card:
    - background-color: rgba(42,45,54,1.0)
    - aspect-ratio: 7/1
    - cursor: default
  custom_fields:
    icon:
      - position: absolute
      - top: 30%
      - 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 value = Number(entity.state) || 0;
            const min = 0;
            const max = 35;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = 250;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]

image

Sur mon smartphone :

J’ai fait une demande sur atmosFrance .il y a 2jours
combien de temps leur faut il pour valider l’inscription?

Attend au moins Lundi qu’ils reprennent le taf.

Re,

J"‘ai ce code

type: custom:button-card
entity: sensor.module_salon_co2
show_state: false
show_icon: false
show_name: false
tap_action: none
double_tap_action: none
hold_action: none
custom_fields:
  bar:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: false
      styles:
        card:
          - height: 12px
          - width: 300px
          - 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: 4px solid rgba(42,45,54,1.0)
          - background-color: white
  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: 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: black
styles:
  card:
    - background-color: transparent
    - aspect-ratio: 3/1
    - cursor: default
  custom_fields:
    bar:
      - position: absolute
      - left: 50%
      - top: 50%
      - transform: translate(-50%, -50%)
    cursor:
      - position: absolute
      - top: "[[[ return 'calc(50% - 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 = 300;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
    min_val:
      - position: absolute
      - bottom: 22%
      - left: 10%
    max_val:
      - position: absolute
      - bottom: 22%
      - right: 10%
    comment:
      - position: absolute
      - left: 50%
      - bottom: 0%
      - transform: translate(-50%, -50%)

Ou j’ai changé la taille de barre à 300px, mais la valeur ici est à 950 donc le curseur devrait être environ au milieu mais ce n’est pas le cas. Que dois-je faire ?

Le curseur doit être au milieu de la barre pour une valeur de 1200 ppm (400 + 1600/2)

Dans les styles du « cursor ». Tu dois mettre la longueur de la « bar » soit 300

Oui ce qui est mon cas du coup

Je répondais à @Xris, chez toi le code est bon, c’est ton interprétation qui n’est pas bonne : milieu de la barre = 1200 ppm. Pour tester le code et la bonne position du curseur, créé une entrée input_number et fait varier les valeurs manuellement

1 « J'aime »

Ah pourtant j’ai bien mis 250 partout et les couleurs du curseurs ne changent pas au bon endroit.
Et parfois avec mon input de test si je place 0 le curseur est complètement en dehors. Je vais relire tout ça

Je pense avoir compris je dois pas avoir réglé l’offset de décalage de position du curseur

type: custom:button-card
entity: input_number.test
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 solid white
          - border-radius: 50%
          - background: none
        icon:
          - width: 90%
          - color: white
  name:
    card:
      type: custom:button-card
      name: Netatmo Salon Température
      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
  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(255,0,0,1.0) 0%,
              rgba(255,165,0,1.0) 5%, rgba(255,255,0,1.0) 10%, rgba(0,255,0,1.0)
              50%, rgba(255,255,0,1.0) 90%, rgba(255,165,0,1.0) 95%,
              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 minTemp = 0; // MIN CHANGÉ
                const maxTemp = 35; // 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)`;
              ]]]
  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: °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
      name: 0°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: 35°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 < 15) { // < 15°C
            return "Trop froid";
          }
          if (temp >= 15 && temp < 18.5) {
            return "Frais";
          }
          if (temp >= 18.5 && 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:
          - height: 8px
          - width: auto
          - height: 1px
          - border-radius: 999px
          - border: 0
          - padding: 8px
          - background: white
          - background-color: |
              [[[
                const minTemp = 0; 
                const maxTemp = 35;
                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]    }, // 0°C
                  { p: 25,  c: [0, 165, 255]  }, // 18.5°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]    }  // 35°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: 0.9rem
          - font-weight: 600
          - color: |
              [[[
                const minTemp = 0; 
                const maxTemp = 35;
                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)`;
              ]]]
styles:
  card:
    - background-color: rgba(42,45,54,1.0)
    - aspect-ratio: 7/1
    - cursor: default
  custom_fields:
    icon:
      - position: absolute
      - top: 30%
      - 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 value = Number(entity.state) || 0;
            const min = 0;
            const max = 35;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = 250;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(64.5% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]

Par contre je ne suis pas bon sur les changements de couleur du curseur

Un exemple pour thermomètre extérieur (plage de température élargie).

Le code :

type: custom:button-card
entity: sensor.perron_thermometre_temperature
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 extérieure<br>&nbsp;&nbsp;<span style="font-size:1.0rem;">Perron</span>';
              }
              return 'Température extérieure - Perron';
            ]]]
          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) 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 = -15;
                const maxTemp = 45;
                const temp = Number(entity.state);
                const value = isNaN(temp) ? minTemp : temp;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((value - 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: 75,  c: [255, 255, 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: "-15°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: 45°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);
          if (temp < -5) return "Froid extrême";
          if (temp >= -5 && temp < 0) return "Très froid";
          if (temp >= 0 && temp < 10) return "Frais";
          if (temp >= 10 && temp < 20) return "Confortable";
          if (temp >= 20 && temp < 30) return "Chaud";
          if (temp >= 30 && temp < 40) return "Très chaud";
          if (temp >= 40) return "Chaleur extrême";
        ]]]
      styles:
        card:
          - width: auto
          - height: auto
          - border-radius: 999px
          - border: 0
          - padding: 5px 10px 5px 10px
          - background-color: |
              [[[
                const minTemp = -15; 
                const maxTemp = 45;
                const temp = Number(entity.state);
                const value = isNaN(temp) ? minTemp : temp;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((value - 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: 75,  c: [255, 255, 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 minTemp = -15; 
                const maxTemp = 45;
                const temp = Number(entity.state);
                const value = isNaN(temp) ? minTemp : temp;
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = clamp(((value - 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: 75,  c: [255, 255, 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_month:
    card:
      type: custom:button-card
      icon: m3of:vertical-align-bottom
      show_name: false
      show_state: true
      layout: icon_state
      state_display: |
        [[[
          const s = states['sensor.perron_temperature_min_mois'];
          return parseFloat(s.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_month:
    card:
      type: custom:button-card
      icon: m3of:vertical-align-top
      show_name: false
      show_state: true
      layout: state_icon
      state_display: |
        [[[
          const s = states['sensor.perron_temperature_max_mois'];
          return parseFloat(s.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 temp = Number(entity.state);
            const value = isNaN(temp) ? -15 : temp;
            const min = -15;
            const max = 45;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = (window.innerWidth <= 600) ? 300 : 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 temp = Number(entity.state);
            const value = isNaN(temp) ? -15 : temp;
            const min = -15;
            const max = 45;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = (window.innerWidth <= 600) ? 300 : 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_month:
      - position: absolute
      - top: 40%
      - left: 5%
    max_temp_month:
      - position: absolute
      - top: 40%
      - right: 2%

Indice de qualité de l’air (à partir des données d’AtmoFrance).

Le code :

type: custom:button-card
entity: sensor.qualite_globale_lyon
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
          name: IQA
          styles:
            card:
              - aspect-ratio: 1/1
              - width: 50px
              - padding: 0
              - border: 4px solid rgba(167,176,205,1.0)
              - border-radius: 50%
              - background: none
            name:
              - font-size: 1.4rem
              - text-align: left
              - color: white
              - font-weight: 800
        - type: custom:button-card
          name: |
            [[[
              return window.innerWidth <= 600
                ? 'Indice de Qualité de l\'Air<br>&nbsp;&nbsp;<span style="font-size:1.0rem;">(AtmoFrance)</span>'
                : 'Indice de Qualité de l\'Air (AtmoFrance)';
            ]]]
          styles:
            card:
              - width: auto
              - padding: 0
              - border: none
              - background: none
            name:
              - font-size: 1.4rem
              - text-align: left
              - color: white
              - font-weight: 500
              - padding-top: "[[[ return window.innerWidth <= 600 ? '2%' : '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
  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: |
              [[[
                const colors = [
                  "rgba(80,240,230,1.0)",
                  "rgba(80,204,170,1.0)",
                  "rgba(240,230,65,1.0)",
                  "rgba(255,80,80,1.0)",
                  "rgba(150,0,50,1.0)",
                  "rgba(125,33,129,1.0)"
                ];
                const barWidth = (window.innerWidth <= 600) ? 300 : 360;
                const step = barWidth / colors.length;
                const blendPercent = 0.5;
                const blendHalf = step * blendPercent;
                const stops = [];
                for (let i = 0; i < colors.length; i++) {
                  const start = i * step;
                  const end = (i + 1) * step;
                  stops.push(`${colors[i]} ${Math.max(0, start)}px`);
                  stops.push(`${colors[i]} ${Math.max(0, end - blendHalf)}px`);
                  if (i < colors.length - 1) {
                    stops.push(`${colors[i+1]} ${Math.min(barWidth, end + blendHalf)}px`);
                  }
                }
                stops.push(`${colors[colors.length - 1]} ${barWidth}px`);
                return `linear-gradient(to right, ${stops.join(', ')})`;
              ]]]
  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 val = Number(entity.state);
                const clamp = (v) => Math.max(1, Math.min(6, v));
                const colors = [
                  "rgba(80,240,230,1.0)",
                  "rgba(80,204,170,1.0)",
                  "rgba(240,230,65,1.0)",
                  "rgba(255,80,80,1.0)",
                  "rgba(150,0,50,1.0)",
                  "rgba(125,33,129,1.0)"
                ];
                return colors[clamp(val)-1];
              ]]]
  comment:
    card:
      type: custom:button-card
      name: |
        [[[
          const labels = ["Bon","Moyen","Dégradé","Mauvais","Très mauvais","Extrêmement mauvais"];
          const val = Number(entity.state);
          return labels[val-1] || "Indisponible";
        ]]]
      styles:
        card:
          - width: auto
          - height: auto
          - border-radius: 999px
          - border: 0
          - padding: 5px 10px
          - background-color: |
              [[[
                const val = Number(entity.state);
                const colors = [
                  "rgba(80,240,230,0.3)",
                  "rgba(80,204,170,0.3)",
                  "rgba(240,230,65,0.3)",
                  "rgba(255,80,80,0.3)",
                  "rgba(150,0,50,0.3)",
                  "rgba(125,33,129,0.3)"
                ];
                return colors[val-1] || "rgba(221,221,221,0.3)";
              ]]]
        name:
          - font-size: 1.0rem
          - font-weight: 600
          - color: |
              [[[
                const val = Number(entity.state);
                const colors = [
                  "rgba(80,240,230,1.0)",
                  "rgba(80,204,170,1.0)",
                  "rgba(240,230,65,1.0)",
                  "rgba(255,80,80,1.0)",
                  "rgba(150,0,50,1.0)",
                  "rgba(125,33,129,1.0)"
                ];
                return colors[val-1] || "rgba(221,221,221,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%
    bar:
      - position: absolute
      - left: 50%
      - top: 58%
      - transform: translate(-50%, -50%)
    cursor:
      - position: absolute
      - top: "[[[ return 'calc(58% - 16px)' ]]]"
      - left: |
          [[[
            const val = Number(entity.state);
            const clamp = (v) => Math.max(1, Math.min(6, v));
            const index = clamp(val);
            const barWidth = (window.innerWidth <= 600) ? 300 : 360;
            const step = barWidth / 6;
            const halfCursor = 8;
            const offsetPx = (index - 1) * step + step/2;
            return `calc(50% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]
    comment:
      - position: absolute
      - left: 50%
      - bottom: "-3%"
      - transform: translate(-50%, -50%)

Rendu sur téléphone portable :

4 « J'aime »

tu es resté sur un dégradé de couleur allant du bleu (froid) au rouge (chaud) pour le curseur et le commentaire alors que tu as modifié les couleurs de la barre.

  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 minTemp = 0; // MIN CHANGÉ
                const maxTemp = 35; // 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]    },     <------- [255, 0, 0] ici
                  { p: 25,  c: [0, 165, 255]  },
                  { p: 50,  c: [0, 255, 0]    },      <------ il manque {p: 37.5, c: [255, 165, 0] }, avant la ligne du 50%
                  { p: 62.5,  c: [255, 255, 0]  },
                  { p: 75,  c: [255, 165, 0]  },
                  { p: 100, c: [255, 0, 0]    }
                ];

Et faire la même modification pour le « comment ».
Par ailleurs, il te manque une composant « orange » à 37.5 % pour le curseur et tes poucentages ne sont pas les mêmes que ceux de ta barre

Oui c’est ce qui me semblait que j’étais pas encore bien précis. Merci pour ton aide

Vous êtes des grands fous mais le résultat est top, j’ai pris, merci, quand j’ai vu la question de départ je ne pensais pas voir un tel résultat, chapeau :wink:
Bob

3 « J'aime »

J’ai modifié pour être plus cohérent avec l’intérieur, petite aide de copilot pour rendre les couleurs cohérentes sur mes valeurs et erreurs. Tu me diras si tu vois des incohérences

type: custom:button-card
entity: input_number.test
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 solid white
          - border-radius: 50%
          - background: none
        icon:
          - width: 90%
          - color: white
  name:
    card:
      type: custom:button-card
      name: Netatmo Salon Température
      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 transparent
          - border-radius: 0
          - background: none
  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: 0
          - background: |-
              linear-gradient(to right,
                rgba(255,0,0,1.0) 0%,           /* 14°C rouge */
                rgba(255,255,0,1.0) 12.5%,      /* 16°C jaune */
                rgba(200,255,120,1.0) 21.875%,  /* 17.5°C jaune-vert */
                rgba(0,255,0,1.0) 28.125%,      /* 18.5°C début vert */
                rgba(0,255,0,1.0) 50%,          /* 22°C vert saturé */
                rgba(180,255,100,1.0) 56.25%,   /* 23°C vert-jaune */
                rgba(255,255,0,1.0) 62.5%,      /* 24°C jaune */
                rgba(255,210,0,1.0) 68.75%,     /* 25°C jaune-orangé */
                rgba(255,120,0,1.0) 75%,        /* 26°C orange-rouge (début rouge) */
                rgba(255,60,0,1.0) 87.5%,       /* 28°C rouge vif */
                rgba(255,0,0,1.0) 100%)         /* 30°C rouge saturé */
  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 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: [255, 0, 0] },     // 14
                  { p: 12.5,   c: [255, 255, 0] },   // 16
                  { p: 21.875, c: [200, 255, 120] }, // 17.5
                  { p: 28.125, c: [0, 255, 0] },     // 18.5
                  { p: 50,     c: [0, 255, 0] },     // 22
                  { p: 56.25,  c: [180, 255, 100] }, // 23
                  { p: 62.5,   c: [255, 255, 0] },   // 24
                  { p: 68.75,  c: [255, 210, 0] },   // 25 (jaune-orangé)
                  { p: 75,     c: [255, 120, 0] },   // 26 (orange-rouge)
                  { p: 87.5,   c: [255, 60, 0] },    // 28 (rouge vif)
                  { p: 100,    c: [255, 0, 0] }      // 30 (rouge saturé)
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1], 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)`;
              ]]]
  value:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: true
      state_display: |
        [[[
          const v = Number(entity.state);
          if (isNaN(v)) return '—';
          return v.toFixed(1);
        ]]]
      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
      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);
          if (isNaN(temp)) return "Indisponible";
          if (temp < 14) { return "En dehors de l'échelle"; }
          if (temp >= 14 && temp <= 15) { return "Froid"; }
          if (temp >= 16 && temp < 18.5) { return "Frais"; }
          if (temp >= 18.5 && temp <= 22) { return "Confortable"; }
          if (temp > 22 && temp <= 24) { return "Tiède"; }
          if (temp > 24 && temp < 27) { return "Chaud"; }
          if (temp >= 27) { return "Canicule"; }
          return "Indisponible";
        ]]]
      styles:
        card:
          - height: 8px
          - width: auto
          - border-radius: 999px
          - border: 0
          - padding: 8px
          - background: white
          - background-color: |
              [[[
                const minTemp = 14;
                const maxTemp = 30;
                const temp = Number(entity.state);
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = isNaN(temp) ? 0 : clamp(((temp - minTemp) / (maxTemp - minTemp)) * 100, 0, 100);
                const stops = [
                  { p: 0,      c: [255, 0, 0] },
                  { p: 12.5,   c: [255, 255, 0] },
                  { p: 21.875, c: [200, 255, 120] },
                  { p: 28.125, c: [0, 255, 0] },
                  { p: 50,     c: [0, 255, 0] },
                  { p: 56.25,  c: [180, 255, 100] },
                  { p: 62.5,   c: [255, 255, 0] },
                  { p: 68.75,  c: [255, 210, 0] },
                  { p: 75,     c: [255, 120, 0] },
                  { p: 87.5,   c: [255, 60, 0] },
                  { p: 100,    c: [255, 0, 0] }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1], 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 minTemp = 14;
                const maxTemp = 30;
                const temp = Number(entity.state);
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = isNaN(temp) ? 0 : clamp(((temp - minTemp) / (maxTemp - minTemp)) * 100, 0, 100);
                const stops = [
                  { p: 0,      c: [255, 0, 0] },
                  { p: 12.5,   c: [255, 255, 0] },
                  { p: 21.875, c: [200, 255, 120] },
                  { p: 28.125, c: [0, 255, 0] },
                  { p: 50,     c: [0, 255, 0] },
                  { p: 56.25,  c: [180, 255, 100] },
                  { p: 62.5,   c: [255, 255, 0] },
                  { p: 68.75,  c: [255, 210, 0] },
                  { p: 75,     c: [255, 120, 0] },
                  { p: 87.5,   c: [255, 60, 0] },
                  { p: 100,    c: [255, 0, 0] }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1], 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: 30%
      - left: 1%
    name:
      - position: absolute
      - top: 3%
      - left: 8%
    separation:
      - position: absolute
      - top: 17%
      - left: 0%
    value:
      - position: absolute
      - top: 26%
      - right: 6.5%
      - margin-right: 1%
      - transform: translateY(-50%)
    unit:
      - position: absolute
      - left: 92%
      - 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 value = Number(entity.state);
            const min = 14;
            const max = 30;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = isNaN(value) ? 0 : clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = 250;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(64.5% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]

Bonjour @Xris,
S’il y a une incohérence, c’est ici : le « - transform: translate(-50%, -50%) » sert à centrer horizontalement et verticalement le custom_fields sur la carte support. Chez toi, il faut simplement spécifier la position de la barre par rapport au bord droit de la carte support et reprendre le calcul de la position du curseur à partir de cette position. Pour le dégradé de la barre, je suppose que tu t’es basé sur l’application de Netatmo mais je trouve que ce n’est pas forcément en adéquation avec les commentaires (11 plages de couleur pour 7 valeurs de commentaire).

Ah donc je pourrais supprimer simplement le transform ? Et réduire le nombre de plage pour avoir un dégradé plus cohérent ?
J’ai adapté un peu à nos usages aussi

Pour le transform: translate, oui tu peux le supprimer. Pour les couleurs, ce n’est qu’une affaire de goût : si ça te convient tel quel, n’y touche pas. Simplement, actuellement 1°C représente 15,625 pixels sur la barre, ce n’est pas forcément l’idéal même si en pourcentage (6.25%) c’est un peu mieux.

J’ai supprimé le transforme translate et repositionné la barre par la droite et le haut :

type: custom:button-card
entity: sensor.netatmo_salon_temperature
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 solid white
          - border-radius: 50%
          - background: none
        icon:
          - width: 90%
          - color: white
  name:
    card:
      type: custom:button-card
      name: Netatmo Salon Température
      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 transparent
          - border-radius: 0
          - background: none
  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: 0
          - background: |-
              linear-gradient(to right,
                rgba(255,0,0,1.0) 0%,           /* 14°C rouge */
                rgba(255,255,0,1.0) 12.5%,      /* 16°C jaune */
                rgba(200,255,120,1.0) 21.875%,  /* 17.5°C jaune-vert */
                rgba(0,255,0,1.0) 28.125%,      /* 18.5°C début vert */
                rgba(0,255,0,1.0) 50%,          /* 22°C vert saturé */
                rgba(180,255,100,1.0) 56.25%,   /* 23°C vert-jaune */
                rgba(255,255,0,1.0) 62.5%,      /* 24°C jaune */
                rgba(255,210,0,1.0) 68.75%,     /* 25°C jaune-orangé */
                rgba(255,120,0,1.0) 75%,        /* 26°C orange-rouge (début rouge) */
                rgba(255,60,0,1.0) 87.5%,       /* 28°C rouge vif */
                rgba(255,0,0,1.0) 100%)         /* 30°C rouge saturé */
  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 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: [255, 0, 0] },     // 14
                  { p: 12.5,   c: [255, 255, 0] },   // 16
                  { p: 21.875, c: [200, 255, 120] }, // 17.5
                  { p: 28.125, c: [0, 255, 0] },     // 18.5
                  { p: 50,     c: [0, 255, 0] },     // 22
                  { p: 56.25,  c: [180, 255, 100] }, // 23
                  { p: 62.5,   c: [255, 255, 0] },   // 24
                  { p: 68.75,  c: [255, 210, 0] },   // 25 (jaune-orangé)
                  { p: 75,     c: [255, 120, 0] },   // 26 (orange-rouge)
                  { p: 87.5,   c: [255, 60, 0] },    // 28 (rouge vif)
                  { p: 100,    c: [255, 0, 0] }      // 30 (rouge saturé)
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1], 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)`;
              ]]]
  value:
    card:
      type: custom:button-card
      show_name: false
      show_icon: false
      show_state: true
      state_display: |
        [[[
          const v = Number(entity.state);
          if (isNaN(v)) return '—';
          return v.toFixed(1);
        ]]]
      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
      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);
          if (isNaN(temp)) return "Indisponible";
          if (temp < 14) { return "En dehors de l'échelle"; }
          if (temp >= 14 && temp <= 15) { return "Froid"; }
          if (temp >= 16 && temp < 18.5) { return "Frais"; }
          if (temp >= 18.5 && temp <= 22) { return "Confortable"; }
          if (temp > 22 && temp <= 24) { return "Tiède"; }
          if (temp > 24 && temp < 27) { return "Chaud"; }
          if (temp >= 27) { return "Canicule"; }
          return "Indisponible";
        ]]]
      styles:
        card:
          - height: 8px
          - width: auto
          - border-radius: 999px
          - border: 0
          - padding: 8px
          - background: white
          - background-color: |
              [[[
                const minTemp = 14;
                const maxTemp = 30;
                const temp = Number(entity.state);
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = isNaN(temp) ? 0 : clamp(((temp - minTemp) / (maxTemp - minTemp)) * 100, 0, 100);
                const stops = [
                  { p: 0,      c: [255, 0, 0] },
                  { p: 12.5,   c: [255, 255, 0] },
                  { p: 21.875, c: [200, 255, 120] },
                  { p: 28.125, c: [0, 255, 0] },
                  { p: 50,     c: [0, 255, 0] },
                  { p: 56.25,  c: [180, 255, 100] },
                  { p: 62.5,   c: [255, 255, 0] },
                  { p: 68.75,  c: [255, 210, 0] },
                  { p: 75,     c: [255, 120, 0] },
                  { p: 87.5,   c: [255, 60, 0] },
                  { p: 100,    c: [255, 0, 0] }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1], 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 minTemp = 14;
                const maxTemp = 30;
                const temp = Number(entity.state);
                const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
                const percent = isNaN(temp) ? 0 : clamp(((temp - minTemp) / (maxTemp - minTemp)) * 100, 0, 100);
                const stops = [
                  { p: 0,      c: [255, 0, 0] },
                  { p: 12.5,   c: [255, 255, 0] },
                  { p: 21.875, c: [200, 255, 120] },
                  { p: 28.125, c: [0, 255, 0] },
                  { p: 50,     c: [0, 255, 0] },
                  { p: 56.25,  c: [180, 255, 100] },
                  { p: 62.5,   c: [255, 255, 0] },
                  { p: 68.75,  c: [255, 210, 0] },
                  { p: 75,     c: [255, 120, 0] },
                  { p: 87.5,   c: [255, 60, 0] },
                  { p: 100,    c: [255, 0, 0] }
                ];
                let i = 1;
                while (i < stops.length && percent > stops[i].p) i++;
                const a = stops[i - 1], 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: 30%
      - left: 1%
    name:
      - position: absolute
      - top: 3%
      - left: 8%
    separation:
      - position: absolute
      - top: 17%
      - left: 0%
    value:
      - position: absolute
      - top: 26%
      - right: 6.5%
      - margin-right: 1%
      - transform: translateY(-50%)
    unit:
      - position: absolute
      - left: 92%
      - 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
      - right: 3.5%
      - top: 49%
    cursor:
      - position: absolute
      - top: "[[[ return 'calc(55% - 12.5px)'; ]]]"
      - left: |
          [[[
            const value = Number(entity.state);
            const min = 14;
            const max = 30;
            const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
            const percent = isNaN(value) ? 0 : clamp(((value - min) / (max - min)) * 100, 0, 100);
            const barWidth = 250;
            const halfCursor = 8;
            const offsetPx = (barWidth * percent) / 100;
            return `calc(64.5% - ${barWidth/2}px + ${offsetPx}px - ${halfCursor}px)`;
          ]]]

Mais tu n’as pas redéfini le calcul de la position du curseur.