A different take on designing a Lovelace UI

did you found a solution? get the same problem

I’ve just finished bleeding of the setup for the old one, I will enjoy it for a while before to try the new!

1 Like

yeah and its still pre beta… not really all the functions and bugs. You can’t make it the default Dashboard, what is a no go atm.

I got a request via chat to add some more picture, if naybody like I can add the adapted code for my personal need which I have taken out of these thread

1buero

Büro/Office: I add in all main rooms icons in the upper right corner which indicate if the radiator is on or off, if on then the icon is blue. Beside the temp icon I add an window open or close icon which check if window open detection is on then there is an grey icon and when the window is detected as open over the radiator then the icon is blue.
By the weather part I just add a fourth indication and middle the whole part for my person look and feel.

climate_on

The radiator background gets white when heating is on, the icon gets blue and the valve state of the ventil is under the icon whit the related value. When the radiator is in eco mode the icon will be green otherwise gray as above. Current temp is in the circle and on the left side the expected temp.

1kueche

Dishwasher integration with related pop up taken from Ngoc git.

2sidebar

Add small icon for blink cameras, pi-hole, low battery, ambilight, light who are on and battery state of the tablet.

Media part actually with gallery function, which is played stored pictures when no media.player is playing. Currently I´m adding trakt as sensor which is working and maybe add kodi as well.

The popup for the lights filter first and second floor and show only active lamps.

battery

Battery icon everywhere where it is needed appear when battery is low.

All set up flexible as possible for me over variables to add the additional function icons everywhere I need to have them.

3 Likes

Hello, I have been trying to implement this now for some days but I’m struggling with the background of the popup. I can’t find anywhere where it gets its background. Can you please point me in the right direction?

Thx.

Hello, can you share the codes, I’m interested in the ones from the thermostats. Tnx

Hi, here the code for the radiator:
I split the battery and valve parts, so that I can add them in other cards where I want to have these parts alone.

                  - type: custom:button-card
                    entity: climate.wohnzimmer
                    name: Front
                    show_state: false
                    tap_action:
                      action: more-info
                    custom_fields:
                      graph:
                        card:
                          entities:
                            - entity: sensor.wohnzimmer_temperature
                            - entity: sensor.wohnzimmer_position
                              color: gray
                              y_axis: secondary
                    variables:
                      valve: '[[[ return states["sensor.wohnzimmer_position"].state]]]'
                      battery_temp: '[[[ return states["sensor.wohnzimmer_battery"].state]]]'
                    triggers_update:
                      - sensor.wohnzimmer_temperature
                      - sensor.wohnzimmer_position
                    template: [valve, radiator, battery_temp]

