I have searched and tested, the problem is browser, backdrop-filter. Try other browser. I use chrome, when I resize to “phone screen” then it blurs, otherwise not.
hey, can you share your config? thanks in advance!
hello, when i turn my tv off, im allways getting this error.
Anyone can help?
Thanks in advance
onthe base template i have:`
base:
template:
- settings
- tilt
- extra_styles
variables:
state_on: >
[[[ return ['on', 'home', 'cool', 'fan_only', 'playing', 'unlocked'].indexOf(!entity || entity.state) !== -1; ]]]
state: >
[[[ return !entity || entity.state; ]]]
entity_id: >
[[[ return !entity || entity.entity_id; ]]]
entity_picture: >
[[[ return !entity || entity.attributes.entity_picture; ]]]
timeout: >
[[[ return !entity || Date.now() - Date.parse(entity.last_changed); ]]]
is_youtube: >
[[[
let is_youtube = entity?.attributes?.app_id === 'com.google.ios.youtube',
sensor = this?._config?.triggers_update,
media_title = entity?.attributes?.media_title,
watching_title = states[sensor]?.attributes?.title;
if (is_youtube && media_title === watching_title) {
return true;
}
]]]
aspect_ratio: 1/1
show_state: true
show_icon: false
state_display: >
[[[
const stateDict = {
'on': variables.translate_on,
'off': variables.translate_off,
'cool': variables.translate_cool,
'fan_only': variables.translate_fan_only,
};
if (variables.state === true) return variables.translate_unknown;
return stateDict[variables.state];
]]]
tap_action:
ui_sound_tablet: |
[[[
let screensaver = states[variables.entity_tablet] === undefined ||
states[variables.entity_tablet].state;
if (variables.state === 'off' && screensaver === 'off') {
hass.callService('media_player', 'play_media', {
entity_id: variables.entity_browser_mod,
media_content_id: '/local/sound/on.m4a',
media_content_type: 'music'
});
}
if (variables.state_on && screensaver === 'off') {
hass.callService('media_player', 'play_media', {
entity_id: variables.entity_browser_mod,
media_content_id: '/local/sound/off.m4a',
media_content_type: 'music'
});
}
]]]
card_bounce: |
[[[
// add animation
if (this.getElementsByTagName("style").length === 0) {
// phone condition
let mq = window.matchMedia('(max-width: 800px)').matches;
let style = document.createElement('style');
style.innerHTML = `
@keyframes card_bounce {
0% { transform: scale(1); }
10% { transform: scale(${ mq ? '0.92' : '0.94' }); }
25% { transform: scale(1); }
30% { transform: scale(${ mq ? '0.96' : '0.98' }); }
100% { transform: scale(1); }
}
`;
this.appendChild(style);
}
// duration
let duration = 800;
// animate
this.style.animation = `card_bounce ${duration}ms cubic-bezier(0.22, 1, 0.36, 1)`;
// reset
window.setTimeout(() => { this.style.animation = "none"; }, duration + 100)
]]]
action: toggle
haptic: medium
double_tap_action:
haptic: success
hold_action:
action: block
styles:
grid:
- grid-template-areas: |
"icon circle"
"n n"
"s s"
- grid-template-columns: repeat(2, 1fr)
- grid-template-rows: auto repeat(2, min-content)
- gap: 1.3%
- align-items: start
- will-change: transform
name:
- justify-self: start
- line-height: 121%
state:
- justify-self: start
- line-height: 115%
card:
- border-radius: var(--button-card-border-radius)
- border-width: 0
- -webkit-tap-highlight-color: rgba(0,0,0,0)
- transition: none
- --mdc-ripple-color: >
[[[
return variables.state_on
? 'rgb(0, 0, 0)'
: '#97989c';
]]]
- color: >
[[[
return variables.state_on
? '#4b5254'
: '#97989c';
]]]
- background-color: >
[[[
return variables.state_on
? 'rgba(255, 255, 255, 0.85)'
: 'rgba(115, 115, 115, 0.25)';
]]]
and int the button:
` - type: custom:button-card
entity: media_player.lg_webos_smart_tv
name: Tv
state_display: >
[[[
if (variables.state === 'playing') {
return 'Playing';
}
if (variables.state === true) {
return variables.translate_unknown;
}
]]]
# double_tap_action: !include popup/sala_tv.yaml
template:
- base
- icon_tv`
Will you share your “gallery” function when no media played? Thx
sure. Install gallery_image over hacs.
ui-lovelace.yaml
#################################################
# #
# MEDIA #
# #
#################################################
- type: grid
title: Medien
view_layout:
grid-area: media
columns: 1
cards:
- type: custom:swipe-card
parameters:
touchStartPreventDefault: false
roundLengths: true
speed: 550
spaceBetween: 40
threshold: 5
autoHeight: false
initialSlide: 0
centeredSlides: true
slidesPerView: auto
# effect: cube loop: false
preventClicksPropagation: true
preventClicks: true
pagination:
type: bullets
cards:
- type: horizontal-stack
cards:
- type: conditional
conditions:
- entity: select.conditional_media
state: gallery
card:
type: custom:gallery-card
entities:
- sensor.gallery_images
menu_alignment: hidden
slideshow_timer: 5
card_mod:
style: |
ha-card {
border-radius: calc(var(--custom-button-card-border-radius) / 2); /* card - rounded corners */
aspect-ratio: 1/1; /* card - square */
}
figure {
margin: 0px !important; /* remove card margins to line up with rest of dashboard */
}
figcaption {
display: none; /* hide image caption */
}
img {
object-fit: cover !important; /* fill the whole card */
aspect-ratio: 1/1; /* needed for object-fit */
}
.btn {
top: 50% !important; /* center buttons */
}
.modal-content {
aspect-ratio: unset; /* undo image aspect-ratio when clicked */
}
- type: conditional
conditions:
- entity: select.conditional_media
state: TV
card:
type: custom:button-card
entity: media_player.tv_wohnzimmer_2
template: [conditional_media, progress_bar]
configuration.yaml
- platform: files
folder: /config/www/img
filter: '**/*.jpg'
name: gallery_images
sort: date
recursive: True
tv_media.yaml
template:
- select:
- name: conditional_media
state: >
{% set recently_added = 'gallery' %}
{% set recently_added_backup = 'Recently Added (Offline)' %}
{% set paused_timeout_minutes = 15 %}
{% set media_players = [
states.media_player.tv_wohnzimmer_2,
states.media_player.tv_schlafzimmer_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.buro,
states.media_player.maja,
states.media_player.kuche,
states.media_player.fernseher_maja_2 ] %}
{% 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 recently_added = recently_added_backup if is_state('sensor.recently_added_offline','Active') else recently_added %}
{% set playing = media_players | selectattr('state','eq','playing') | list %}
{% set timeout_playing = False if on | length == 0 else
(as_timestamp(now()) - as_timestamp(on | map(attribute='last_changed') | list | max)) < paused_timeout_minutes * 60 %}
{% set paused = media_players | selectattr('state','eq','paused') | list %}
{% set timeout_paused = False if paused | length == 0 else
(as_timestamp(now()) - as_timestamp(paused | map(attribute='last_changed') | list | max)) < paused_timeout_minutes * 60 %}
{% if playing %}
{{ media('playing') if timeout_playing else media('paused') if timeout_paused else media('playing') }}
{% elif paused %}
{{ media('paused') if timeout_paused else recently_added }}
{% else %}
{{ recently_added }}
{% endif %}
options: >
{% set recently_added = ['gallery'] %}
{% set recently_added_backup = ['Recently Added (Offline)'] %}
{% set media_players = [
states.media_player.tv_wohnzimmer_2,
states.media_player.vardagsrum,
states.media_player.lea,
states.media_player.tv_schlafzimmer_2,
states.sensor.ps5_101_activity,
states.media_player.wohnzimmer_oben,
states.media_player.schlafzimmer,
states.media_player.buro,
states.media_player.maja,
states.media_player.kuche,
states.media_player.fernseher_maja_2 ] %}
{{ recently_added + recently_added_backup + media_players | map(attribute='name') | list }}
select_option:
service: select.select_option
target:
entity_id: select.conditional_media
data:
option: >
{{ option }}
I hope that I don´t forget something
How do you get that green glow around the buttons?
Thanks
Its just some box-shadow styling, based on state, that I added to the button card template that I use for those particular button cards:
card:
- border-radius: var(--button-card-border-radius)
- border-width: 0
- -webkit-tap-highlight-color: rgba(0,0,0,0)
- transition: none
- --mdc-ripple-color: >
[[[
return variables.state_on
? 'rgb(0, 0, 0)'
: '#97989c';
]]]
- color: >
[[[
return variables.state_on
? '#4b5254'
: '#97989c';
]]]
- background-color: >
[[[
return variables.state_on
? 'rgba(255, 255, 255, 0.85)'
: 'rgba(115, 115, 115, 0.25)';
]]]
- box-shadow: >
[[[
return variables.state_on
? '0px 0px 10px 3px red'
: '0px 0px 10px 3px green';
]]]
This is great!
Unfortunately, I’m unable to make it work in several grids at the same time; only the first one is showing the values.
E.g., below an example of my dashboard where I have one grid per room and I’d like to have the temperature and humidity in each title. However, only the first one shows. Is this expected or is there any way to make it work like this?
The goal is to eliminate those 4 cards and avoid having to swipe whenever I need to turn on a light using the wallpanel
Hi,
I adapt the code template from VietNgoc to the following:
Font Size and Icon Size can adapt to your needs.
Now you can show on all grifs: Temperature, Humidity, Position of a thermostat valve (or your lux sensor) and motion.
For example:
tempsensor: sensor.buero2_temperature
humidsensor: sensor.buero_position
positionSensor: sensor.buero_position
motionSensor: binary_sensor.bewegungsmelder_treppe
fontSize: 0.8rem
motionIconSize: 15px
when there is no sensor defined then nothing will be shown. I hope that works.
change_grid_title:
state_display: >
[[[
function findFirstH1Above(element) {
let currentElement = element;
while (currentElement) {
if (currentElement.shadowRoot) {
const shadowH1 = currentElement.shadowRoot.querySelector('h1');
if (shadowH1) return shadowH1;
}
const lightH1 = currentElement.querySelector('h1');
if (lightH1) return lightH1;
currentElement = currentElement.getRootNode().host;
if (!currentElement) break;
}
return null;
}
let parentElement = this.getRootNode().host;
let headerTitle = findFirstH1Above(parentElement);
if (headerTitle && !headerTitle.dataset.processed) {
const tempSensor = parseFloat(states[variables.tempsensor]?.state);
const humidSensor = parseFloat(states[variables.humidsensor]?.state);
const positionSensor = parseFloat(states[variables.positionSensor]?.state);
const motionState = states[variables.motionSensor]?.state;
const motionIcon = motionState === 'on' ? 'mdi:motion-sensor' : 'mdi:motion-sensor-off';
const fontSize = variables.fontSize || '14px';
const motionIconSize = variables.motionIconSize || '20px';
let currentTitle = headerTitle.innerText;
headerTitle.style.display = 'flex';
headerTitle.style.width = '100%';
headerTitle.style.justifyContent = 'space-between';
headerTitle.style.alignItems = 'center';
headerTitle.style.paddingInline = 'inherit';
const formattedTemp = !isNaN(tempSensor) ?
(tempSensor % 1 === 0 ? tempSensor.toFixed(0) : tempSensor.toFixed(1)).replace('.', ',') + '°C' : '';
const formattedHumid = !isNaN(humidSensor) ? humidSensor.toFixed(0) + '%' : '';
const formattedPosition = !isNaN(positionSensor) ? positionSensor.toFixed(0) + '%' : '';
let sensorValues = [];
if (formattedTemp) sensorValues.push(formattedTemp);
if (formattedHumid) sensorValues.push(formattedHumid);
if (formattedPosition) sensorValues.push(formattedPosition);
const sensorDisplay = sensorValues.join(' / ');
const motionIconHTML = states[variables.motionSensor]
? `<ha-icon icon="${motionIcon}" style="margin-left: 5px; --mdc-icon-size: ${motionIconSize};"></ha-icon>`
: '';
headerTitle.innerHTML = `
<div>${currentTitle}</div>
<div style="font-size: ${fontSize}; display: flex; align-items: center;">
${sensorDisplay}
${motionIconHTML}
</div>
`;
headerTitle.dataset.processed = true;
}
]]]
best, Michael
Hi,
I have an issue with padding value of the grid header and I don´t find where I can change it without installing car-mod. For faster page visivility on older tablets I would like to use it without card-mod:
The green marked padding are should be smaller.
Thanks,
Michael
I think there is no other way to interfere with css without card-mod. Because this is a default lovelace grid card from HA, not custom
thanks for your answer. that’s a pity. Is there any chance to change the source code of lovelace or whereever these .card-header element comes from. Even under the risk that I need to do it again after any update.
Does anyone have an idea how to scale the sidebar relative to the right side? Now the sidebar is too big and that is at the expense of the rest of the display.
do you mind sharing the code for glow effect on your dishwasher button?
thanks.
Hi @arifroni I have it set for hover on button, but you can use it normally as box-shadow inset style…
If anyone is interested. I’m trying to edit the person button by adding a visualization of the charge of the person’s iPhone in the top right circle. So using the percentage charge as it’s done for the lights but without showing the percentage, so just a visual cue for each person.
I’ve tried many things but nothing seems to stick.
In the person template, I’ve added two variables:
variables:
circle_input: >
[[[
if (entity && entity.last_changed) {
const lastChanged = new Date(entity.last_changed);
const now = new Date();
const diff = Math.floor((now - lastChanged) / 1000); // difference in seconds
if (diff < 60) return `${diff}s`;
if (diff < 3600) return `${Math.floor(diff / 60)}m`;
if (diff < 86400) return `${Math.floor(diff / 3600)}h`;
return `${Math.floor(diff / 86400)}d`;
}
return '';
]]]
battery_level: >
[[[
return entity.attributes.battery_level || 0;
]]]
charging: >
[[[
return entity.attributes.charging || false;
]]]
Which are defined as attributes in my created combined.person entity.
And this is my circle template:
circle:
styles:
card:
- --c-stroke-color-on: '#b0b0b0'
- --c-stroke-color-off: none
- --c-fill-color-on: none
- --c-fill-color-off: rgba(255,255,255,0.04)
- --c-stroke-width: 1.5
- --c-stroke-width-dragging: 4
- --c-font-color: '#97989c'
- --c-font-size: 17px
- --c-unit-font-size: 10.5px
- --c-font-weight: 700
- --c-letter-spacing: -0.02rem
custom_fields:
circle:
- display: initial
- width: 90%
- margin: -3% 2% 0 0
- justify-self: end
- opacity: 1
custom_fields:
circle: >
[[[
if (entity) {
let r = 22.1,
c = r * 2 * Math.PI,
tspan = '<tspan dx=".2" dy="-.4">',
domain = entity.entity_id.split('.')[0],
state = variables.state_on,
input = variables.circle_input || ' ',
unit = variables.circle_input_unit || ' ';
/* * * * * * * * * * * * * * * * * *
* *
* CIRCLE *
* *
* * * * * * * * * * * * * * * * * */
let circle = (state, input, unit, batteryLevel, charging) => {
let color = batteryLevel > 20 ? '#00ff00' : (batteryLevel > 10 ? 'orange' : 'red');
let dashoffset = c - (batteryLevel / 100 * c);
return `
<svg viewBox="0 0 50 50">
<style>
circle {
transform: rotate(-90deg);
transform-origin: 50% 50%;
stroke-dasharray: ${c};
stroke-dashoffset: ${typeof input === 'number' && c - input / 100 * c};
stroke-width: var(--c-stroke-width);
stroke: ${state ? 'var(--c-stroke-color-on)' : 'var(--c-stroke-color-off)'};
fill: ${state ? 'var(--c-fill-color-on)' : 'var(--c-fill-color-off)'};
}
text {
font-size: var(--c-font-size);
font-weight: var(--c-font-weight);
letter-spacing: var(--c-letter-spacing);
fill: var(--c-font-color);
}
tspan {
font-size: var(--c-unit-font-size);
}
#circle_value, tspan {
text-anchor: middle;
dominant-baseline: central;
}
</style>
<circle id="circle_stroke" cx="25" cy="25" r="${r}" stroke="gray" stroke-opacity="50%" stroke-width="2" fill="none" />
${batteryLevel !== undefined ? `
<circle cx="25" cy="25" r="${r}" stroke="${color}" stroke-dashoffset="${dashoffset}" stroke-dasharray="${c}" stroke-linecap="round" stroke-width="2" fill="none" style="transform: rotate(-90deg); transform-origin: 50% 50%;" />
${charging ? `<circle cx="25" cy="25" r="${r}" stroke="blue" stroke-width="2" fill="none" style="opacity: 0.5;" />` : ''}
` : ''}
<text id="circle_value" x="50%" y="54%" font-size="var(--c-font-size)" text-anchor="middle" alignment-baseline="middle" dominant-baseline="middle">${input}${tspan}${unit}</tspan></text>
</svg>
${domain === 'light' && `
<input id="circle_slider" type="range" min="0" max="100" value="${input}">
`}
`;
}
/* * * * * * * * * * * * * * * * * *
* *
* LIGHT *
* *
* * * * * * * * * * * * * * * * * */
if (domain === 'light' && state) {
// wait 0ms for shadow dom
setTimeout(() => {
// then get elements
let elt = this.shadowRoot,
circle_slider = elt.getElementById('circle_slider'),
circle_value = elt.getElementById('circle_value'),
circle_stroke = elt.getElementById('circle_stroke');
// approximate position of thumb relative to circle
circle_slider.style.top = `${(circle_slider.value - 50) / 1.66 - 1}%`;
// debug position
let debug = false;
if (debug) circle_slider.style.opacity = 0.3;
// pass each event to handler
['click', 'input', 'mousedown', 'mouseup', 'touchstart', 'touchend'].forEach((event) => {
circle_slider.addEventListener(event, handler, { passive: true })
});
function handler(event) {
// "this" refers to slider
if (event.target === this) {
// bypass button-card tap_action
event.stopPropagation();
// update circle_value
circle_value.innerHTML = `${this.value}${tspan}${unit}</tspan>`;
// update stroke
circle_stroke.style.strokeDashoffset = c - this.value / 100 * c;
circle_stroke.style.strokeWidth = 'var(--c-stroke-width-dragging)';
// set cursor while dragging
if (event.type === 'mousedown' || event.type === 'input') {
this.style.cursor = 'grabbing';
} else {
this.style.cursor = 'grab';
}
// reset stroke width if value doesn't change
if (input == this.value && (event.type === 'click' || event.type === 'touchend'))
circle_stroke.style.strokeWidth = 'var(--c-stroke-width)';
// on release
if (event.type === 'mouseup' || event.type === 'touchend') {
// display loader if brightness is 0
if (circle_slider.value == 0 && elt.getElementById('loader')) {
elt.getElementById('loader').style.display = 'initial';
elt.getElementById('circle').style.display = 'none';
}
// set brightness
hass.callService('light', 'turn_on', {
entity_id: entity.entity_id,
brightness_pct: this.value
});
}
}
}
}, 0);
return circle(state, input, unit, batteryLevel, charging);
}
/* * * * * * * * * * * * * * * * * *
* *
* PERSON *
* *
* * * * * * * * * * * * * * * * * */
else if (domain === 'person') {
let time = c => {
let s = (c / 1e3),
m = (c / 6e4),
h = (c / 36e5),
d = (c / 864e5);
return s < 60
? parseInt(s) + 's'
: m < 60 ? parseInt(m) + 'm'
: h < 24 ? parseInt(h) + 'h'
: parseInt(d) + 'd';
};
let batteryLevel = state_attr(entity.entity_id, 'battery_level') || 0;
let charging = state_attr(entity.entity_id, 'charging') || false;
let input = states[variables.retain] === undefined || states[variables.retain].state === 'unavailable'
? time(Date.now() - Date.parse(entity.last_changed))
: time(Date.now() - Date.parse(states[variables.retain].state));
unit = ' ';
return circle(state, input, unit, batteryLevel, charging);
}
/* * * * * * * * * * * * * * * * * *
* *
* CLIMATE *
* *
* * * * * * * * * * * * * * * * * */
else if (domain === 'climate') {
return circle(state, input, unit, batteryLevel, charging);
}
/* * * * * * * * * * * * * * * * * *
* *
* PIHOLE *
* *
* * * * * * * * * * * * * * * * * */
else if (domain === 'pihole') {
return circle(state, input, unit, batteryLevel, charging);
}
/* * * * * * * * * * * * * * * * * *
* *
* KIDSHEATER *
* *
* * * * * * * * * * * * * * * * * */
else if (domain === 'kidsheater') {
return circle(state, input, unit, batteryLevel, charging);
}
/* * * * * * * * * * * * * * * * * *
* *
* AWAIR *
* *
* * * * * * * * * * * * * * * * * */
else if (domain === 'awair') {
return circle(state, input, unit, batteryLevel, charging);
}
/* * * * * * * * * * * * * * * * * *
* *
* OTHER *
* *
* * * * * * * * * * * * * * * * * */
else if (variables.state_on) {
return circle(state, input, unit, batteryLevel, charging);
}
}
]]]
I can’t figure out why it’s not working.
On a side note, too bad that this thread has slowed down. It was nice seeing new ideas pop up here and there.
@chezpaul2 All default settings from Matt … You’re probably overcomplicating…
card:
type: custom:button-card
entity: person.viet_ngoc
name: Viet Ngoc
template:
- circle
- person
variables:
circle_input: >
[[[
let battery = Math.round(states['sensor.viet_ngoc_battery_level'].state);
if (battery) return battery;
]]]
circle_input_unit: ' '
update: And if you want to get really fancy, create a new template with base and circle.
template:
battery_circle:
template:
- base
- circle
variables:
battery: ' '
circle_unit: ' '
state_display: >
[[[
if (entity) {
return variables.state === 'home'
? variables.translate_home
: variables.state === 'not_home'
? variables.translate_not_home
: variables.state;
}
return variables.translate_unknown;
]]]
triggers_update: sensor.time
custom_fields:
icon: >
[[[
return entity && variables.entity_picture
? `<img src="${variables.entity_picture}" width="100%">`
: null;
]]]
circle: >
[[[
let input = states[variables.battery].state,
radius = 20.5,
circumference = radius * 2 * Math.PI;
let unit = variables.circle_unit;
var color = "rgba(48, 128, 181, 0.8)";
if (input <= 20) {
color = "#FDD60F";
} else if (input <= 40) {
color = "rgba(48, 128, 181, 0.8)";
}
else {
color = "#27C950";
}
return `
<svg viewBox="0 0 50 50">
<style>
circle {
transform: rotate(-90deg);
transform-origin: 50% 50%;
stroke-dasharray: ${circumference};
stroke-dashoffset: ${circumference - input / 100 * circumference};
}
tspan {
font-size: 10px;
}
</style>
<circle cx="25" cy="25" r="${radius}" stroke="${color}" stroke-width="3" fill="none" stroke-linecap="round"/>
<text x="50%" y="54%" fill="#8d8e90" font-size="14" text-anchor="middle" alignment-baseline="middle" dominant-baseline="middle">${input}<tspan font-size="10">${unit}</tspan></text>
</svg>
`;
]]]
styles:
custom_fields:
icon:
- clip-path: circle()
- width: 82%
- pointer-events: none
- display: grid
- filter: >
[[[
return variables.state === 'not_home'
? `grayscale(1)`
: null;
]]]
button:
card:
type: custom:button-card
entity: person.viet_ngoc
name: Viet Ngoc
template:
- battery_circle
variables:
battery: sensor.viet_ngoc_battery_level
circle_unit: ' %'
or you can add any other item you like over another template, nearly everything is possible:
phone:
styles:
custom_fields:
battery_status:
- position: absolute
- right: -5%
- left: 58%
- top: 39%
- font-size: calc(var(--button-card-font-size) * 0.25 * var(--card-phone))
- transition: top 250ms ease-out
battery_charge:
- position: absolute
- right: -5%
- left: 58%
- top: 49%
- font-size: calc(var(--button-card-font-size) * 0.25 * var(--card-phone))
wifi_status:
- position: absolute
- right: -5%
- left: 58%
- top: 60%
- font-size: calc(var(--button-card-font-size) * 0.25 * var(--card-phone))
# proximity:
# - position: absolute
# - right: -5%
# - left: 58%
# - top: 67%
# - font-size: calc(var(--button-card-font-size) * 0.25 * var(--card-phone))
custom_fields:
battery_status: |
[[[
var isHome = states[variables.person].state === 'home';
var battery_level = states[variables.phone_level].state;
var battery_state = states[variables.phone_state].state;
var color = isHome ? (battery_level < 15 ? '#FF0000' : '#3182b7') : '#808080';
var blink = (battery_state === 'charging' && isHome) ? 'blink-charging 5s linear infinite' : 'none';
var battery_icon = 'mdi:battery';
if (battery_state === 'charging') {
battery_icon = 'mdi:battery-charging';
} else if (battery_level <= 10) battery_icon = 'mdi:battery-outline';
else if (battery_level <= 20) battery_icon = 'mdi:battery-20';
else if (battery_level <= 30) battery_icon = 'mdi:battery-30';
else if (battery_level <= 40) battery_icon = 'mdi:battery-40';
else if (battery_level <= 50) battery_icon = 'mdi:battery-50';
else if (battery_level <= 60) battery_icon = 'mdi:battery-60';
else if (battery_level <= 70) battery_icon = 'mdi:battery-70';
else if (battery_level <= 80) battery_icon = 'mdi:battery-80';
else if (battery_level <= 90) battery_icon = 'mdi:battery-90';
else if (battery_level >= 100) battery_icon = 'mdi:battery';
return `<ha-icon
icon="${battery_icon}"
style="width:25%; color: ${color}; animation: ${blink};"></ha-icon>
<span style="color: ${color};">${battery_level}%</span>`;
]]]
wifi_status: |
[[[
var isHome = states[variables.person].state === 'home';
var wifi_connected = states[variables.wifi].state === 'on';
var wifi_connection_name = wifi_connected ? states[variables.wifi_connection].state : '';
var wifi_color = isHome ? '#3182b7' : '#808080';
return wifi_connected ? `<ha-icon icon="mdi:wifi" style="width:25%; color: ${wifi_color};"></ha-icon><span style="margin-left: 5px; color: ${wifi_color};">${wifi_connection_name}</span>` : '';
]]]
battery_charge: |
[[[
var battery_charge = states[variables.battery_charge].state;
var battery_level = states[variables.phone_level].state;
var charge_icon = 'mdi:battery';
var charge_status = '';
var charge_color = '#3182b7'; // Standardfarbe (Hellblau)
if (battery_level == 100) {
charge_status = 'Voll';
charge_color = '#3182b7'; // Blau für 100% Akkustand
} else if (battery_charge === 'charging') {
charge_icon = 'mdi:battery-charging';
charge_status = 'Laden';
charge_color = '#27C950'; // Grün für Laden
} else if (battery_charge === 'discharging') {
charge_icon = 'mdi:battery';
charge_status = 'Entlädt';
charge_color = '#FFA500'; // Orange für Entladen
} else {
charge_status = battery_charge;
}
return `<ha-icon icon="${charge_icon}" style="width:25%; color: ${charge_color};"></ha-icon><span style="margin-left: 5px; color: ${charge_color};">${charge_status}</span>`;
]]]
# proximity: |
# [[[
# var isHome = states[variables.person].state === 'home';
# const distance = parseFloat(states[variables.proximity].state).toFixed(1);
# var color = isHome ? '#3182b7' : '#808080';
# return `<ha-icon
# icon="mdi:map-marker-distance"
# style="width:25%; color: ${color};">
# </ha-icon> <span style="color: ${color};">${distance} km</span>`
# ]]]
styles:
- "@keyframes blink-charging":
0%, 100% { opacity: 1; }
50% { opacity: 0; }
variables:
phone_level: sensor.sh_s41p_battery_level
# battery_status: sensor.sh_s41p_battery_level
wifi_connection: sensor.sh_s41p_wifi_connection