Petite carte lever et coucher du soleil

Bonjour,
Je sais qu’il existe deja une carte horizon card mais je l’a trouvé trop grosse et sur mon tableau de bord je voulais quelque chose d’assez discret.
Les calculs sont fait à l’aide d’IA. Il n’y a aucune prétention et je partage car si quelqu’un veut améliorer s’est sans soucis. C’est fait base buttom-card et card-mod, il faut aussi l’intégration lune car en fin de journée a la place du soleil apparait la lune avec son quartier ou pleine.
Image :

Code :

type: custom:button-card
entity: sun.sun
show_name: false
show_icon: false
styles:
  card:
    - height: 160px
    - padding: 10px
    - border-radius: 15px
  grid:
    - grid-template-areas: |
        "arc"
        "heures"
    - grid-template-rows: 95px 45px
custom_fields:
  arc: |
    [[[
      const az = states['sun.sun'].attributes.azimuth;
      const el = states['sun.sun'].attributes.elevation;
      const moon = states['sensor.moon'] ? states['sensor.moon'].state : 'full_moon';
      const x = ((az - 90) / 180) * 240 + 30;
      const t = (x - 30) / 240; 
      const y = (1 - t) * (1 - t) * 85 + 2 * (1 - t) * t * -10 + t * t * 85;
      const isDay = el > 0;
      let visual = "";
      let color = "#FFCE20";
      if (isDay) {
        const ratio = Math.min(Math.max(el / 40, 0), 1); 
        const g = Math.round(130 + (125 * ratio));
        const b = Math.round(0 + (120 * ratio));
        color = `rgb(255, ${g}, ${b})`;
        visual = `<circle cx="${x}" cy="${y}" r="7" fill="${color}" style="transition: all 1s; filter: drop-shadow(0 0 10px ${color});" />`;
      } else {
        const phases = {'new_moon': 0.1, 'waxing_crescent': 0.3, 'first_quarter': 0.5, 'waxing_gibbous': 0.8, 'full_moon': 1, 'waning_gibbous': 0.8, 'last_quarter': 0.5, 'waning_crescent': 0.3};
        let p = phases[moon] ?? 1;
        const r = 10;
        const s = p > 0.5 ? 0 : 1;
        const iR = Math.max(Math.abs(p - 0.5) * 2.2 * r, 0.5);
        visual = `<g transform="translate(${x}, ${y})"><circle r="${r}" fill="#1a1a1a" stroke="rgba(93, 169, 221, 0.2)" stroke-width="0.5" /><path d="M 0 ${-r} A ${r} ${r} 0 1 ${s} 0 ${r} A ${iR} ${r} 0 1 ${1-s} 0 ${-r}" fill="#5da9dd" style="filter: drop-shadow(0 0 8px #3a7bd5);" /></g>`;
      }
      const hColor = isDay ? color.replace('rgb', 'rgba').replace(')', ', 0.15)') : 'rgba(58, 123, 213, 0.15)';
      return `<svg viewBox="0 0 300 100" style="width:100%; height:100%; overflow: visible;"><defs><radialGradient id="h" cx="${x/3}%" cy="${y}%" r="60%"><stop offset="0%" stop-color="${hColor}" /><stop offset="100%" stop-color="transparent" /></radialGradient></defs><rect width="300" height="100" fill="url(#h)" style="transition: all 1s;" /><path d="M 30 85 Q 150 -10 270 85" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="2" stroke-dasharray="4,4" />${visual}<line x1="20" y1="86" x2="280" y2="86" stroke="rgba(255,255,255,0.05)" stroke-width="1" /></svg>`;
    ]]]
  heures: |
    [[[
      const r = new Date(states['sun.sun'].attributes.next_rising).toLocaleTimeString('fr-FR', {hour: '2-digit', minute: '2-digit'});
      const s = new Date(states['sun.sun'].attributes.next_setting).toLocaleTimeString('fr-FR', {hour: '2-digit', minute: '2-digit'});
      return `<div style="display: flex; justify-content: space-between; width: 90%; margin: 5px auto 0;"><div style="text-align: center;"><div style="color: rgba(255,255,255,0.2); font-size: 8px; font-weight: bold;">LEVER</div><span style="color: #FFA726; font-size: 14px; font-weight: 900;">${r}</span></div><div style="text-align: center;"><div style="color: rgba(255,255,255,0.2); font-size: 8px; font-weight: bold;">COUCHER</div><span style="color: #F44336; font-size: 14px; font-weight: 900;">${s}</span></div></div>`;
    ]]]