radiator:
  template:
    - base
    - circle
  variables:
    state: >
      [[[
        return entity?.attributes.hvac_action;
      ]]]
    state_on: >
      [[[
        return entity?.attributes.hvac_action === 'heating';
      ]]]
    circle_input: >
      [[[
        if (entity) {
          const temp = entity.attributes.current_temperature;
          return parseFloat(temp).toFixed(1);
        }
      ]]]
    circle_input_unit: '°C'
  state_display: >
    [[[ return entity.attributes.hvac_action === 'idle' ? 'Aus' : 'Heizen'; ]]]
  custom_fields:
    graph:
      card:
        type: "custom:mini-graph-card"
        height: 140
        hours_to_show: 24
        points_per_hour: 1
        line_width: 8
        font_size: 75
        decimals: 0
        show:
          name: false
          icon: false
          state: false
          legend: false
          points: false
          fill: fade
          animate: true
        color_thresholds:
          - color: '#276696'
            value: 17
          - color: '#22878c'
            value: 18
          - color: '#228c3d'
            value: 19
          - color: '#d35400'
            value: 20
          - color: '#e6370b'
            value: 21
          - color: '#cc1f08'
            value: 22
        card_mod:
          style: |
            :host{
                  --ha-card-border-width: 0px;
                 }
    icon: |
      [[[
        var action = entity.attributes.hvac_action;
        var eco_mode = entity.attributes.eco_mode;
        var state = entity.state;
        var svg_color;
    
        if (state === 'off') {
          svg_color = '#127612';
        } else if (action === 'heating') {
          svg_color = '#3182b7'; 
        } else if (action === 'idle' && eco_mode === 'ON') {
          svg_color = '#127612';
        } else {
          svg_color = '#97989c';
        }
    
        var temperature = entity.attributes.temperature;
        var current_temperature = entity.attributes.current_temperature;
        var text_color;
    
        if (temperature === current_temperature) {
          text_color = '#97989c';  // Grau, wenn gleich
        } else {
          text_color = action === 'heating' ? '#3182b7' : (action === 'idle' && eco_mode === 'ON' ? '#127612' : '#3182b7');
        }
        return `<div style="display: flex; align-items: center; justify-content: flex-start; position: relative; width: 87%;">
                  <svg viewBox="-949 951 100 125"><style>@keyframes animate{0%{transform: scale(0.85);}20%{transform: scale(1.1);}40%{transform: scale(0.95);}60%{transform: scale(1.03);}80%{transform: scale(0.97);}100%{transform: scale(1);}}.animate{animation: animate 0.8s; transform-origin: center;}</style>
        <path fill="#9da0a2" d="M-895.9,1020.1v-56.7c0-5.5-4.5-10-10-10c-5.5,0-10,4.5-10,10v56.7c-3.7,3-6,7.6-6,12.4c0,8.8,7.2,16,16,16 c8.8,0,15.9-7.2,15.9-16C-890,1027.7-892.2,1023.1-895.9,1020.1z M-905.9,1045.1c-6.9,0-12.5-5.6-12.5-12.5c0-4.4,2.3-8.4,6-10.7 v-58.4c0-3.6,2.9-6.6,6.6-6.6c3.6,0,6.6,2.9,6.6,6.6v58.4c3.6,2.3,6,6.3,6,10.7C-893.4,1039.5-899,1045.1-905.9,1045.1z"/>
        <path fill="${svg_color}" d="M-902.8,1024v-22.9c0-1.7-1.4-3.1-3.1-3.1c-1.7,0-3.1,1.4-3.1,3.1v22.9c-3.5,1.3-6,4.6-6,8.5c0,5,4.1,9.1,9.1,9.1 c5,0,9.1-4.1,9.1-9.1C-896.8,1028.6-899.3,1025.3-902.8,1024z"/>
        <rect fill="#9da0a2" x="-892.4" y="974.9" width="7.7" height="3.4"/>
        <rect fill="#9da0a2" x="-892.4" y="986.3" width="16.3" height="3.4"/>
        <rect fill="#9da0a2" x="-892.4" y="997.6" width="7.7" height="3.4"/>
        <rect fill="#9da0a2"v x="-892.4" y="1008.9" width="16.3" height="3.4"/>
        </svg>
                  <span style="position: absolute; right: -12%; top: 5%; transform: translateY(-50%); font-size: calc(var(--button-card-font-size) * 0.6);">${temperature}°C</span>
                </div>`;
      ]]]
  styles:
    card:
      - background-color: >
          [[[
            return entity?.attributes.hvac_action === 'heating'
                ? 'rgba(255, 255, 255, 0.85)'
                : 'rgba(115, 115, 115, 0.25)';
          ]]]
      - color: >
          [[[
            return entity?.attributes.hvac_action === 'heating'
                ? '#3182b7'
                : '#97989c';
          ]]]
    custom_fields:
      icon:
        - width: 108%
        - margin-left: -27%
      temperature:
        - font-size: calc(var(--button-card-font-size) * 0.6)
        - right: 10%
        - top: 50%
        - transform: translateY(-50%)
      graph:
        - bottom: 0%
        - left: 0%
        - width: 130%
        - position: absolute
        - margin: 0% 0% -13% -15%
      circle:
        - display: initial
        - width: 90%
        - margin: -6% -5% 0 0
        - justify-self: end
        - opacity: 1
        - --c-fill-color-on: rgba(255,255,255,0.04)
        - --c-stroke-color-on: '#3182b7'
        - --c-fill-color-off: rgba(255,255,255,0.04)
        - --c-stroke-color-off: 'none'
    style: |
      ha-card {
              box-shadow: none;
              }
    name:
      - place-self: start
      - margin-top: -55%

valve:
  styles:
    custom_fields:
      valve:
        - position: absolute
        - right: -6%
        - top: 58%
        - font-size: calc(var(--button-card-font-size) * .2 * var(--card-phone))
        - transition: top 250ms ease-out
        - '--text-color-sensor': >-
           [[[ 
             var hvac_action = states[entity.entity_id].attributes.hvac_action;
             if (hvac_action === 'heating' || variables.valve > 50) return "#3182b7";
             else if (variables.valve >= 1 && hvac_action === 'heating') return "#3182b7";
             return "#97989c";
           ]]]
  custom_fields:
    valve: |
      [[[
        var hvac_action = states[entity.entity_id].attributes.hvac_action;
        var valve_icon = hvac_action === 'heating' ? 'open' : 'closed';
        return `<ha-icon icon="mdi:valve-${valve_icon}" style="width:10%; color: var(--text-color-sensor);"></ha-icon><span>${variables.valve}%</span>`;
      ]]]

