diff --git a/collections/batch6.md b/collections/batch6.md
index e69de29..8a3eb9a 100644
--- a/collections/batch6.md
+++ b/collections/batch6.md
@@ -0,0 +1,1429 @@
+
+[![Revolut.Me][revolut_me_shield]][revolut_me]
+[![PayPal.Me][paypal_me_shield]][paypal_me]
+[![ko_fi][ko_fi_shield]][ko_fi_me]
+[![buymecoffee][buy_me_coffee_shield]][buy_me_coffee_me]
+[![patreon][patreon_shield]][patreon_me]
+
+
+
+
+
+# Home Assistant [Batch 6](https://youtu.be/RZncjsCWjTw) Collection.
+
+Other collections? watch these YouTube videos for more cards and instructions:
+[Batch 1](https://youtu.be/5vYz37AqSO4) | [Batch 2](https://youtu.be/izx0JMrnhWE) | [Batch 3](https://youtu.be/SrFbC1ae35E) | [Batch 4](https://youtu.be/avAg9CR9TRc) | [Batch 5](https://youtu.be/5k6DfaymBZE)
+
+
+
+> [!NOTE]
+> If you are using the **Sections** view type, you may need to set `rows` to around `1.5` for the card,
+> otherwise the card may appear compressed.
+>
+> ```yaml
+> grid_options:
+> rows: 1.5
+> ```
+
+
+
+# Cards:
+
+
+
+
+
+Presence sensor card
+
+```yaml
+type: custom:button-card
+entity: binary_sensor.aqara_presence_fp300_presence
+name: Living Room
+show_state: false
+show_label: true
+show_icon: false
+variables:
+ distance: sensor.aqara_presence_fp300_target_distance
+ show_distance: true
+ range: number.aqara_presence_fp300_detection_range
+ show_range: true
+ max_supported_limit: 6
+ zone_steps: 24
+ track_button: button.aqara_presence_fp300_track_target_distance
+ show_track_button: true
+ temp: sensor.aqara_presence_fp300_temperature
+ humidity: sensor.aqara_presence_fp300_humidity
+ lux: sensor.aqara_presence_fp300_illuminance
+ show_environment: true
+ temp_low: 18
+ temp_mid: 23
+ temp_high: 27
+ hum_low: 30
+ hum_high: 60
+ lux_min: 50
+ lux_max: 400
+ size_shape: 75px
+ size_card_height: 125px
+ font_primary: 15px
+ font_secondary: 12px
+ font_badge: 10px
+styles:
+ card:
+ - --config-shape-size: '[[[ return variables.size_shape ]]]'
+ - --config-card-height: >
+ [[[ return variables.show_range !== false ? variables.size_card_height :
+ '99px'; ]]]
+ - --config-font-primary: '[[[ return variables.font_primary ]]]'
+ - --config-font-secondary: '[[[ return variables.font_secondary ]]]'
+ - --config-font-badge: '[[[ return variables.font_badge ]]]'
+ - height: var(--config-card-height) !important
+ - padding: 0px !important
+ - overflow: hidden
+ - position: relative
+ grid:
+ - padding: 12px 16px
+ - height: 100%
+ - box-sizing: border-box
+ - grid-template-areas: '"radar n" "radar l"'
+ - grid-template-columns: var(--config-shape-size) 1fr
+ - grid-template-rows: auto auto
+ - align-content: start
+ - gap: 0px 14px
+ - position: relative
+ - z-index: 2
+ name:
+ - justify-self: start
+ - font-size: var(--config-font-primary)
+ - font-weight: 500
+ - align-self: end
+ - margin-bottom: 2px
+ - position: relative
+ - color: var(--primary-text-color)
+ label:
+ - justify-self: start
+ - font-size: var(--config-font-secondary)
+ - opacity: 0.7
+ - align-self: start
+ - margin-top: 2px
+ - position: relative
+ - color: var(--secondary-text-color)
+ custom_fields:
+ radar:
+ - grid-area: radar
+ - justify-self: start
+ - width: var(--config-shape-size)
+ - height: var(--config-shape-size)
+ - border-radius: 50%
+ - border: 1px solid rgba(128, 128, 128, 0.15)
+ - background: rgba(128, 128, 128, 0.05)
+ - position: relative
+ - overflow: hidden
+ - box-shadow: inset 0 2px 8px rgba(0,0,0,0.1)
+ badge1:
+ - position: absolute
+ - top: 12px
+ - right: |
+ [[[
+ let btn_valid = variables.show_track_button !== false && variables.track_button && states[variables.track_button] && states[variables.track_button].state !== 'unknown' && states[variables.track_button].state !== 'unavailable';
+ return btn_valid ? '46px' : '12px';
+ ]]]
+ - transition: right 0.3s ease-out
+ - padding: 4px 10px
+ - font-size: var(--config-font-badge)
+ - letter-spacing: 0.5px
+ - white-space: nowrap
+ - font-weight: 600
+ - text-transform: uppercase
+ - z-index: 5
+ badge2:
+ - position: absolute
+ - top: 45px
+ - right: 12px
+ - padding: 4px 10px
+ - font-size: var(--config-font-badge)
+ - letter-spacing: 0.5px
+ - white-space: nowrap
+ - opacity: 0.9
+ - font-weight: 500
+ - z-index: 5
+ track_button:
+ - position: absolute
+ - top: 11px
+ - right: 12px
+ - z-index: 10
+ - display: |
+ [[[
+ let btn_valid = variables.show_track_button !== false && variables.track_button && states[variables.track_button] && states[variables.track_button].state !== 'unknown' && states[variables.track_button].state !== 'unavailable';
+ return btn_valid ? 'block' : 'none';
+ ]]]
+ zone_map:
+ - position: absolute
+ - bottom: 0
+ - left: 0
+ - width: 100%
+ - height: 42px
+ - z-index: 1
+tap_action:
+ action: more-info
+label: |
+ [[[
+ if (!entity) return 'Entity Setup Required';
+
+ let range_valid = variables.show_range !== false && variables.range && states[variables.range] && states[variables.range].state !== 'unknown' && states[variables.range].state !== 'unavailable';
+ if (!range_valid) return '\u00A0';
+
+ let max_l = parseFloat(variables.max_supported_limit) || 6.0;
+ let bm = parseInt(states[variables.range].state);
+ let highest_zone = -1;
+ if (!isNaN(bm)) {
+ for(let i=23; i>=0; i--) { if((bm & (1<= 0 ? (highest_zone + 1) * 0.25 : max_l;
+ return `Zone Limit: ${limit.toFixed(1)}m`;
+ ]]]
+custom_fields:
+ badge1: ' '
+ badge2: ' '
+ track_button:
+ card:
+ type: custom:button-card
+ entity: '[[[ return variables.track_button ]]]'
+ icon: mdi:crosshairs-gps
+ show_name: false
+ show_state: false
+ styles:
+ card:
+ - width: 26px
+ - height: 26px
+ - border-radius: 50%
+ - background: rgba(128, 128, 128, 0.1)
+ - border: 1px solid rgba(128, 128, 128, 0.2)
+ - box-shadow: 0 2px 4px rgba(0,0,0,0.1)
+ - display: flex
+ icon:
+ - width: 16px
+ - color: var(--primary-text-color)
+ tap_action:
+ action: call-service
+ service: button.press
+ service_data:
+ entity_id: '[[[ return variables.track_button ]]]'
+ radar: |
+ [[[
+ let is_on = entity && entity.state === 'on';
+
+ let dist_valid = variables.show_distance !== false && variables.distance && states[variables.distance] && states[variables.distance].state !== 'unknown' && states[variables.distance].state !== 'unavailable';
+ let safe_dist = dist_valid ? parseFloat(states[variables.distance].state) : 0;
+
+ let range_valid = variables.show_range !== false && variables.range && states[variables.range] && states[variables.range].state !== 'unknown' && states[variables.range].state !== 'unavailable';
+ let bm = range_valid ? parseInt(states[variables.range].state) : 0;
+
+ let max_r = parseFloat(variables.max_supported_limit) || 6.0;
+ let highest_zone = -1;
+ if (range_valid && !isNaN(bm)) {
+ for(let i=23; i>=0; i--) { if((bm & (1<= 0 ? (highest_zone + 1) * 0.25 : max_r) : max_r;
+
+ if (safe_dist > max_r) safe_dist = max_r;
+
+ let raw_zone = Math.floor(safe_dist / 0.25);
+ let is_zone_valid = range_valid ? ((bm & (1 << raw_zone)) !== 0) : true;
+
+ let max_svg = 47.5;
+ let range_pct = (range_val / max_r) * max_svg;
+ let dist_pct = dist_valid ? ((safe_dist / max_r) * max_svg) : 0;
+
+ let c_rgb = is_on ? '0, 229, 255' : '158, 158, 158';
+ let t_rgb = is_zone_valid ? c_rgb : '255, 82, 82';
+ let stroke_color = 'rgba(128,128,128,0.2)';
+
+ return `
+
+
+ ${is_on ? `
+
+ ` : ''}
+ `;
+ ]]]
+ zone_map: |
+ [[[
+ let range_valid = variables.show_range !== false && variables.range && states[variables.range] && states[variables.range].state !== 'unknown' && states[variables.range].state !== 'unavailable';
+ if (!range_valid) return '';
+
+ let is_on = entity && entity.state === 'on';
+
+ let dist_valid = variables.show_distance !== false && variables.distance && states[variables.distance] && states[variables.distance].state !== 'unknown' && states[variables.distance].state !== 'unavailable';
+ let safe_dist = dist_valid ? parseFloat(states[variables.distance].state) : 0;
+
+ let bm = parseInt(states[variables.range].state);
+ if(isNaN(bm)) bm = 0;
+
+ let max_r = parseFloat(variables.max_supported_limit) || 6.0;
+ let visual_steps = parseInt(variables.zone_steps) || 12;
+
+ safe_dist = Math.max(0, Math.min(safe_dist, max_r - 0.001));
+ let step_size = max_r / visual_steps;
+ let tracked_visual_step = Math.floor(safe_dist / step_size);
+
+ let total_raw_zones = Math.ceil(max_r / 0.25);
+ let raw_per_step = total_raw_zones / visual_steps;
+ let visual_array = Array(visual_steps).fill(false);
+
+ for(let i=0; i= 0 && v_idx < visual_steps) visual_array[v_idx] = true;
+ }
+ }
+
+ let zone_bars = ``;
+
+ for(let i=0; i
`;
+ }
+ zone_bars += ``;
+
+ let l1 = (max_r * 0.25).toFixed(1) + 'm';
+ let l2 = (max_r * 0.50).toFixed(1) + 'm';
+ let l3 = (max_r * 0.75).toFixed(1) + 'm';
+ let l4 = max_r.toFixed(0) + 'm';
+
+ let labels = `
+ 0m${l1}${l2}${l3}${l4}
+
`;
+
+ return `${labels}${zone_bars}
`;
+ ]]]
+extra_styles: |
+ [[[
+ let is_on = entity && entity.state === 'on';
+
+ let dist_valid = variables.show_distance !== false && variables.distance && states[variables.distance] && states[variables.distance].state !== 'unknown' && states[variables.distance].state !== 'unavailable' && !isNaN(parseFloat(states[variables.distance].state));
+ let safe_dist = dist_valid ? parseFloat(states[variables.distance].state) : 0;
+ if (safe_dist > variables.max_supported_limit) safe_dist = variables.max_supported_limit;
+
+ let range_valid = variables.show_range !== false && variables.range && states[variables.range] && states[variables.range].state !== 'unknown' && states[variables.range].state !== 'unavailable' && !isNaN(parseInt(states[variables.range].state));
+ let bm = range_valid ? parseInt(states[variables.range].state) : 0;
+
+ let raw_zone = Math.floor(safe_dist / 0.25);
+ let is_zone_valid = range_valid ? ((bm & (1 << raw_zone)) !== 0) : true;
+
+ let status_text = 'CLEAR';
+ if (!entity) {
+ status_text = 'SETUP REQ';
+ } else if (is_on) {
+ if (dist_valid) {
+ status_text = `PRESENCE • ${safe_dist.toFixed(1)}m`;
+ } else {
+ status_text = 'PRESENCE';
+ }
+ }
+
+ let color = is_on ? (is_zone_valid ? '0, 229, 255' : '255, 82, 82') : '158, 158, 158';
+
+ let env_parts = [];
+ let c_temp = '128, 128, 128';
+ let c_hum = '128, 128, 128';
+ let c_lux = '128, 128, 128';
+
+ if (variables.show_environment !== false && variables.temp && states[variables.temp]) {
+ let st = states[variables.temp].state;
+ if (st !== 'unknown' && st !== 'unavailable') {
+ let v = parseFloat(st);
+ if (!isNaN(v)) {
+ env_parts.push(`${v.toFixed(1)}°C`);
+ if (v < variables.temp_low) c_temp = '66, 165, 245';
+ else if (v < variables.temp_mid) c_temp = '255, 235, 59';
+ else if (v < variables.temp_high) c_temp = '255, 167, 38';
+ else c_temp = '239, 83, 80';
+ }
+ }
+ }
+
+ if (variables.show_environment !== false && variables.humidity && states[variables.humidity]) {
+ let st = states[variables.humidity].state;
+ if (st !== 'unknown' && st !== 'unavailable') {
+ let v = parseFloat(st);
+ if (!isNaN(v)) {
+ env_parts.push(`${v.toFixed(0)}%`);
+ if (v < variables.hum_low) c_hum = '255, 167, 38';
+ else if (v <= variables.hum_high) c_hum = '66, 165, 245';
+ else c_hum = '255, 167, 38';
+ }
+ }
+ }
+
+ if (variables.show_environment !== false && variables.lux && states[variables.lux]) {
+ let st = states[variables.lux].state;
+ if (st !== 'unknown' && st !== 'unavailable') {
+ let v = parseFloat(st);
+ if (!isNaN(v)) {
+ env_parts.push(`${v.toFixed(0)}lx`);
+ c_lux = v < variables.lux_min ? '120, 144, 156' : (v > variables.lux_max ? '255, 235, 59' : '212, 225, 87');
+ }
+ }
+ }
+
+ let b2_text = env_parts.join(' • ');
+ let display_b2 = env_parts.length > 0 ? 'block' : 'none';
+ let bg_gradient = `linear-gradient(90deg, rgba(${c_temp}, 0.15) 0%, rgba(${c_hum}, 0.15) 50%, rgba(${c_lux}, 0.2) 100%)`;
+
+ return `
+ #card {
+ --appliance-color: ${color};
+ }
+
+ #badge1 {
+ display: block;
+ background: rgba(var(--appliance-color), 0.07);
+ color: var(--primary-text-color);
+ border-top: 1px solid var(--divider-color, rgba(128,128,128,0.2));
+ border-bottom: 1px solid var(--divider-color, rgba(128,128,128,0.2));
+ border-left: 2.5px solid rgb(var(--appliance-color));
+ border-radius: 6px !important;
+ }
+ #badge1::before { content: "${status_text}"; }
+
+ #badge2 {
+ display: ${display_b2};
+ background: ${bg_gradient};
+ color: var(--primary-text-color);
+ border-top: 1px solid var(--divider-color, rgba(128,128,128,0.2));
+ border-bottom: 1px solid var(--divider-color, rgba(128,128,128,0.2));
+ border-left: 2.5px solid rgb(${c_temp});
+ border-right: 2.5px solid rgb(${c_lux});
+ border-radius: 6px !important;
+ }
+ #badge2::before { content: "${b2_text}"; }
+
+ @keyframes radar-spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
+ `;
+ ]]]
+
+```
+
+
+
+
+
+
+
+Smoke detector card
+
+```yaml
+type: custom:button-card
+entity: binary_sensor.aqara_smoke_detector_smoke
+name: Smoke Detector
+show_state: false
+show_label: true
+variables:
+ sensor_density: sensor.aqara_smoke_detector_smoke_density
+ sensor_battery: sensor.aqara_smoke_detector_battery
+ show_bar: false
+ theme_idle_colored: false
+ heartbeat_color: '#4caf50'
+ heartbeat_interval: 3s
+ batt_red: 20
+ batt_orange: 40
+ batt_yellow: 70
+ dens_yellow: 0.1
+ dens_orange: 0.3
+ dens_red: 0.5
+ size_icon: 45px
+ size_shape: 65px
+ size_card_height: 95px
+ font_primary: 15px
+ font_secondary: 12px
+ font_badge: 10px
+styles:
+ card:
+ - --config-icon-size: '[[[ return variables.size_icon ]]]'
+ - --config-shape-size: '[[[ return variables.size_shape ]]]'
+ - --config-card-height: '[[[ return variables.size_card_height ]]]'
+ - --config-font-primary: '[[[ return variables.font_primary ]]]'
+ - --config-font-secondary: '[[[ return variables.font_secondary ]]]'
+ - --config-font-badge: '[[[ return variables.font_badge ]]]'
+ - height: var(--config-card-height) !important
+ - padding: 0px !important
+ - overflow: hidden
+ - position: relative
+ - transition: background 0.5s ease
+ grid:
+ - padding: 12px 16px
+ - height: 100%
+ - box-sizing: border-box
+ - grid-template-areas: '"i n" "i l"'
+ - grid-template-columns: var(--config-shape-size) 1fr
+ - grid-template-rows: auto auto
+ - align-content: center
+ - gap: 0px 12px
+ - position: relative
+ - z-index: 2
+ icon:
+ - width: var(--config-icon-size)
+ - height: var(--config-icon-size)
+ - color: var(--primary-text-color)
+ - z-index: 3
+ - animation: var(--anim-icon-shake)
+ - transform-origin: center center
+ - will-change: transform
+ img_cell:
+ - width: var(--config-shape-size)
+ - height: var(--config-shape-size)
+ - border-radius: 50%
+ - border: 1px solid var(--divider-color, rgba(128,128,128,0.2)) !important
+ - background: rgba(var(--appliance-color), 0.1) !important
+ - position: relative
+ - overflow: visible !important
+ - justify-self: start
+ - z-index: 2
+ name:
+ - justify-self: start
+ - font-size: var(--config-font-primary)
+ - font-weight: 500
+ - align-self: end
+ - margin-bottom: 2px
+ - position: relative
+ - z-index: 3
+ label:
+ - justify-self: start
+ - font-size: var(--config-font-secondary)
+ - opacity: 0.7
+ - align-self: start
+ - margin-top: 2px
+ - position: relative
+ - z-index: 3
+ custom_fields:
+ bg_smoke:
+ - position: absolute
+ - inset: 0
+ - z-index: 0
+ - pointer-events: none
+ - display: var(--display-smoke)
+ heartbeat:
+ - position: absolute
+ - top: 10px
+ - left: 10px
+ - width: 6px
+ - height: 6px
+ - border-radius: 50%
+ - background: var(--hb-color)
+ - box-shadow: 0 0 6px var(--hb-color)
+ - display: var(--display-heartbeat)
+ - animation: hardware-blink var(--hb-interval) infinite
+ - z-index: 5
+ badge1:
+ - position: absolute
+ - top: 10px
+ - right: 10px
+ - background: rgba(var(--appliance-color), 0.15)
+ - color: var(--primary-text-color)
+ - border-top: 1px solid rgba(128,128,128, 0.2)
+ - border-bottom: 1px solid rgba(128,128,128, 0.2)
+ - border-left: 2.5px solid rgb(var(--appliance-color))
+ - padding: 2px 10px
+ - border-radius: 6px
+ - font-size: var(--config-font-badge)
+ - font-weight: 600
+ - text-transform: uppercase
+ - letter-spacing: 0.5px
+ - white-space: nowrap
+ - z-index: 5
+ badge2:
+ - position: absolute
+ - top: 38px
+ - right: 10px
+ - color: var(--primary-text-color)
+ - border-top: 1px solid rgba(128,128,128, 0.2)
+ - border-bottom: 1px solid rgba(128,128,128, 0.2)
+ - padding: 4px 8px
+ - font-size: 10px
+ - letter-spacing: 0.5px
+ - white-space: nowrap
+ - opacity: 0.9
+ - text-transform: uppercase
+ - font-weight: 500
+ - border-radius: 6px
+ - display: var(--display-badge2)
+ - z-index: 5
+ bar:
+ - position: absolute
+ - bottom: 0
+ - left: 0
+ - height: 3.5px
+ - width: var(--appliance-level)
+ - background: rgb(var(--appliance-color))
+ - box-shadow: 0 0 8px rgb(var(--appliance-color))
+ - transition: width 0.5s ease
+ - display: var(--display-bar)
+ - z-index: 5
+tap_action:
+ action: more-info
+label: |
+ [[[
+ let ent_smoke = entity ? entity.entity_id : null;
+ if (!ent_smoke || !states[ent_smoke]) return 'Entity Setup Required';
+ return states[ent_smoke].state === 'on' ? 'Smoke Detected' : 'Monitoring';
+ ]]]
+icon: mdi:smoke-detector
+custom_fields:
+ badge1: ' '
+ badge2: ' '
+ bar: ' '
+ heartbeat: ' '
+ bg_smoke: |
+ [[[
+ return `
+
+
+
+
+ `;
+ ]]]
+extra_styles: |
+ [[[
+ let ent_smoke = entity ? entity.entity_id : null;
+ let ent_bat = variables.sensor_battery;
+ let ent_den = variables.sensor_density;
+
+ let is_smoke = ent_smoke && states[ent_smoke] && states[ent_smoke].state === 'on';
+ let raw_bat = states[ent_bat] ? parseFloat(states[ent_bat].state) : NaN;
+ let raw_den = states[ent_den] ? parseFloat(states[ent_den].state) : NaN;
+
+ let theme_colored = variables.theme_idle_colored !== undefined ? variables.theme_idle_colored : false;
+ let status = 'CLEAR';
+ let color = theme_colored ? '0, 229, 255' : '158, 158, 158';
+
+ let anim_shake = 'none';
+ let anim_strobe = 'none';
+ let display_smoke = 'none';
+ let display_heartbeat = 'block';
+
+ if (is_smoke) {
+ status = 'SMOKE ALARM';
+ color = '244, 67, 54';
+ anim_shake = 'buzzer-shake 0.1s linear infinite';
+ anim_strobe = 'strobe-fade 0.8s ease-in-out infinite';
+ display_smoke = 'block';
+ display_heartbeat = 'none';
+ }
+
+ let progress = isNaN(raw_bat) ? 100 : Math.max(0, Math.min(100, raw_bat));
+ let display_bar = (variables.show_bar !== false && !isNaN(raw_bat)) ? 'block' : 'none';
+
+ let c_bat = '76, 175, 80';
+ if (!isNaN(raw_bat)) {
+ if (raw_bat <= (variables.batt_red || 20)) { c_bat = '244, 67, 54'; }
+ else if (raw_bat <= (variables.batt_orange || 40)) { c_bat = '255, 152, 0'; }
+ else if (raw_bat <= (variables.batt_yellow || 70)) { c_bat = '255, 235, 59'; }
+ }
+
+ let c_den = '76, 175, 80';
+ if (!isNaN(raw_den)) {
+ if (raw_den >= (variables.dens_red || 0.5)) { c_den = '244, 67, 54'; }
+ else if (raw_den >= (variables.dens_orange || 0.3)) { c_den = '255, 152, 0'; }
+ else if (raw_den >= (variables.dens_yellow || 0.1)) { c_den = '255, 235, 59'; }
+ }
+
+ let b2_arr = [];
+ let b2_text = '';
+ if (!isNaN(raw_bat)) b2_arr.push(`Bat ${Math.round(raw_bat)}%`);
+ if (!isNaN(raw_den)) b2_arr.push(`Den ${raw_den}`);
+
+ let display_badge2 = b2_arr.length > 0 ? 'block' : 'none';
+ if (b2_arr.length > 0) {
+ b2_text = b2_arr.join(' • ');
+ }
+
+ let bg_badge2 = 'rgba(128,128,128,0.05)';
+ let b2_left = '1px solid rgba(128,128,128,0.2)';
+ let b2_right = '1px solid rgba(128,128,128,0.2)';
+
+ if (!isNaN(raw_bat) && !isNaN(raw_den)) {
+ bg_badge2 = `linear-gradient(90deg, rgba(${c_bat}, 0.15) 30%, rgba(${c_den}, 0.2) 75%)`;
+ b2_left = `2.5px solid rgb(${c_bat})`;
+ b2_right = `2.5px solid rgb(${c_den})`;
+ } else if (!isNaN(raw_bat)) {
+ bg_badge2 = `rgba(${c_bat}, 0.15)`;
+ b2_left = `2.5px solid rgb(${c_bat})`;
+ } else if (!isNaN(raw_den)) {
+ bg_badge2 = `rgba(${c_den}, 0.15)`;
+ b2_right = `2.5px solid rgb(${c_den})`;
+ }
+
+ return `
+ #card {
+ --appliance-color: ${color};
+ --appliance-level: ${progress}%;
+ --anim-icon-shake: ${anim_shake};
+ --anim-cell-strobe: ${anim_strobe};
+ --display-smoke: ${display_smoke};
+ --display-bar: ${display_bar};
+ --display-heartbeat: ${display_heartbeat};
+ --display-badge2: ${display_badge2};
+ --hb-color: ${variables.heartbeat_color || '#4caf50'};
+ --hb-interval: ${variables.heartbeat_interval || '5s'};
+ }
+
+ #badge1::before { content: "${status}"; }
+
+ #badge2 {
+ background: ${bg_badge2};
+ border-left: ${b2_left};
+ border-right: ${b2_right};
+ }
+
+ #badge2::before { content: "${b2_text}"; }
+
+ #img-cell::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ border-radius: 50%;
+ background-color: rgba(var(--appliance-color), 0.5);
+ box-shadow: 0 0 20px rgba(var(--appliance-color), 0.6);
+ opacity: 0;
+ animation: var(--anim-cell-strobe);
+ pointer-events: none;
+ will-change: opacity;
+ }
+
+ .smoke-plume {
+ position: absolute;
+ bottom: -40px;
+ background: radial-gradient(circle at center, color-mix(in srgb, var(--primary-text-color) 40%, transparent) 0%, transparent 65%);
+ border-radius: 50%;
+ opacity: 0;
+ will-change: transform, opacity;
+ }
+
+ .plume-1 { animation: drift-1 4s ease-in infinite; }
+ .plume-2 { animation: drift-2 4.5s ease-in infinite; }
+ .plume-3 { animation: drift-3 5s ease-in infinite; }
+ .plume-4 { animation: drift-1 5.5s ease-in infinite; }
+
+ @keyframes buzzer-shake {
+ 0%, 100% { transform: translate3d(0, 0, 0); }
+ 25% { transform: translate3d(-1px, 0.5px, 0) rotate(-1.5deg); }
+ 75% { transform: translate3d(1px, -0.5px, 0) rotate(1.5deg); }
+ }
+
+ @keyframes strobe-fade {
+ 0%, 100% { opacity: 0; }
+ 50% { opacity: 1; }
+ }
+
+ @keyframes hardware-blink {
+ 0%, 8%, 100% { opacity: 0; }
+ 4% { opacity: 1; }
+ }
+
+ @keyframes drift-1 {
+ 0% { transform: translate3d(0, 10px, 0) scale(0.5); opacity: 0; }
+ 30% { opacity: 0.7; }
+ 100% { transform: translate3d(-15px, -110px, 0) scale(2.8); opacity: 0; }
+ }
+
+ @keyframes drift-2 {
+ 0% { transform: translate3d(0, 10px, 0) scale(0.5); opacity: 0; }
+ 30% { opacity: 0.6; }
+ 100% { transform: translate3d(20px, -100px, 0) scale(3.2); opacity: 0; }
+ }
+
+ @keyframes drift-3 {
+ 0% { transform: translate3d(0, 10px, 0) scale(0.5); opacity: 0; }
+ 30% { opacity: 0.8; }
+ 100% { transform: translate3d(-5px, -120px, 0) scale(3.0); opacity: 0; }
+ }
+ `;
+ ]]]
+
+```
+
+
+
+
+
+
+
+Liquid card (Water)
+
+```yaml
+type: custom:button-card
+entity: sensor.water_tank_level
+name: Water Tank
+show_icon: true
+show_name: true
+show_state: true
+icon: mdi:water
+variables:
+ card_h: 150
+ circle_size: 100
+styles:
+ card:
+ - height: '[[[ return variables.card_h + ''px'' ]]]'
+ - position: relative
+ - overflow: hidden
+ - background: transparent
+ - box-shadow: var(--ha-card-box-shadow, none)
+ - border-radius: var(--ha-card-border-radius, 12px)
+ - border: |
+ [[[
+ let level = entity ? parseFloat(entity.state) || 0 : 0;
+ let rgb = level <= 20 ? "150, 29, 29" : "29, 130, 150";
+ return `1px solid rgba(${rgb}, 0.3)`;
+ ]]]
+ grid:
+ - grid-template-areas: '"i" "s" "n"'
+ - grid-template-columns: 1fr
+ - grid-template-rows: min-content min-content min-content
+ - height: 100%
+ - align-content: center
+ - justify-content: center
+ - gap: '[[[ return (variables.circle_size * 0.02) + ''px'' ]]]'
+ icon:
+ - color: white
+ - width: '[[[ return (variables.circle_size * 0.35) + ''px'' ]]]'
+ - justify-self: center
+ - filter: drop-shadow(0px 1px 3px rgba(0,0,0,0.8))
+ - position: relative
+ - z-index: 3
+ state:
+ - color: white
+ - font-size: '[[[ return (variables.circle_size * 0.15) + ''px'' ]]]'
+ - font-weight: bold
+ - justify-self: center
+ - text-shadow: 0px 1px 3px rgba(0,0,0,0.8)
+ - position: relative
+ - z-index: 3
+ name:
+ - color: white
+ - font-size: '[[[ return (variables.circle_size * 0.10) + ''px'' ]]]'
+ - justify-self: center
+ - text-shadow: 0px 1px 3px rgba(0,0,0,0.8)
+ - position: relative
+ - z-index: 3
+ custom_fields:
+ glass_circle:
+ - position: absolute
+ - top: 50%
+ - left: 50%
+ - transform: translate(-50%, -50%)
+ - width: '[[[ return variables.circle_size + ''px'' ]]]'
+ - height: '[[[ return variables.circle_size + ''px'' ]]]'
+ - border-radius: 50%
+ - background: rgba(0, 0, 0, 0.25)
+ - backdrop-filter: blur(6px)
+ - -webkit-backdrop-filter: blur(6px)
+ - border: 1px solid rgba(255, 255, 255, 0.15)
+ - box-shadow: 0 4px 15px rgba(0,0,0,0.3)
+ - z-index: 2
+ back_wave:
+ - position: absolute
+ - inset: 0
+ - z-index: 1
+ - pointer-events: none
+ - display: |-
+ [[[
+ let level = entity ? parseFloat(entity.state) || 0 : 0;
+ return level <= 0 ? "none" : "block";
+ ]]]
+ front_wave:
+ - position: absolute
+ - inset: 0
+ - z-index: 1
+ - pointer-events: none
+ - display: |-
+ [[[
+ let level = entity ? parseFloat(entity.state) || 0 : 0;
+ return level <= 0 ? "none" : "block";
+ ]]]
+custom_fields:
+ glass_circle: |
+ [[[ return ``; ]]]
+ back_wave: |
+ [[[
+ let level = entity ? parseFloat(entity.state) || 0 : 0;
+ let rgb = level <= 20 ? "150, 29, 29" : "29, 130, 150";
+ let color = `rgba(${rgb}, 0.6)`;
+
+ /* Draw SVG path */
+ let path = "M0 7.5 Q30 0 60 7.5 T120 7.5 L120 15 L0 15 Z";
+ let svg = ``;
+
+ /* Repeat the SVG 10 times */
+ let wave_row = svg.repeat(10);
+
+ return `
+
+ `;
+ ]]]
+ front_wave: |
+ [[[
+ let level = entity ? parseFloat(entity.state) || 0 : 0;
+ let rgb = level <= 20 ? "150, 29, 29" : "29, 130, 150";
+ let color = `rgba(${rgb}, 1)`;
+
+ /* Draw SVG path */
+ let path = "M0 7.5 Q30 0 60 7.5 T120 7.5 L120 15 L0 15 Z";
+ let svg = ``;
+
+ /* Repeat the SVG 10 times */
+ let wave_row = svg.repeat(10);
+
+ return `
+
+ `;
+ ]]]
+extra_styles: |
+ @keyframes scroll-left {
+ 0% { transform: translateX(0); }
+ 100% { transform: translateX(-120px); }
+ }
+ @keyframes scroll-right {
+ 0% { transform: translateX(-120px); }
+ 100% { transform: translateX(0); }
+ }
+
+```
+
+
+
+Liquid card (Fuel)
+
+```yaml
+type: custom:button-card
+entity: sensor.fuel_tank_level
+name: Fuel Tank
+show_icon: true
+show_name: true
+show_state: true
+icon: mdi:barrel
+variables:
+ card_h: 150
+ circle_size: 100
+styles:
+ card:
+ - height: '[[[ return variables.card_h + ''px'' ]]]'
+ - position: relative
+ - overflow: hidden
+ - background: transparent
+ - box-shadow: var(--ha-card-box-shadow, none)
+ - border-radius: var(--ha-card-border-radius, 12px)
+ - border: |
+ [[[
+ let level = entity ? parseFloat(entity.state) || 0 : 0;
+ let rgb = level <= 20 ? "211, 47, 47" : "245, 124, 0";
+ return `1px solid rgba(${rgb}, 0.3)`;
+ ]]]
+ grid:
+ - grid-template-areas: '"i" "s" "n"'
+ - grid-template-columns: 1fr
+ - grid-template-rows: min-content min-content min-content
+ - height: 100%
+ - align-content: center
+ - justify-content: center
+ - gap: '[[[ return (variables.circle_size * 0.02) + ''px'' ]]]'
+ icon:
+ - color: white
+ - width: '[[[ return (variables.circle_size * 0.35) + ''px'' ]]]'
+ - justify-self: center
+ - filter: drop-shadow(0px 1px 3px rgba(0,0,0,0.8))
+ - position: relative
+ - z-index: 3
+ state:
+ - color: white
+ - font-size: '[[[ return (variables.circle_size * 0.15) + ''px'' ]]]'
+ - font-weight: bold
+ - justify-self: center
+ - text-shadow: 0px 1px 3px rgba(0,0,0,0.8)
+ - position: relative
+ - z-index: 3
+ name:
+ - color: white
+ - font-size: '[[[ return (variables.circle_size * 0.10) + ''px'' ]]]'
+ - justify-self: center
+ - text-shadow: 0px 1px 3px rgba(0,0,0,0.8)
+ - position: relative
+ - z-index: 3
+ custom_fields:
+ glass_circle:
+ - position: absolute
+ - top: 50%
+ - left: 50%
+ - transform: translate(-50%, -50%)
+ - width: '[[[ return variables.circle_size + ''px'' ]]]'
+ - height: '[[[ return variables.circle_size + ''px'' ]]]'
+ - border-radius: 50%
+ - background: rgba(0, 0, 0, 0.25)
+ - backdrop-filter: blur(6px)
+ - -webkit-backdrop-filter: blur(6px)
+ - border: 1px solid rgba(255, 255, 255, 0.15)
+ - box-shadow: 0 4px 15px rgba(0,0,0,0.3)
+ - z-index: 2
+ back_wave:
+ - position: absolute
+ - inset: 0
+ - z-index: 1
+ - pointer-events: none
+ - display: >-
+ [[[ return ((entity ? parseFloat(entity.state) || 0 : 0) <= 0) ?
+ "none" : "block"; ]]]
+ front_wave:
+ - position: absolute
+ - inset: 0
+ - z-index: 1
+ - pointer-events: none
+ - display: >-
+ [[[ return ((entity ? parseFloat(entity.state) || 0 : 0) <= 0) ?
+ "none" : "block"; ]]]
+custom_fields:
+ glass_circle: |
+ [[[ return ``; ]]]
+ back_wave: |
+ [[[
+ let level = entity ? parseFloat(entity.state) || 0 : 0;
+ let rgb = level <= 20 ? "211, 47, 47" : "245, 124, 0";
+ let color = `rgba(${rgb}, 0.6)`;
+
+ /* Draw SVG path */
+ let path = "M0 7.5 Q30 0 60 7.5 T120 7.5 L120 15 L0 15 Z";
+ let svg = ``;
+
+ /* Repeat the SVG 10 times */
+ let wave_row = svg.repeat(10);
+
+ return `
+
+ `;
+ ]]]
+ front_wave: |
+ [[[
+ let level = entity ? parseFloat(entity.state) || 0 : 0;
+ let rgb = level <= 20 ? "211, 47, 47" : "245, 124, 0";
+ let color = `rgba(${rgb}, 1)`;
+
+ /* Draw SVG path */
+ let path = "M0 7.5 Q30 0 60 7.5 T120 7.5 L120 15 L0 15 Z";
+ let svg = ``;
+
+ /* Repeat the SVG 10 times */
+ let wave_row = svg.repeat(10);
+
+ return `
+
+ `;
+ ]]]
+extra_styles: |
+ @keyframes scroll-left {
+ 0% { transform: translateX(0); }
+ 100% { transform: translateX(-120px); }
+ }
+ @keyframes scroll-right {
+ 0% { transform: translateX(-120px); }
+ 100% { transform: translateX(0); }
+ }
+
+```
+
+
+
+Liquid card (Battery/percentage)
+
+```yaml
+type: custom:button-card
+entity: sensor.anasbox_battery_level
+name: Battery
+show_icon: true
+show_name: true
+show_state: true
+variables:
+ card_h: 150
+ circle_size: 110
+ threshold_red: 20
+ threshold_orange: 50
+ threshold_green: 100
+ charger_entity: binary_sensor.anasbox_is_charging
+icon: |
+ [[[
+ if (entity && entity.attributes && entity.attributes.icon) return entity.attributes.icon;
+ let level = entity && entity.state && !isNaN(entity.state) ? parseFloat(entity.state) : 0;
+ let charger = variables.charger_entity && states[variables.charger_entity] ? states[variables.charger_entity] : null;
+ let is_charging = charger ? ['on', 'charging', 'plugged'].includes(charger.state.toLowerCase()) : false;
+ let rounded = Math.round(level / 10) * 10;
+ if (is_charging) {
+ if (rounded === 0) return "mdi:battery-charging-outline";
+ return `mdi:battery-charging-${rounded}`;
+ }
+ if (rounded === 100) return "mdi:battery";
+ if (rounded === 0) return "mdi:battery-outline";
+ return `mdi:battery-${rounded}`;
+ ]]]
+styles:
+ card:
+ - height: '[[[ return variables.card_h + ''px'' ]]]'
+ - position: relative
+ - overflow: hidden
+ - background: transparent
+ - box-shadow: var(--ha-card-box-shadow, none)
+ - border-radius: var(--ha-card-border-radius, 12px)
+ - border: |
+ [[[
+ let level = entity && entity.state && !isNaN(entity.state) ? parseFloat(entity.state) : 0;
+ let is_charging = variables.charger_entity && states[variables.charger_entity] ? ['on', 'charging', 'plugged'].includes(states[variables.charger_entity].state.toLowerCase()) : false;
+ let rgb = "0, 255, 100";
+ if (is_charging) rgb = "0, 255, 255";
+ else if (level <= variables.threshold_red) rgb = "244, 67, 54";
+ else if (level <= variables.threshold_orange) rgb = "255, 152, 0";
+ return `1px solid rgba(${rgb}, 0.3)`;
+ ]]]
+ grid:
+ - grid-template-areas: '"i" "s" "n"'
+ - grid-template-columns: 1fr
+ - grid-template-rows: min-content min-content min-content
+ - height: 100%
+ - align-content: center
+ - justify-content: center
+ - gap: '[[[ return (variables.circle_size * 0.02) + ''px'' ]]]'
+ icon:
+ - color: white
+ - width: '[[[ return (variables.circle_size * 0.35) + ''px'' ]]]'
+ - justify-self: center
+ - filter: drop-shadow(0px 1px 3px rgba(0,0,0,0.8))
+ - position: relative
+ - z-index: 3
+ state:
+ - color: white
+ - font-size: '[[[ return (variables.circle_size * 0.15) + ''px'' ]]]'
+ - font-weight: bold
+ - justify-self: center
+ - text-shadow: 0px 1px 3px rgba(0,0,0,0.8)
+ - position: relative
+ - z-index: 3
+ name:
+ - color: white
+ - font-size: '[[[ return (variables.circle_size * 0.10) + ''px'' ]]]'
+ - justify-self: center
+ - text-shadow: 0px 1px 3px rgba(0,0,0,0.8)
+ - position: relative
+ - z-index: 3
+ custom_fields:
+ glass_circle:
+ - position: absolute
+ - top: 50%
+ - left: 50%
+ - transform: translate(-50%, -50%)
+ - width: '[[[ return variables.circle_size + ''px'' ]]]'
+ - height: '[[[ return variables.circle_size + ''px'' ]]]'
+ - border-radius: 50%
+ - background: rgba(0, 0, 0, 0.25)
+ - backdrop-filter: blur(6px)
+ - -webkit-backdrop-filter: blur(6px)
+ - border: 1px solid rgba(255, 255, 255, 0.15)
+ - box-shadow: 0 4px 15px rgba(0,0,0,0.3)
+ - z-index: 2
+ back_wave:
+ - position: absolute
+ - inset: 0
+ - z-index: 1
+ - pointer-events: none
+ front_wave:
+ - position: absolute
+ - inset: 0
+ - z-index: 1
+ - pointer-events: none
+custom_fields:
+ glass_circle: |
+ [[[ return ``; ]]]
+ back_wave: |
+ [[[
+ let level = entity && entity.state && !isNaN(entity.state) ? parseFloat(entity.state) : 0;
+ let display_level = Math.max(level, 5);
+ let is_charging = variables.charger_entity && states[variables.charger_entity] ? ['on', 'charging', 'plugged'].includes(states[variables.charger_entity].state.toLowerCase()) : false;
+ let rgb = "46, 204, 113";
+ if (is_charging) rgb = "52, 152, 219";
+ else if (level <= variables.threshold_red) rgb = "231, 76, 60";
+ else if (level <= variables.threshold_orange) rgb = "243, 156, 18";
+ let color = `rgba(${rgb}, 0.6)`;
+ let speed = is_charging ? "2s" : "4s";
+ let path = "M0 7.5 Q30 0 60 7.5 T120 7.5 L120 15 L0 15 Z";
+ let svg = ``;
+ let wave_row = svg.repeat(10);
+ return `
+
+ `;
+ ]]]
+ front_wave: |
+ [[[
+ let level = entity && entity.state && !isNaN(entity.state) ? parseFloat(entity.state) : 0;
+ let display_level = Math.max(level, 5);
+ let is_charging = variables.charger_entity && states[variables.charger_entity] ? ['on', 'charging', 'plugged'].includes(states[variables.charger_entity].state.toLowerCase()) : false;
+ let rgb = "46, 204, 113";
+ if (is_charging) rgb = "52, 152, 219";
+ else if (level <= variables.threshold_red) rgb = "231, 76, 60";
+ else if (level <= variables.threshold_orange) rgb = "243, 156, 18";
+ let color = `rgba(${rgb}, 1)`;
+ let speed = is_charging ? "1.5s" : "3s";
+ let path = "M0 7.5 Q30 0 60 7.5 T120 7.5 L120 15 L0 15 Z";
+ let svg = ``;
+ let wave_row = svg.repeat(10);
+ return `
+
+ `;
+ ]]]
+extra_styles: |
+ @keyframes scroll-left {
+ 0% { transform: translateX(0); }
+ 100% { transform: translateX(-120px); }
+ }
+ @keyframes scroll-right {
+ 0% { transform: translateX(-120px); }
+ 100% { transform: translateX(0); }
+ }
+
+```
+
+
+
+
+
+
+
+Mushroom light card with color temps quick toggles
+
+
+
+>* after pasting this card, use the Find & Replace ALL feature in the card editor (CTRL+F) and Replace `light.CHANGE_ME` with your light entity, to change it everywhere at once.
+
+```yaml
+type: custom:vertical-stack-in-card
+cards:
+ - type: custom:mushroom-light-card
+ entity: light.CHANGE_ME
+ name: Light Card Name
+ icon: mdi:led-strip-variant
+ show_brightness_control: true
+ show_color_control: true
+ show_color_temp_control: true
+ use_light_color: true
+ card_mod:
+ style:
+ mushroom-light-brightness-control$:
+ mushroom-slider$: |
+ .slider {
+ background: rgba(255, 255, 255, 0.05) !important;
+ }
+ .slider-track-active {
+ box-shadow: inset 0 2px 4px rgba(0,0,0,0.15) !important;
+ }
+ .: |
+ ha-card {
+ background: none !important;
+ border: none !important;
+ box-shadow: none !important;
+
+ padding-top: 10px !important;
+ padding-bottom: 10px !important;
+
+ --control-height: 38px;
+ --control-border-radius: 19px;
+ }
+ mushroom-state-info {
+ max-width: calc(100% - 140px) !important;
+ }
+ - type: custom:mushroom-chips-card
+ alignment: end
+ card_mod:
+ style: |
+ ha-card {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ background: none !important;
+ border: none !important;
+ box-shadow: none !important;
+
+ width: max-content;
+ pointer-events: none;
+ z-index: 10;
+
+ --chip-background: rgba(255, 255, 255, 0.05);
+ --chip-box-shadow: none;
+ --chip-border-width: 0px;
+ --chip-spacing: 6px;
+ --chip-padding: 0 10px;
+ --chip-height: 38px;
+ --chip-icon-size: 20px;
+ }
+ mushroom-template-chip {
+ pointer-events: auto;
+ }
+ chips:
+ - type: template
+ entity: light.CHANGE_ME
+ icon: mdi:weather-sunset
+ icon_color: >-
+ {% set temp = state_attr(entity, 'color_temp_kelvin') %}
+
+ {% set mode = state_attr(entity, 'color_mode') %}
+
+ {{ 'orange' if is_state(entity, 'on') and mode == 'color_temp' and
+ temp != None and temp <= 3200 else 'disabled' }}
+ tap_action:
+ action: call-service
+ service: light.turn_on
+ target:
+ entity_id: light.CHANGE_ME
+ data:
+ color_temp_kelvin: 2702
+ - type: template
+ entity: light.CHANGE_ME
+ icon: mdi:white-balance-sunny
+ icon_color: >-
+ {% set temp = state_attr(entity, 'color_temp_kelvin') %}
+
+ {% set mode = state_attr(entity, 'color_mode') %}
+
+ {{ 'amber' if is_state(entity, 'on') and mode == 'color_temp' and temp
+ != None and 3200 < temp < 5000 else 'disabled' }}
+ tap_action:
+ action: call-service
+ service: light.turn_on
+ target:
+ entity_id: light.CHANGE_ME
+ data:
+ color_temp_kelvin: 4000
+ - type: template
+ entity: light.CHANGE_ME
+ icon: mdi:snowflake
+ icon_color: >-
+ {% set temp = state_attr(entity, 'color_temp_kelvin') %}
+
+ {% set mode = state_attr(entity, 'color_mode') %}
+
+ {{ 'light-blue' if is_state(entity, 'on') and mode == 'color_temp' and
+ temp != None and temp >= 5000 else 'disabled' }}
+ tap_action:
+ action: call-service
+ service: light.turn_on
+ target:
+ entity_id: light.CHANGE_ME
+ data:
+ color_temp_kelvin: 6535
+card_mod:
+ style: |
+ ha-card {
+ position: relative;
+ }
+
+```
+
+
+---
+
+[paypal_me_shield]: https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white
+
+[paypal_me]: https://paypal.me/anasboxsupport
+
+[revolut_me_shield]:
+https://img.shields.io/badge/revolut-FFFFFF?style=for-the-badge&logo=revolut&logoColor=black
+
+[revolut_me]: https://revolut.me/anas4e
+
+[ko_fi_shield]: https://img.shields.io/badge/Ko--fi-F16061?style=for-the-badge&logo=ko-fi&logoColor=white
+
+[ko_fi_me]: https://ko-fi.com/anasbox
+
+[buy_me_coffee_shield]:
+https://img.shields.io/badge/Buy%20Me%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black
+
+[buy_me_coffee_me]: https://www.buymeacoffee.com/anasbox
+
+[patreon_shield]:
+https://img.shields.io/badge/patreon-404040?style=for-the-badge&logo=patreon&logoColor=white
+
+[patreon_me]: https://patreon.com/AnasBox