Merci

6 « J'aime »

merci pour ce partage.
optimisé pour le mode dark :frowning:

Hello,

En même temps le jaune sur un thème clair, c’est pas simple :slight_smile:

j’ai mis ça en test, à voir.

type: custom:button-card
entity: sun.sun
show_name: false
show_icon: false
styles:
  card:
    - height: 160px
    - padding: 10px
    - border-radius: 15px
    - background-color: "#ffffff"
  grid:
    - grid-template-areas: |
        "arc"
        "heures"
    - grid-template-rows: 95px 45px
custom_fields:
  arc: |
    [[[
      const az = states['sun.sun'].attributes.azimuth;
      const el = states['sun.sun'].attributes.elevation;
      const moon = states['sensor.moon'] ? states['sensor.moon'].state : 'full_moon';
      const x = ((az - 90) / 180) * 240 + 30;
      const t = (x - 30) / 240; 
      const y = (1 - t) * (1 - t) * 85 + 2 * (1 - t) * t * -10 + t * t * 85;
      const isDay = el > 0;
      let visual = "";
      let color = "#FFCE20";
      
      if (isDay) {
        // SOLEIL : On garde le jaune mais on ajoute une lueur orange pour le contraste
        const ratio = Math.min(Math.max(el / 40, 0), 1); 
        const g = Math.round(130 + (125 * ratio));
        const b = Math.round(0 + (120 * ratio));
        color = `rgb(255, ${g}, ${b})`;
        visual = `<circle cx="${x}" cy="${y}" r="7" fill="${color}" style="transition: all 1s; filter: drop-shadow(0 0 8px #FFA500);" />`;
      } else {
        // LUNE : Adaptée pour fond clair
        const phases = {'new_moon': 0.1, 'waxing_crescent': 0.3, 'first_quarter': 0.5, 'waxing_gibbous': 0.8, 'full_moon': 1, 'waning_gibbous': 0.8, 'last_quarter': 0.5, 'waning_crescent': 0.3};
        let p = phases[moon] ?? 1;
        const r = 10;
        const s = p > 0.5 ? 0 : 1;
        const iR = Math.max(Math.abs(p - 0.5) * 2.2 * r, 0.5);
        visual = `<g transform="translate(${x}, ${y})"><circle r="${r}" fill="#d1d1d1" stroke="rgba(0,0,0,0.1)" stroke-width="0.5" /><path d="M 0 ${-r} A ${r} ${r} 0 1 ${s} 0 ${r} A ${iR} ${r} 0 1 ${1-s} 0 ${-r}" fill="#3a7bd5" style="filter: drop-shadow(0 0 8px #3a7bd5);" /></g>`;
      }
      
      // Halo lumineux adapté au fond clair
      const hColor = isDay ? 'rgba(255, 206, 32, 0.2)' : 'rgba(58, 123, 213, 0.1)';
      
      return `
        <svg viewBox="0 0 300 100" style="width:100%; height:100%; overflow: visible;">
          <defs>
            <radialGradient id="h" cx="${x/3}%" cy="${y}%" r="60%">
              <stop offset="0%" stop-color="${hColor}" />
              <stop offset="100%" stop-color="transparent" />
            </radialGradient>
          </defs>
          <rect width="300" height="100" fill="url(#h)" style="transition: all 1s;" />
          
          <path d="M 30 85 Q 150 -10 270 85" fill="none" stroke="rgba(0,0,0,0.15)" stroke-width="2" stroke-dasharray="4,4" />
          
          ${visual}
          
          <line x1="20" y1="86" x2="280" y2="86" stroke="rgba(0,0,0,0.1)" stroke-width="1" />
        </svg>`;
    ]]]
  heures: |
    [[[
      const r = new Date(states['sun.sun'].attributes.next_rising).toLocaleTimeString('fr-FR', {hour: '2-digit', minute: '2-digit'});
      const s = new Date(states['sun.sun'].attributes.next_setting).toLocaleTimeString('fr-FR', {hour: '2-digit', minute: '2-digit'});
      return `
        <div style="display: flex; justify-content: space-between; width: 90%; margin: 5px auto 0;">
          <div style="text-align: center;">
            <div style="color: rgba(0,0,0,0.4); font-size: 8px; font-weight: bold;">LEVER</div>
            <span style="color: #EF6C00; font-size: 14px; font-weight: 900;">${r}</span>
          </div>
          <div style="text-align: center;">
            <div style="color: rgba(0,0,0,0.4); font-size: 8px; font-weight: bold;">COUCHER</div>
            <span style="color: #D32F2F; font-size: 14px; font-weight: 900;">${s}</span>
          </div>
        </div>`;
    ]]]