battery_temp:
  custom_fields:
    battery_temp: |
      [[[
        if (variables.battery_temp == '100') { 
          return `<ha-icon icon="" style="width:100%;display:flex;color: #EF4F1A;"></ha-icon>`;
        }
        if (variables.battery_temp == 'unavailable') { 
          return `<ha-icon icon="mdi:battery-alert-variant-outline" style="width:100%;display:flex;display:flex;color: #EF4F1A;"></ha-icon>`;
        }
        if (variables.battery_temp <= '10') { 
          return `<ha-icon icon="mdi:battery-alert-variant-outline" style="width:100%;display:flex;color: #EF4F1A;"></ha-icon>`;
        }
        if (variables.battery_temp <= '20') {
          return `<ha-icon icon="mdi:battery-20" style="width:100%;display:flex;color: #EF4F1A;"></ha-icon>`;
        }
        if (variables.battery_temp == 'on') { 
          return `<ha-icon icon="mdi:battery-alert-variant-outline" style="width:100%;display:flex;color: #EF4F1A;"></ha-icon>`;
        }
        else {
          return `<ha-icon icon="" style="width:100%;display:flex;color: #EF4F1A;"></ha-icon>`;
        }
      ]]]
  styles:
    custom_fields:
      battery_temp:
        - position: absolute
#        - height: auto
        - transition: 0.25s
        - width: 14%
#        - right: -9%
#        - padding: 5%
#        - margin: -10%
#        - top: 65%
        - right: |
            [[[
              return entity.state === 'on'
                ? '-11%'
                : '-11%';
            ]]]
        - top: |
            [[[
              return entity.state === 'on'
                ? '44%'
                : '44%';
            ]]]

the sensors I used:

#  - platform: template
#    sensors:
      wohnzimmer_temperature:
        friendly_name: Wohnzimmer Temperatur
        unit_of_measurement: '°C'
        value_template: "{{ state_attr('climate.wohnzimmer', 'current_temperature') }}"

#  - platform: template
#    sensors:
      wohnzimmer_position:
        friendly_name: Wohnzimmer Position
        unit_of_measurement: '%'
        value_template: "{{ state_attr('climate.wohnzimmer', 'position') }}"

I hope I didn´t missed anything

Hi VietNgoc,

thanks for sharing your git. Can you tell me how you define the related kodi sensor for the movie intergration:

sensor.kodi_added_movie_*
with
added_movie

      - type: grid
        title: Movie
        view_layout:
          grid-area: movie
        columns: 1
        cards:
          - type: custom:auto-entities
            filter:
              include:
                - entity_id: 'sensor.kodi_added_movie_*'
                  options:
                    type: custom:button-card
                    template:
                      - added_movies
            card:
              type: custom:swipe-card
              parameters:
                watchSlidesProgress: false
                slidesPerView: 1
                speed: 700
                roundLengths: true
                grabCursor: false
                followFinger: false
                pagination:
                  type: bullets
                  clickable: true
                  dynamicBullets: true
                  dynamicMainBullets: 1
                effect: cube
                cubeEffect:
                  shadow: false
                autoplay:
                  delay: 10000
                  disableOnInteraction: false
                  pauseOnMouseEnter: true
            card_param: cards
            sort:
              count: 10

the needed part for my need from your media integration I got, but the kodi sensor confuses me…

swipe

I am trying to make a dashboard for mobile

I use a swiper card, but at the right, the card is not fully there
i tried card width, but that doesnt change

  - type: custom:swipe-card
    card_width: auto

any ideas ?

edit: when I resize my window on desktop and resize it to original, it is ok…

as rquested by message:
widget_weather.yaml

widget_weather:
  template:
    - base
  variables:
    temp_min: ''
    temp_max: ''
    humidity: ''
    feels: ''
    current_weather: ''
    is_bellow_horrizon: >
      [[[
        if (states['sun.sun'].state == 'below_horizon') {
            return true;
        }
      ]]]
  aspect_ratio: 1/1
  show_icon: false
  show_entity_picture: true
  show_name: true
  show_state: true
  show_label: true
  tap_action:
    action: more-info
  styles:
    grid:
      - grid-template-areas: |
          "n"
          "temp"
          "i"
          "s"
          "l"
      - grid-template-columns: 1fr
      - grid-template-rows: 1fr 1fr
      - gap: 0%
      - overflow: visible
    card:
      - padding: 11.5% 10.5% 10.5% 11.5%
      - color: rgba(255, 255, 255, 0.6)
      - background: >
          [[[
            let weather = states[variables.current_weather].state.toLowerCase();
            let now = new Date();
            let isDaytime = now.getHours() >= 6 && now.getHours() < 18;
            if (!isDaytime) {
              return `linear-gradient(to top, rgba(255, 192, 203, 0.5) 0%, rgba(90,113,157,0.4) 100%) 100% / cover, url(/local/svg/weather/gif/${weather}-night.gif)`;
            } else {
              return `linear-gradient(to top, rgba(255, 192, 203, 0.5) 0%, rgba(90,113,157,0.4) 100%) 100% / cover, url(/local/svg/weather/gif/${weather}-day.gif)`;
            }
          ]]]
      - background-size: cover
    state:
#      - text-transform: uppercase
      - margin-top: 5px
      - line-height: 100%
    name:
      - place-self: start
#      - text-transform: uppercase
      - font-weight: 500
      - font-family: Futura
      - letter-spacing: 0.1vw
      - margin-top: -7%
    img_cell:
      - justify-content: start
      - overflow: visible
    icon:
      - width: 25%
      - margin-top: -26px
    label:
      - place-self: center
      - text-align: center
      - margin-top: 3%
      - margin-bottom: -7%
      - align-items: center
    custom_fields:
      temp:
        - place-self: start
        - margin-top: -27px
        - font-family: Futura
        - color: rgba(240, 255, 255, 0.8)
  label: >
    [[[
      return `
        <table style="width: 100%; text-align: left;">
          <tr>
            <td style="width: 20%;">
              <ha-icon icon="mdi:water-percent" style="width: 1.5em; height: 1.5em;"></ha-icon>
            </td>
            <td style="width: 30%;">
              <span>${states[variables.humidity].state}%</span>
            </td>
            <td style="width: 50%; text-align: center;">
              -
            <td style="width: 20%;">
              <ha-icon icon="mdi:weather-windy" style="width: 1.5em; height: 1.5em;"></ha-icon>
            </td>
            <td style="width: 30%;">
              <span>${states[variables.temp_max].state}kmh</span>
            </td>
          </tr>
          <tr>
            <td style="width: 20%;">
              <ha-icon icon="mdi:thermometer" style="width: 1.5em; height: 1.5em;"></ha-icon>
            </td>
            <td style="width: 30%;">
              <span>${states[variables.temp_min].state}°C</span>
            </td>
            <td style="width: 50%; text-align: center;">
              -
            </td>
            <td style="width: 20%;">
              <ha-icon icon="mdi:weather-sunny" style="width: 1.5em; height: 1.5em;"></ha-icon>
            </td>
            <td style="width: 30%;">
              <span>${states[variables.feels].state}°C</span>
            </td>
          </tr>
        </table>
      `
    ]]]
  custom_fields:
    temp: >
      [[[ return entity.attributes.temperature + "°C"; ]]]
  entity_picture: >
    [[[
      let weather = states[variables.current_weather].state.toLowerCase();
      if ((weather == 'sunny') && (states['sun.sun'].state == 'above_horizon'))
        return "/local/svg/weather/clear-day.svg";
        if ((weather == 'sunny') || (weather == 'clear-night') && (states['sun.sun'].state == 'below_horizon'))
          return "/local/svg/weather/clear-night.svg";
            if (weather == 'fog')
              return "/local/svg/weather/fog.svg";
                if ((weather == 'partlycloudy') && (states['sun.sun'].state == 'above_horizon'))
                  return "/local/svg/weather/partly-cloudy-day.svg";
                    if ((weather == 'partlycloudy') && (states['sun.sun'].state == 'below_horizon'))
                      return "/local/svg/weather/partly-cloudy-night.svg";
                        if (weather == 'rainy')
                          return "/local/svg/weather/rain.svg";
                            if (weather == 'sleet')
                              return "/local/svg/weather/sleet.svg";
                                if (weather == 'snow')
                                  return "/local/svg/weather/snow.svg";
                                    if (weather == 'cloudy')
                                      return "/local/svg/weather/cloudy.svg";
      else (weather == 'wind')
        return "/local/svg/weather/wind.svg";
    ]]]
  extra_styles: |
    [[[
      return `
        #name {
          font-size: 0.9vw;
        }
        #temp {
          font-size: 2.0vw;
        }
        #state {
          font-size: 0.9vw;
        }
        #label {
          font-size: 0.7vw;
          white-space: wrap;
        }
        @media screen and (max-width: 1500px) {
          #name {
            font-size: 1.1vw;
          }
          #temp {
            font-size: 2vw;
          }
          #state {
            font-size: 1.2vw;
          }
          #label {
            font-size: 0.7vw;
          }
        @media screen and (max-width: 800px) {
          #name {
            font-size: 3vw;
            margin-top: -5%;
          }
          #temp {
            font-size: 4vw;
            margin-top: -4px !important;
          }
          #state {
            font-size: 2.1vw;
          }
          #icon {
            display: none !important;
          }
          #label {
            font-size: 1.5vw;
            font-weight: 500 !important;
            margin-top: 5%;
            margin-bottom: 0%;
          }
        }
      `
    ]]]