Et merci @Pierre6363 pour le partage :wink:

cdt

C’est mieux oui :slight_smile:

Ca tombe bien je suis pile poil pendant le coucher lol :slight_smile:

Moins visuel après le coucher .
je pensais pas qu’on avait temps de différence d’horaire de coucher en France ??
17h16 ?? pour vous

Re,

sans doute l’effet de ma grotte :wink:

image

next_dawn: "2026-01-19T06:57:45.318643+00:00"
next_dusk: "2026-01-18T16:52:11.502355+00:00"
next_midnight: "2026-01-18T23:55:28+00:00"
next_noon: "2026-01-19T11:55:18+00:00"
next_rising: "2026-01-19T07:35:05.802008+00:00"
next_setting: "2026-01-19T16:16:16.046907+00:00"

Après à voir suivant coucher ou crépuscule

cdt

au coucher on devrait pas etre sous l’horizon?

Re,

Non il y a le coucher, puis ensuite le crépuscule

cdt

1 « J'aime »

Bonjour,
De rien pour le partage, c’est le but de ce forum, partage et entraide.
J’ai apporter des petites mdifs:

  • la course du soleil n’était pas complète sur l’arc ça doit être corrigé
  • et la lune maintenant parcours aussi tout l’arc la nuit
  • et suivant si c’est un thème clair ou sombre les traits clairs sont passés en foncés.
    Code :
type: custom:button-card
entity: sun.sun
show_name: false
show_icon: false
styles:
  card:
    - height: 160px
    - padding: 10px
    - border-radius: 15px
   # - background-color: var(--card-background-color)
  grid:
    - grid-template-areas: |
        "arc"
        "heures"
    - grid-template-rows: 95px 45px
custom_fields:
  arc: |
    [[[
      const sun = states['sun.sun'];
      if (!sun) return "";
      const now = new Date().getTime();
      const nS = new Date(sun.attributes.next_setting).getTime();
      const nR = new Date(sun.attributes.next_rising).getTime();
      const isDay = sun.attributes.elevation > 0;
      let t;
      if (isDay) {
        const todayR = nS - (9.33 * 3600000); 
        t = Math.min(Math.max((now - todayR) / (nS - todayR), 0), 1);
      } else {
        const startN = nS > now ? nS - 86400000 : nS;
        t = Math.min(Math.max((now - startN) / (nR - startN), 0), 1);
      }
      const x = t * 240 + 30;
      const y = (1 - t) * (1 - t) * 85 + 2 * (1 - t) * t * -10 + t * t * 85;
      const color = isDay ? "#FFA726" : "#5da9dd";
      return `<svg viewBox="0 0 300 100" style="overflow:visible"><path d="M 30 85 Q 150 -10 270 85" fill="none" stroke="var(--disabled-text-color)" stroke-width="2" stroke-dasharray="4,4" opacity="0.3" /><line x1="20" y1="85" x2="280" y2="85" stroke="var(--secondary-text-color)" stroke-width="1" opacity="0.5" /><circle cx="${x}" cy="${y}" r="7" fill="${color}" style="filter:drop-shadow(0 0 5px ${color})" /></svg>`;
    ]]]
  heures: |
    [[[
      const sun = states['sun.sun'];
      if (!sun) return "";
      const nR = new Date(sun.attributes.next_rising);
      const nS = new Date(sun.attributes.next_setting);
      let dR = nR, dS = nS;
      if (nR < nS) dS = new Date(nS.getTime() - 86400000);
      else dR = new Date(nR.getTime() - 86400000);
      const r = dR.toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'});
      const s = dS.toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'});
      return `<div style="display:flex;justify-content:space-between;width:90%;margin:5px auto 0;"><div style="text-align:center;"><div style="color:var(--secondary-text-color);font-size:8px;font-weight:bold;opacity:0.6;">LEVER</div><span style="color:#FFA726;font-size:14px;font-weight:900;">${r}</span></div><div style="text-align:center;"><div style="color:var(--secondary-text-color);font-size:8px;font-weight:bold;opacity:0.6;">COUCHER</div><span style="color:#F44336;font-size:14px;font-weight:900;">${s}</span></div></div>`;
    ]]]