ui_lovelace.yaml:

                  - type: custom:button-card
                    entity: weather.openweathermap
                    name: Steninge
                    double_tap_action:
                      action: more-info
                    tap_action:
                      !include popup/weather.yaml
                    template:
                      - widget_weather
                    variables:
                      temp_min: sensor.aussen_rain
                      temp_max: sensor.aussen_wind
                      humidity: sensor.aussen_humidity
                      feels: sensor.aussen_feels
                      current_weather: weather.openweathermap

sensors need to defined and pictures must be in the right folders.

1 Like

Andrew, can you explane me exactly what you do to fit it at a iPad?

May somebody is interested. I always faced the problem that Sonos show no picture when source is TV. I didn´d find a way around in the thread yet, so I take some time and add a fixed picture for Sonos.
m1

Furthermore I add that also devices which have the state “on” instead of “playing” is now shown as well, for example
m2
m3

Then, if more than one device is playing or on will now swap every 30 seconds.

tv_media.yaml

homeassistant:
  customize:
    media_player.tv_wohnzimmer_2:
      friendly_name: TV Wohnzimmer
      device_class: speaker
    media_player.lea:
      friendly_name: Sonos Lea
      device_class: speaker
    media_player.vardagsrum:
      friendly_name: Sonos
      device_class: speaker
    sensor.ps5_101_activity:
      friendly_name: PS5 Activity
      device_class: tv
    media_player.fernseher_maja_2:
      friendly_name: TV Maja
      device_class: tv
    media_player.tv_schlafzimmer_tv:
      friendly_name: Schlafzimmer
      device_class: tv
    media_player.wohnzimmer_oben:
      friendly_name: Echo Wohnzimmer Oben
      device_class: speaker
    media_player.schlafzimmer:
      friendly_name: Echo Schlafzimmer
      device_class: speaker
    media_player.buro:
      friendly_name: Echo Büro
      device_class: speaker
    media_player.maja:
      friendly_name: Echo Maja
      device_class: speaker
    media_player.kuche:
      friendly_name: Echo Küche
      device_class: speaker
    media_player.dm900_2:
      friendly_name: dm900
      device_class: tv

template:
  - select:
      - name: conditional_media
        state: >
          {% set recently_added = 'Neu hinzugefügt' %}
          {% set media_players = [
            states.media_player.tv_wohnzimmer_2,
            states.media_player.vardagsrum,
            states.media_player.lea,
            states.sensor.ps5_101_activity,
            states.media_player.wohnzimmer_oben,
            states.media_player.schlafzimmer,
            states.media_player.schlafzimmer_tv,
            states.media_player.dm900_2,
            states.media_player.buro,
            states.media_player.kuche,
            states.media_player.maja ] %}

          {% macro media(state) %}
          {% set state = media_players | selectattr('state','eq',state) | list %}
          {% set last_changed = recently_added if state | length == 0 else state | map(attribute='last_changed') | list | max %}
            {{ state | selectattr('last_changed','eq', last_changed) | map(attribute='name') | list | join }}
          {% endmacro %}
          
          {% set active_players = media_players | selectattr('state','in',['playing','on']) | list %}
          {% if active_players | length == 0 %}
            {{ recently_added }}
          {% else %}
            {% set current_index = (states('input_number.current_media_index') | int) % (active_players | length) %}
            {% set current_player = active_players[current_index] %}
            {{ current_player.name }}
          {% endif %}

        options: >
          {% set recently_added = ['Neu hinzugefügt'] %}
          {% set media_players = [
            states.media_player.tv_wohnzimmer_2,
            states.media_player.vardagsrum,
            states.media_player.lea,
            states.sensor.ps5_101_activity,
            states.media_player.wohnzimmer_oben,
            states.media_player.schlafzimmer,
            states.media_player.schlafzimmer_tv,
            states.media_player.dm900_2,
            states.media_player.buro,
            states.media_player.maja,
            states.media_player.kuche ] %}
          {{ recently_added + media_players | map(attribute='name') | list }}

        select_option:
          service: select.select_option
          target:
            entity_id: select.conditional_media
          data:
            option: >
              {{ option }}

# Additional Home Assistant configuration to handle index rotation
input_number:
  current_media_index:
    name: Current Media Index
    initial: 0
    min: 0
    max: 1000
    step: 1
  #################################################
  #                                               #
  #               CONDITIONAL MEDIA               #
  #                                               #
  #################################################
conditional_media:
  aspect_ratio: 1000/996
  template:
    - base
    - base_media
    - icon_play_pause
    # - icon_playing_bars
  variables:
    i: >
      [[[
        if (entity) {
            let data = entity.attributes.data;
            return data === undefined || Math.floor(Math.random() * (data.length - 1)) + 1;
        }
      ]]]
  state_display: >
    [[[
      let horizontalStack = this.getRootNode().host,
          swipeCard = horizontalStack.getRootNode().host,
          gridTitle = swipeCard.getRootNode().querySelector("h1");
      swipeCard.swiper.on("slideChange", () => {
          if (swipeCard.swiper.realIndex === 0) {
              gridTitle.textContent = "Media";
          }
          else if (swipeCard.swiper.realIndex === 1) {
              gridTitle.textContent = "Players";
          }
      });
      if (entity) {
          let elt = this.shadowRoot,
              await = setTimeout(marquee, 100),
              data = entity.attributes.data,
              artist = entity.attributes.media_artist,
              title = entity.attributes.media_title,
              originaltitle = entity.attributes.originaltitle,
              game_title = entity.attributes.title_name;
          var output = artist === undefined && title !== undefined
            ? title
            : game_title !== undefined
              ? `${game_title}`
              : title === undefined && artist !== undefined
                  ? artist
                  : title !== undefined && artist !== undefined
                      ? `<div id="title" style="font-weight: 800; text-transform: uppercase; font-family: Raleway; letter-spacing: 0.07vw;">${title}</div><span style="font-size: 0.90em; color: #efefefd1; font-family: Raleway; text-transform: capitalize; letter-spacing: 0.03vw;">${artist}</span>`
                      : variables.translate_idle;
          function marquee() {
              let state = elt.getElementById("title"),
                  container = elt.getElementById("container");
              if (state && container) {
                  state.innerHTML = title;
                  let ro = new ResizeObserver(entries => {
                      let spacer = "&nbsp;".repeat(3),
                          s = entries[0],
                          c = entries[1],
                          r = s && s.contentRect &&
                              c && c.contentRect &&
                              s.contentRect.width !== 0 &&
                              c.contentRect.width !== 0;
                      if (r && s.contentRect.width < c.contentRect.width) {
                          state.classList.remove("marquee");
                      }
                      else if (r && s.contentRect.width >= c.contentRect.width) {
                          state.innerHTML = `${title} ${spacer} ${title} &nbsp;`;
                          state.classList.add("marquee");
                      }
                  });
                  ro.observe(state);
                  ro.observe(container);
              }
          }
          return output;
      }
    ]]]
  styles:
    grid:
      - gap: 0.65%
    name:
      - justify-self: flex-start
      - line-height: 120%
      - padding: 0.2vw
      - margin: -0.2vw
      - color: rgba(255,255,255,0.6)
      - display: >
          [[[
            if (window.matchMedia('(max-width: 800px)').matches)
              return 'none';
            if (window.matchMedia('(max-width: 1440px)').matches)
              return 'block';
            else return 'block';
          ]]]
    state:
      - line-height: 120%
      - max-width: 100%
      - text-shadow: 3px 1px 4px black
      - text-transform: uppercase
      - padding-bottom: >
          [[[
            return variables.is_kodi || variables.media_on ? '9%' : '4%';
          ]]]
      - overflow: visible
    card:
      - padding: 5.75% 5.75% 0 5.75%
      - border-radius: calc(var(--button-card-border-radius) / 2)
      - background: &media_background >
          [[[
            if (entity) {
              if (variables.is_youtube) {
                  return `rgba(115, 115, 115, 0.2) center center/cover no-repeat`;
              } else {
                let data = entity.attributes.data;
                return data && (data[variables.i].entity_picture)
                    ? `rgba(115, 115, 115, 0.2) center center/cover no-repeat`
                    :  data && (data[variables.i].fanart)
                      ? `rgba(115, 115, 115, 0.2) top center/cover no-repeat`
                      : `rgba(115, 115, 115, 0.2) center center/cover no-repeat`;
              }
            }
          ]]]
      - background-image: &media_background_image >
          [[[
            if (entity) {
              if (variables.is_youtube) {
                  return `url(${states[this._config?.triggers_update].state})`;
              }
              if (entity?.attributes?.app_id === 'com.bamtechmedia.dominguez.main.MainActivity-com.disney.disneyplus') {
                  return `url(/local/icons/Disney+.jpg)`;
              }
              if (entity?.attributes?.app_id === 'com.amazon.ignition.IgnitionActivity-com.amazon.amazonvideo.livingroom') {
                  return `url(/local/icons/prime.video.jpg)`;
              }
              if (entity?.attributes?.app_id === 'com.amazon.amazonvideo.livingroom') {
                  return `url(/local/icons/prime.video.jpg)`;
              }
              if (entity?.attributes?.app_id === 'com.netflix.ninja') {
                  return `url(/local/icons/netflix.png)`;
              }
              if (entity?.attributes?.app_id === 'de.cyberdream.dreamepg.MainActivityTV-de.cyberdream.dreamepg.tv.player') {
                  return `url(/local/icons/cyberdream.png)`;
              }
              if (entity?.attributes?.app_id === 'com.nordvpn.android.tv.MainActivity-com.nordvpn.android') {
                  return `url(/local/icons/nordvpn.png)`;
              }
              if (entity?.attributes?.app_id === 'se.svt.svtplay.ui.tv.profile.ProfilePickerActivity-se.svt.android.svtplay') {
                  return `url(/local/icons/svt.jpg)`;
              }
              if (entity?.attributes?.media_title === 'TV') {
                  return `url(/local/icons/fernsehen.png)`;
              }
              if (entity?.attributes?.media_content_type === 'tvshow') {
                  return `url(/local/icons/fernsehen.png)`;
              }
              if (entity?.attributes?.activity === 'idle') {
                  return `url(/local/icons/cyberdream.png)`;
              }
              if (entity?.attributes?.app_id === 'org.droidtv.channels.ChannelsActivity-org.droidtv.channels') {
                  return `url(/local/icons/fernsehen.png)`;
              }
              else {
                let data = entity.attributes.data,
                game_image = entity.attributes.title_image;
                return game_image !== undefined
                  ? `url("${game_image}")`
                  : data && (data[variables.i].poster || data[variables.i].fanart)
                    ? `linear-gradient(to top, rgba(0, 0, 0, .65) 0%, rgba(0, 0, 0, 0) 100%), url("${data[variables.i].entity_picture}"), url("${data[variables.i].fanart}")`
                    : `url("${variables.entity_picture}")`;
              }
            }
          ]]]
      - color: >
          [[[
            return entity === undefined
                ? '#97989c'
                : '#efefef';
          ]]]
      - text-shadow: >
          [[[
            return entity === undefined
                ? 'none'
                : '1px 1px 5px rgba(18, 22, 23, 0.9)';
          ]]]
    custom_fields:
      icon:
        - width: 30%
        - fill: >
            [[[
              return entity && variables.media_on
                  ? 'rgba(255, 255, 255, 0.80)'
                  : '#9da0a2';
            ]]]
      blur_overlay:
        - display: block
        - position: absolute
        - width: 122%
        - height: 101%
        - filter: var(--blur-intensity)
        - clip-path: >
            [[[
              if (entity) {
                if (variables.is_youtube) {
                    return `inset(74% 7% 3.45% 9% round 0 0 calc(var(--button-card-border-radius) / 2) calc(var(--button-card-border-radius) / 2))`;
                } else {
                  return `inset(80% 3.45% 3.45% 5.45% round 0 0 calc(var(--button-card-border-radius) / 2) calc(var(--button-card-border-radius) / 2))`;
                }
              }
            ]]]
        - background: *media_background
        - background-image: *media_background_image
        # - left: -10%
        # - bottom: -5%
  custom_fields:
    blur_overlay: >
      [[[
        setTimeout(() => {
            let elt = this.shadowRoot,
                card = elt.getElementById('card'),
                container = elt.getElementById('container'),
                blur_overlay = elt.getElementById('blur_overlay');
            if (elt && card && container && blur_overlay) {
                card.insertBefore(blur_overlay, container);
            }
          }, 0);
        return ' ';
      ]]]