et une variante avec l’heure actuel sous le soleil

code :

type: custom:button-card
entity: sun.sun
show_name: false
show_icon: false
styles:
  card:
    - height: 160px
    - padding: 10px
    - border-radius: 15px
   # - background-color: var(--card-background-color)
  grid:
    - grid-template-areas: |
        "arc"
        "heures"
    - grid-template-rows: 95px 45px
custom_fields:
  arc: |
    [[[
      const sun = states['sun.sun'];
      if (!sun) return "";
      const now = new Date();
      const nS = new Date(sun.attributes.next_setting).getTime();
      const nR = new Date(sun.attributes.next_rising).getTime();
      const el = sun.attributes.elevation;
      const isDay = el > 0;
      
      let t;
      if (isDay) {
        const todayR = nS - (9.33 * 60 * 60 * 1000); 
        t = Math.min(Math.max((now.getTime() - todayR) / (nS - todayR), 0), 1);
      } else {
        const startN = nS > now.getTime() ? nS - 86400000 : nS;
        t = Math.min(Math.max((now.getTime() - startN) / (nR - startN), 0), 1);
      }

      const x = t * 240 + 30;
      const y = (1 - t) * (1 - t) * 85 + 2 * (1 - t) * t * -10 + t * t * 85;
      
      const currentTime = now.toLocaleTimeString('fr-FR', {hour: '2-digit', minute: '2-digit'});
      const color = isDay ? "#FFA726" : "#5da9dd";

      return `
        <svg viewBox="0 0 300 100" style="overflow:visible">
          <defs>
            <filter id="glow">
              <feGaussianBlur stdDeviation="2.5" result="coloredBlur"/><feMerge><feMergeNode in="coloredBlur"/><feMergeNode in="SourceGraphic"/></feMerge>
            </filter>
          </defs>
          <path d="M 30 85 Q 150 -10 270 85" fill="none" stroke="var(--disabled-text-color)" stroke-width="2" stroke-dasharray="4,4" opacity="0.3" />
          <line x1="20" y1="85" x2="280" y2="85" stroke="var(--secondary-text-color)" stroke-width="1" opacity="0.5" />
          
          <circle cx="${x}" cy="${y}" r="7" fill="${color}" filter="url(#glow)" />
          
          <text x="${x}" y="${y + 18}" text-anchor="middle" fill="var(--primary-text-color)" style="font-size: 8px; font-weight: bold; opacity: 0.9;">${currentTime}</text>
        </svg>`;
    ]]]
  heures: |
    [[[
      const sun = states['sun.sun'];
      if (!sun) return "";
      const nR = new Date(sun.attributes.next_rising);
      const nS = new Date(sun.attributes.next_setting);
      let dR = nR, dS = nS;
      if (nR < nS) dS = new Date(nS.getTime() - 86400000);
      else dR = new Date(nR.getTime() - 86400000);
      
      const r = dR.toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'});
      const s = dS.toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'});
      
      return `
        <div style="display: flex; justify-content: space-between; width: 90%; margin: 5px auto 0;">
          <div style="text-align: center;"><div style="color: var(--secondary-text-color); font-size: 8px; font-weight: bold; opacity: 0.6;">LEVER</div><span style="color: #FFA726; font-size: 14px; font-weight: 900;">${r}</span></div>
          <div style="text-align: center;"><div style="color: var(--secondary-text-color); font-size: 8px; font-weight: bold; opacity: 0.6;">COUCHER</div><span style="color: #F44336; font-size: 14px; font-weight: 900;">${s}</span></div>
        </div>`;
    ]]]

Vous pouvez modifiez et à votre tour et partagé si vous le souhaitez.

Merci

2 « J'aime »

Hello un énorme merci ! La card est parfaite