automation for a 30sec refresh:

alias: Rotate Media Player
description: ""
trigger:
  - platform: time_pattern
    seconds: /30
action:
  - service: input_number.set_value
    data_template:
      entity_id: input_number.current_media_index
      value: >
        {% set current_index = states('input_number.current_media_index') | int
        %} {% set active_players = [
          states.media_player.tv_wohnzimmer_2,
          states.media_player.tv_wohnzimmer,
          states.media_player.vardagsrum,
          states.media_player.lea,
          states.sensor.ps5_101_activity,
          states.media_player.wohnzimmer_oben,
          states.media_player.schlafzimmer,
          states.media_player.schlafzimmer_tv,
          states.media_player.dm900_2,
          states.media_player.buro,
          states.media_player.kuche,
          states.media_player.maja ] | selectattr('state','in',['playing','on']) | list %}
        {% set new_index = (current_index + 1) % (active_players | length) %} {{
        new_index }}

Hello everybody. Very interesting project @Mattias_Persson , I’ve been using it for a long time. But now I would like to insert the value of the temperature and humidity sensors in the name of the room… is this possible?

Very thanks

1 Like

Hi, recently I created a template for this change, I share it with everyone…

add 2 variables as temp and humid sensors. add to button card. see below, then create a new template for this change.

    type: custom:button-card
    entity: light.living_room_group
    name: Lounge
    double_tap_action: !include ../shared/popup/rooms/livingroom.yaml
    hold_action: !include ../shared/honeycomb/living.yaml
    template:
      - rooms_base
      - icon_couch_lamp
      - change_grid_title
    variables:
      tempsensor: sensor.office_temp_humid_temperature
      humidsensor: sensor.office_temp_humid_humidity