Voici le code pour avoir l’heure sous le soleil et l’aura du soleil sur la card ainsi que l’éphémeride (provenant du sensor de l’intégration Ephémeride) :

type: custom:button-card
entity: sun.sun
show_name: false
show_icon: false
styles:
  card:
    - height: 160px
    - padding: 10px
    - border-radius: 15px
  grid:
    - grid-template-areas: |
        "arc"
        "heures"
    - grid-template-rows: 95px 45px
custom_fields:
  arc: |
    [[[
      const sun = states['sun.sun'];
      if (!sun) return "";

      const now = new Date();
      const nextR = new Date(sun.attributes.next_rising).getTime();
      const nextS = new Date(sun.attributes.next_setting).getTime();
      const el = sun.attributes.elevation;
      const isDay = el > 0;

      // === CALCUL TEMPOREL CORRECT (jour / nuit) ===
      let start, end;

      if (isDay) {
        // lever -> coucher
        start = nextR < nextS ? nextR : nextR - 86400000;
        end = nextS;
      } else {
        // coucher -> lever
        start = nextS < nextR ? nextS : nextS - 86400000;
        end = nextR;
      }

      const t = Math.min(Math.max((now.getTime() - start) / (end - start), 0), 1);

      // === POSITION SUR ARC (100 % complet) ===
      const x = t * 240 + 30;
      const y = (1 - t) * (1 - t) * 85
              + 2 * (1 - t) * t * -10
              + t * t * 85;

      const moon = states['sensor.moon'] ? states['sensor.moon'].state : 'full_moon';

      let visual = "";
      let color = "#FFCE20";

      if (isDay) {
        const ratio = Math.min(Math.max(el / 40, 0), 1);
        const g = Math.round(130 + (125 * ratio));
        const b = Math.round(0 + (120 * ratio));
        color = `rgb(255, ${g}, ${b})`;

        visual = `
          <circle cx="${x}" cy="${y}" r="7"
            fill="${color}"
            style="transition: all 1s; filter: drop-shadow(0 0 10px ${color});" />`;
      } else {
        const phases = {
          'new_moon': 0.1,
          'waxing_crescent': 0.3,
          'first_quarter': 0.5,
          'waxing_gibbous': 0.8,
          'full_moon': 1,
          'waning_gibbous': 0.8,
          'last_quarter': 0.5,
          'waning_crescent': 0.3
        };
        let p = phases[moon] ?? 1;
        const r = 10;
        const s = p > 0.5 ? 0 : 1;
        const iR = Math.max(Math.abs(p - 0.5) * 2.2 * r, 0.5);

        visual = `
          <g transform="translate(${x}, ${y})">
            <circle r="${r}" fill="#1a1a1a"
              stroke="rgba(93,169,221,0.2)" stroke-width="0.5"/>
            <path d="M 0 ${-r} A ${r} ${r} 0 1 ${s} 0 ${r}
                     A ${iR} ${r} 0 1 ${1-s} 0 ${-r}"
              fill="#5da9dd"
              style="filter: drop-shadow(0 0 8px #3a7bd5);" />
          </g>`;
      }

      const hColor = isDay
        ? color.replace('rgb', 'rgba').replace(')', ', 0.15)')
        : 'rgba(58,123,213,0.15)';

      const time = now.toLocaleTimeString('fr-FR', {
        hour: '2-digit',
        minute: '2-digit'
      });

      return `
        <svg viewBox="0 0 300 100" style="width:100%; height:100%; overflow: visible;">
          <defs>
            <radialGradient id="h" cx="${x/3}%" cy="${y}%" r="60%">
              <stop offset="0%" stop-color="${hColor}" />
              <stop offset="100%" stop-color="transparent" />
            </radialGradient>
          </defs>

          <rect width="300" height="100" fill="url(#h)" />

          <path d="M 30 85 Q 150 -10 270 85"
                fill="none"
                stroke="rgba(255,255,255,0.1)"
                stroke-width="2"
                stroke-dasharray="4,4" />

          ${visual}

          <text x="${x}" y="${y + 18}" text-anchor="middle"
                fill="rgba(255,255,255,0.9)"
                style="font-size:8px; font-weight:700;">
            ${time}
          </text>

          <line x1="20" y1="86" x2="280" y2="86"
                stroke="rgba(255,255,255,0.05)" stroke-width="1" />
        </svg>`;
    ]]]
  heures: |
    [[[
      const sun = states['sun.sun'];
      if (!sun) return "";

      const r = new Date(sun.attributes.next_rising)
        .toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'});
      const s = new Date(sun.attributes.next_setting)
        .toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'});

      const saint = states['sensor.ephemeride_saint_du_jour']
        ? states['sensor.ephemeride_saint_du_jour'].state
        : '';

      return `
        <div style="
          position: relative;
          width: 100%;
          height: 32px;
          margin-top: -6px;
        ">

          <!-- LEVER -->
          <div style="
            position: absolute;
            left: 28px;
            bottom: 0;
            text-align: left;
          ">
            <div style="color:rgba(255,255,255,0.25); font-size:7px; font-weight:bold;">
              LEVER
            </div>
            <div style="color:#FFA726; font-size:13px; font-weight:900;">
              ${r}
            </div>
          </div>

          <!-- SAINT (centré, discret) -->
          <div style="
            position: absolute;
            left: 50%;
            transform: translateX(-50%);
            bottom: -2px;
            text-align: center;
          ">
            <div style="color:rgba(255,255,255,0.35); font-size:7px; font-weight:bold;">
              SAINT
            </div>
            <div style="
              color:var(--primary-text-color);
              font-size:13px;
              font-weight:900;
              white-space:nowrap;
            ">
              ${saint}
            </div>
          </div>

          <!-- COUCHER -->
          <div style="
            position: absolute;
            right: 28px;
            bottom: 0;
            text-align: right;
          ">
            <div style="color:rgba(255,255,255,0.25); font-size:7px; font-weight:bold;">
              COUCHER
            </div>
            <div style="color:#F44336; font-size:13px; font-weight:900;">
              ${s}
            </div>
          </div>

        </div>`;
    ]]]