add the new template also to the button card, this template is to change only the grid title, so you just need to add it to one of the cards in your grid card.

change_grid_title:
  state_display: >
    [[[
      // Add event listener to reset flag when dashboard changes
      window.addEventListener("location-changed", () => {
        // Reset the flag when the dashboard changes
        window.gridTitleProcessed = false;
      });

      if (!window.gridTitleProcessed) {
        let sensorState = parseFloat(states[variables.tempsensor].state),
            humidityState = parseFloat(states[variables.humidsensor].state);
        if (entity) {
          let parentElement = this.getRootNode().host;
          let headerTitle = findFirstH1Above(parentElement);
          let currentTitle = headerTitle.innerText;
          function findFirstH1Above(element) {
            let currentElement = element;

            while (currentElement) {
              // Check for <h1> in the current element's shadow root, if it exists
              if (currentElement.shadowRoot) {
                const shadowH1 = currentElement.shadowRoot.querySelector('h1');
                if (shadowH1) {
                  return shadowH1;
                }
              }

              // Check for <h1> in the current element's light DOM
              const lightH1 = currentElement.querySelector('h1');
              if (lightH1) {
                return lightH1;
              }

              // Move to the parent node
              currentElement = currentElement.getRootNode().host;

              // If the current element has no parent (we're at the document level), break the loop
              if (!currentElement) {
                break;
              }
            }

            console.log('No <h1> element found above the given element');
            return null;
          }
          headerTitle.style.display = 'flex';
          headerTitle.style.width = '100%';
          headerTitle.style.justifyContent = 'space-between';
          headerTitle.style.alignItems = 'center';
          headerTitle.style.paddingInline = 'inherit';
          headerTitle.innerHTML = `
          <div>${currentTitle}</div>
          <div style="font-size: x-large;">
            ${sensorState.toFixed(0)}°
            ${humidityState.toFixed(0)}%
          </div>
          `;
        }
        window.gridTitleProcessed = true;
      }
    ]]]

7 Likes

Hey VietNgoc,
great work from you.
I’ve been following your config on Github for a while, but can’t find this code from above there. Is the Github not up to date?

Hi, I upload to public repo mostly when I have some major change in code. Otherwise, I have already added this part… thanks :grimacing:

I’ve read somewhere that style is not working on mobile. is there a way to resize it another way N

This is truly amazing. I wasn’t able to do it because I lacked the skill, but thank you very much
image

Good afternoon everybody. I wanted to have the buttons at the bottom like “page 1, page 2, page 3, etc…” and change only part of the screen as shown in the photo. Buttons are no problem creating them. But is this possible to do? Has anyone done it?

Thanks

wrap all parts as one card, then use this plugin. But you will have a lot of work to assign the right template for buttons. Or easier if you make a new view, and assign the same sidebar to each one.

I had a similar method for my config for a while, but I don’t use it anymore. For demonstration I send example of view for my media library, example of use for switching pages Prev / Next

1 Like