1 « J'aime »

Merci pour cette option supplémentaire.

Slt…
C’est pas mal, mais je suprime le test si jour , dans la partie du choix pour Afficher soleil / Lune , permet de voir si La LUNE est pendant le JOUR !


Origine autre !

J'ai des entités spéciales pour la lune et le saint du Jour
type: custom:button-card
entity: sun.sun
show_name: false
show_icon: false
styles:
  card:
    - height: 160px
    - padding: 10px
    - border-radius: 15px
  grid:
    - grid-template-areas: |
        "arc"
        "heures"
    - grid-template-rows: 95px 45px
custom_fields:
  arc: |
    [[[
      const sun = states['sun.sun'];
      if (!sun) return "";

      const now = new Date();
      const nextR = new Date(sun.attributes.next_rising).getTime();
      const nextS = new Date(sun.attributes.next_setting).getTime();
      const el = sun.attributes.elevation;
      const isDay = el > 0;

      // === CALCUL TEMPOREL CORRECT (jour / nuit) ===
      let start, end;

      if (isDay) {
        // lever -> coucher
        start = nextR < nextS ? nextR : nextR - 86400000;
        end = nextS;
      } else {
        // coucher -> lever
        start = nextS < nextR ? nextS : nextS - 86400000;
        end = nextR;
      }

      const t = Math.min(Math.max((now.getTime() - start) / (end - start), 0), 1);

      // === POSITION SUR ARC (100 % complet) ===
      const x = t * 240 + 30;
      const y = (1 - t) * (1 - t) * 85
              + 2 * (1 - t) * t * -10
              + t * t * 85;

      const moon = states['sensor.moon_phase'] ? states['sensor.moon_phase'].state : 'full_moon';

      let visual = "";
      let color = "#FFCE20";


        const ratio = Math.min(Math.max(el / 40, 0), 1);
        const g = Math.round(130 + (125 * ratio));
        const b = Math.round(0 + (120 * ratio));
        color = `rgb(255, ${g}, ${b})`;

        visual = `
          <circle cx="${x}" cy="${y}" r="7"
            fill="${color}"
            style="transition: all 1s; filter: drop-shadow(0 0 10px ${color});" />`;

        const phases = {
          'new_moon': 0.1,
          'waxing_crescent': 0.3,
          'first_quarter': 0.5,
          'waxing_gibbous': 0.8,
          'full_moon': 1,
          'waning_gibbous': 0.8,
          'last_quarter': 0.5,
          'waning_crescent': 0.3
        };
        let p = phases[moon] ?? 1;
        const r = 10;
        const s = p > 0.5 ? 0 : 1;
        const iR = Math.max(Math.abs(p - 0.5) * 2.2 * r, 0.5);

        visual = `
          <g transform="translate(${x}, ${y})">
            <circle r="${r}" fill="#1a1a1a"
              stroke="rgba(93,169,221,0.2)" stroke-width="0.5"/>
            <path d="M 0 ${-r} A ${r} ${r} 0 1 ${s} 0 ${r}
                     A ${iR} ${r} 0 1 ${1-s} 0 ${-r}"
              fill="#5da9dd"
              style="filter: drop-shadow(0 0 8px #3a7bd5);" />
          </g>`;


      const hColor = isDay
        ? color.replace('rgb', 'rgba').replace(')', ', 0.15)')
        : 'rgba(58,123,213,0.15)';

      const time = now.toLocaleTimeString('fr-FR', {
        hour: '2-digit',
        minute: '2-digit'
      });

      return `
        <svg viewBox="0 0 300 100" style="width:100%; height:100%; overflow: visible;">
          <defs>
            <radialGradient id="h" cx="${x/3}%" cy="${y}%" r="60%">
              <stop offset="0%" stop-color="${hColor}" />
              <stop offset="100%" stop-color="transparent" />
            </radialGradient>
          </defs>

          <rect width="300" height="100" fill="url(#h)" />

          <path d="M 30 85 Q 150 -10 270 85"
                fill="none"
                stroke="rgba(255,255,255,0.1)"
                stroke-width="2"
                stroke-dasharray="4,4" />

          ${visual}

          <text x="${x}" y="${y + 18}" text-anchor="middle"
                fill="rgba(255,255,255,0.9)"
                style="font-size:8px; font-weight:700;">
            ${time}
          </text>

          <line x1="20" y1="86" x2="280" y2="86"
                stroke="rgba(255,255,255,0.05)" stroke-width="1" />
        </svg>`;
    ]]]
  heures: |
    [[[
      const sun = states['sun.sun'];
      if (!sun) return "";

      const r = new Date(sun.attributes.next_rising)
        .toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'});
      const s = new Date(sun.attributes.next_setting)
        .toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'});

      const saint = states['sensor.saint_france_portugal']
        ? states['sensor.saint_france_portugal'].state
        : '';

      return `
        <div style="
          position: relative;
          width: 100%;
          height: 32px;
          margin-top: -6px;
        ">

          <!-- LEVER -->
          <div style="
            position: absolute;
            left: 28px;
            bottom: 0;
            text-align: left;
          ">
            <div style="color:rgba(255,255,255,0.25); font-size:7px; font-weight:bold;">
              LEVER
            </div>
            <div style="color:#FFA726; font-size:13px; font-weight:900;">
              ${r}
            </div>
          </div>

          <!-- SAINT (centré, discret) -->
          <div style="
            position: absolute;
            left: 50%;
            transform: translateX(-50%);
            bottom: -2px;
            text-align: center;
          ">
            <div style="color:rgba(255,255,255,0.35); font-size:7px; font-weight:bold;">
              SAINT
            </div>
            <div style="
              color:var(--primary-text-color);
              font-size:13px;
              font-weight:900;
              white-space:nowrap;
            ">
              ${saint}
            </div>
          </div>

          <!-- COUCHER -->
          <div style="
            position: absolute;
            right: 28px;
            bottom: 0;
            text-align: right;
          ">
            <div style="color:rgba(255,255,255,0.25); font-size:7px; font-weight:bold;">
              COUCHER
            </div>
            <div style="color:#F44336; font-size:13px; font-weight:900;">
              ${s}
            </div>
          </div>

        </div>`;
    ]]]

Chacun peut l’adapter à sa façon.
Le soucis c’est que la lune et soleil n’ont pas le meme azimut et élévation donc on ne devrait pas les faire afficher sur le même arc de cercle. La journée la carte est pour le soleil et l’arc de cercle peut correspondre à la trajectoire du soleil et la nuit l’arc de cercle peut correspondre à la trajectoire de la lune, d’où la condition de jour ou nuit.

1 « J'aime »