diff --git a/appliances_v2.md b/appliances_v2.md
index 7bb4501..188d115 100644
--- a/appliances_v2.md
+++ b/appliances_v2.md
@@ -24,19 +24,19 @@
# Home Assistant Animated Appliances Cards V2
>* Dishwasher
->
>* Washing Machine
->
>* Dryer
->
>* Combo Washer & Dryer
->
>* Fridge
+>* Oven
This [YouTube Video](https://youtu.be/3Njo1-jht5w) explains how to do it.
+
+
+
`Loading image... please wait`
@@ -1757,6 +1757,405 @@ extra_styles: |
```
+
+6 - Smart Oven
+
+```yaml
+type: custom:button-card
+entity: sensor.smart_oven_status
+name: Smart Oven
+show_state: false
+show_label: true
+variables:
+ sensor_status: sensor.smart_oven_status
+ sensor_program: sensor.smart_oven_current_program
+ sensor_time_remaining: sensor.smart_oven_time_remaining
+ sensor_current_temp: sensor.smart_oven_current_temperature
+ sensor_target_temp: sensor.smart_oven_target_temperature
+ sensor_power: sensor.smart_oven_power
+ sensor_door: binary_sensor.smart_oven_door
+ progress_mode: temp
+ max_time: 120
+ state_idle: idle, off, standby, unknown, unavailable
+ state_heating: preheat, preheating, heating
+ state_baking: bake, baking, roast, roasting, cooking, running, on, convection
+ state_cooling: cooling, cool
+ state_done: finished, complete, end, done, ready
+ size_icon: 45px
+ size_shape: 65px
+ size_card_height: 95px
+ font_primary: 15px
+ font_secondary: 12px
+ font_badge: 11px
+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: all 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
+ icon:
+ - width: var(--config-icon-size)
+ - height: var(--config-icon-size)
+ - color: var(--primary-text-color)
+ - z-index: 5
+ - filter: drop-shadow(0 2px 2px rgba(0,0,0,0.5))
+ img_cell:
+ - width: var(--config-shape-size)
+ - height: var(--config-shape-size)
+ - border-radius: 50%
+ - border: 1px solid rgba(128, 128, 128, 0.2) !important
+ - position: relative
+ - overflow: hidden !important
+ - justify-self: start
+ name:
+ - justify-self: start
+ - font-size: var(--config-font-primary)
+ - font-weight: 500
+ - align-self: end
+ - margin-bottom: 2px
+ - position: relative
+ label:
+ - justify-self: start
+ - font-size: var(--config-font-secondary)
+ - opacity: 0.7
+ - align-self: start
+ - margin-top: 2px
+ - position: relative
+ custom_fields:
+ badge_prog:
+ - position: absolute
+ - right: 10px
+ - padding: 2px 10px
+ - border-radius: 12px
+ - font-size: var(--config-font-badge)
+ - font-weight: 600
+ - text-transform: uppercase
+ - letter-spacing: 0.5px
+ - white-space: nowrap
+ - max-width: 150px
+ - overflow: hidden
+ - text-overflow: ellipsis
+ - z-index: 1
+ - transition: all 0.4s ease
+ badge_group:
+ - position: absolute
+ - right: 10px
+ - display: flex
+ - gap: 6px
+ - flex-direction: row
+ - z-index: 5
+ - transition: all 0.4s ease
+ bar:
+ - position: absolute
+ - bottom: 0
+ - left: 0
+ - height: 3.5px
+ - width: 100%
+ - background: var(--appliance-bar-bg)
+ - clip-path: inset(0 calc(100% - var(--appliance-level)) 0 0)
+ - transition: clip-path 0.5s ease, background 0.5s ease
+tap_action:
+ action: more-info
+label: |
+ [[[
+ if (!entity) return 'Entity Setup Required';
+ let status = String(entity.state);
+ let _s = status.trim().toLowerCase();
+
+ let state_idle = (variables.state_idle || '').split(',').map(s => s.trim().toLowerCase());
+ let state_heating = (variables.state_heating || '').split(',').map(s => s.trim().toLowerCase());
+ let state_baking = (variables.state_baking || '').split(',').map(s => s.trim().toLowerCase());
+ let state_cooling = (variables.state_cooling || '').split(',').map(s => s.trim().toLowerCase());
+ let state_done = (variables.state_done || '').split(',').map(s => s.trim().toLowerCase());
+ if (/^\d+$/.test(_s)) {
+ if (state_idle.includes(_s)) status = state_idle.find(s => !/^\d+$/.test(s)) || status;
+ else if (state_heating.includes(_s)) status = state_heating.find(s => !/^\d+$/.test(s)) || status;
+ else if (state_baking.includes(_s)) status = state_baking.find(s => !/^\d+$/.test(s)) || status;
+ else if (state_cooling.includes(_s)) status = state_cooling.find(s => !/^\d+$/.test(s)) || status;
+ else if (state_done.includes(_s)) status = state_done.find(s => !/^\d+$/.test(s)) || status;
+ }
+
+ return status.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+ ]]]
+icon: mdi:stove
+custom_fields:
+ badge_prog: ' '
+ badge_group: ' '
+ bar: ' '
+extra_styles: |
+ [[[
+ let ent_status = variables.sensor_status;
+ let ent_program = variables.sensor_program;
+ let ent_timerem = variables.sensor_time_remaining;
+ let ent_currtemp = variables.sensor_current_temp;
+ let ent_targtemp = variables.sensor_target_temp;
+ let ent_power = variables.sensor_power;
+ let ent_door = variables.sensor_door;
+ let max_time = variables.max_time;
+ let p_mode = (variables.progress_mode || 'temp').toLowerCase();
+
+ let state_idle = (variables.state_idle || '').split(',').map(s => s.trim().toLowerCase());
+ let state_heating = (variables.state_heating || '').split(',').map(s => s.trim().toLowerCase());
+ let state_baking = (variables.state_baking || '').split(',').map(s => s.trim().toLowerCase());
+ let state_cooling = (variables.state_cooling || '').split(',').map(s => s.trim().toLowerCase());
+ let state_done = (variables.state_done || '').split(',').map(s => s.trim().toLowerCase());
+
+ let status = states[ent_status] ? String(states[ent_status].state) : 'unknown';
+ let _s = status.trim().toLowerCase();
+
+ if (/^\d+$/.test(_s)) {
+ if (state_idle.includes(_s)) status = state_idle.find(s => !/^\d+$/.test(s)) || status;
+ else if (state_heating.includes(_s)) status = state_heating.find(s => !/^\d+$/.test(s)) || status;
+ else if (state_baking.includes(_s)) status = state_baking.find(s => !/^\d+$/.test(s)) || status;
+ else if (state_cooling.includes(_s)) status = state_cooling.find(s => !/^\d+$/.test(s)) || status;
+ else if (state_done.includes(_s)) status = state_done.find(s => !/^\d+$/.test(s)) || status;
+ }
+
+ let s_lower = status.toLowerCase();
+
+ let is_idle = state_idle.includes(s_lower);
+ let is_heating = state_heating.includes(s_lower);
+ let is_baking = state_baking.includes(s_lower);
+ let is_cooling = state_cooling.includes(s_lower);
+ let is_done = state_done.includes(s_lower);
+
+ let program = states[ent_program] ? String(states[ent_program].state).trim() : '';
+ let program_clean = (program && program.toLowerCase() !== 'unknown' && program.toLowerCase() !== 'none' && !/^\d+$/.test(program))
+ ? program.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase()) : '';
+
+ let curr_temp = states[ent_currtemp] ? parseFloat(states[ent_currtemp].state) : NaN;
+ let targ_temp = states[ent_targtemp] ? parseFloat(states[ent_targtemp].state) : NaN;
+ let uom = states[ent_currtemp] && states[ent_currtemp].attributes ? states[ent_currtemp].attributes.unit_of_measurement : '°';
+ let is_f = String(uom).toLowerCase().includes('f');
+
+ let has_power = ent_power && states[ent_power] && !['unknown', 'unavailable'].includes(states[ent_power].state.toLowerCase());
+ let pwr = has_power ? Math.round(parseFloat(states[ent_power].state)) : 0;
+
+ let raw_val = states[ent_timerem] ? states[ent_timerem].state.trim() : '0';
+ let time_uom = states[ent_timerem] && states[ent_timerem].attributes ? states[ent_timerem].attributes.unit_of_measurement : '';
+ let time_rem = 0;
+
+ if (raw_val.includes('-') && raw_val.includes(':')) {
+ let end_ts = new Date(raw_val);
+ let now = new Date();
+ time_rem = end_ts > now ? Math.floor((end_ts - now) / 60000) : 0;
+ } else if (raw_val.includes(':')) {
+ let parts = raw_val.split(':');
+ if (parts.length === 3) time_rem = (parseInt(parts[0]) * 60) + parseInt(parts[1]);
+ else if (parts.length === 2) time_rem = (parseInt(parts[0]) * 60) + parseInt(parts[1]);
+ } else {
+ let parsed_val = parseFloat(raw_val) || 0;
+ let uom_lower = time_uom ? time_uom.toLowerCase() : '';
+
+ if (uom_lower === 'h' || uom_lower === 'hours') {
+ time_rem = Math.floor(parsed_val * 60);
+ } else if (uom_lower === 'm' || uom_lower === 'min' || uom_lower === 'minutes') {
+ time_rem = Math.floor(parsed_val);
+ } else {
+ if (parsed_val > 0 && parsed_val <= 10 && raw_val.includes('.')) {
+ time_rem = Math.floor(parsed_val * 60);
+ } else {
+ time_rem = Math.floor(parsed_val);
+ }
+ }
+ }
+ time_rem = Math.max(0, time_rem);
+
+ let hours = Math.floor(time_rem / 60);
+ let mins = time_rem % 60;
+ let time_formatted = (time_rem > 0 && !is_idle && !is_done) ? `${hours}h ${mins.toString().padStart(2, '0')}m` : '';
+
+ let color = '158, 158, 158';
+ let flame_op = '0'; let flame_speed = '0s'; let icon_bg = 'rgba(128, 128, 128, 0.1)';
+
+ if (is_heating) { color = '255, 87, 34'; flame_op = '1'; flame_speed = '0.1s'; icon_bg = '#221111'; }
+ else if (is_baking) { color = '244, 67, 54'; flame_op = '0.8'; flame_speed = '0.2s'; icon_bg = '#221111'; }
+ else if (is_cooling) { color = '33, 150, 243'; icon_bg = 'rgba(33, 150, 243, 0.1)'; }
+ else if (is_done) { color = '76, 175, 80'; icon_bg = 'rgba(76, 175, 80, 0.1)'; }
+
+ let progress = 0;
+ let bar_bg = `rgb(${color})`;
+
+ if (is_idle || is_done) {
+ progress = 0;
+ } else {
+ if (p_mode === 'time') {
+ let max_t = Math.max(parseFloat(max_time), time_rem);
+ if (max_t > 0) {
+ progress = Math.floor(((max_t - time_rem) / max_t) * 100);
+ }
+ } else {
+ if (!isNaN(targ_temp) && targ_temp > 0 && !isNaN(curr_temp)) {
+ progress = Math.floor((curr_temp / targ_temp) * 100);
+ bar_bg = "linear-gradient(90deg, #FFEB3B 0%, #FFEB3B 15%, #FF9800 35%, #FF9800 50%, #E65100 70%, #E65100 80%, #F44336 100%)";
+ } else if (Math.max(parseFloat(max_time), time_rem) > 0) {
+ let max_t = Math.max(parseFloat(max_time), time_rem);
+ progress = Math.floor(((max_t - time_rem) / max_t) * 100);
+ bar_bg = `rgb(${color})`;
+ }
+ }
+ progress = Math.max(2, Math.min(100, progress));
+ }
+
+ let door_state = (ent_door && states[ent_door]) ? states[ent_door].state.toLowerCase() : null;
+ let corner_color = '128, 128, 128'; let corner_display = 'none';
+ if (ent_door && door_state && door_state !== 'unknown' && door_state !== 'unavailable') {
+ corner_display = 'block';
+ if (door_state === 'on' || door_state === 'open') corner_color = '244, 67, 54';
+ }
+
+ let status_clean = status.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+ let badge_prog = program_clean || status_clean;
+ if (has_power) {
+ badge_prog += ` • ${pwr}W`;
+ }
+
+ let badge_time = time_formatted;
+ let badge_temp = '';
+
+ let is_dual_temp = false;
+ if (!isNaN(curr_temp) && curr_temp > 0) {
+ badge_temp = curr_temp + uom;
+ if ((is_heating || is_baking) && !isNaN(targ_temp) && targ_temp > 0) {
+ badge_temp += ' ➔ ' + targ_temp + uom;
+ is_dual_temp = true;
+ }
+ }
+
+ let offset_y = 10;
+ let pos_prog = -1; let pos_group = -1;
+
+ if (badge_prog) {
+ pos_prog = offset_y;
+ offset_y += 28;
+ }
+ if (badge_time || badge_temp) {
+ pos_group = offset_y;
+ }
+
+ let temp_color = '158, 158, 158';
+ if (!isNaN(curr_temp) && curr_temp > 0) {
+ let warm_threshold = is_f ? 120 : 50;
+ let hot_threshold = is_f ? 300 : 150;
+ if (curr_temp >= hot_threshold) temp_color = '244, 67, 54';
+ else if (curr_temp >= warm_threshold) temp_color = '255, 152, 0';
+ }
+
+ let targ_color = color;
+
+ let b_border = `1px solid rgba(128,128,128, 0.2)`;
+ let b_br_time = `2px solid rgb(${color})`;
+
+ let b_bg_temp = `rgba(${temp_color}, 0.15)`;
+ let b_bl_temp = b_border;
+ let b_br_temp = `2px solid rgb(${temp_color})`;
+
+ if (is_dual_temp) {
+ b_bg_temp = `linear-gradient(90deg, rgba(${temp_color}, 0.15) 0%, rgba(${targ_color}, 0.15) 100%)`;
+ b_bl_temp = `2px solid rgb(${temp_color})`;
+ b_br_temp = `2px solid rgb(${targ_color})`;
+ }
+
+ return `
+ #card {
+ --appliance-color: ${color};
+ --appliance-level: ${progress}%;
+ --appliance-bar-bg: ${bar_bg};
+ --appliance-bg: ${icon_bg};
+ --oven-flame-op: ${flame_op};
+ --oven-flame-speed: ${flame_speed};
+ --door-corner-color: rgb(${corner_color});
+ --door-corner-display: ${corner_display};
+ }
+
+ #card::after {
+ content: ''; display: var(--door-corner-display); position: absolute;
+ top: -0.5px; left: -0.5px; opacity: 0.9; width: 15px; height: 15px;
+ border-top: 5px solid var(--door-corner-color); border-left: 5px solid var(--door-corner-color);
+ border-top-left-radius: var(--ha-card-border-radius, 12px); pointer-events: none; transition: border-color 0.3s ease;
+ }
+
+ #img-cell { background: var(--appliance-bg) !important; transition: background 0.5s ease; }
+
+ #img-cell::before {
+ content: ""; position: absolute; left: 50%; bottom: -60%; width: 80%; height: 80%;
+ background: radial-gradient(circle at center, #ffff00 0%, #ff9800 100%);
+ transform: translateX(-50%) rotate(45deg); box-shadow: 0 0 20px #ff5722;
+ border-radius: 20px 0 20px 0; z-index: 1; opacity: var(--oven-flame-op);
+ animation: flame-burn var(--oven-flame-speed) infinite alternate;
+ display: ${flame_op === '0' ? 'none' : 'block'};
+ }
+
+ #img-cell::after {
+ content: ""; position: absolute; left: 45%; bottom: -65%; width: 90%; height: 90%;
+ background: rgba(255, 87, 34, 0.6); transform: translateX(-50%) rotate(45deg);
+ border-radius: 30px 0 30px 0; z-index: 0; opacity: var(--oven-flame-op);
+ animation: flame-burn-two 0.3s infinite alternate-reverse;
+ display: ${flame_op === '0' ? 'none' : 'block'};
+ }
+
+ #badge_prog {
+ top: ${pos_prog}px; display: ${pos_prog > -1 ? 'block' : 'none'};
+ background: rgba(${color}, 0.15); color: rgb(var(--appliance-color));
+ border: 1px solid rgba(var(--appliance-color), 0.3);
+ }
+ #badge_prog::before { content: "${badge_prog}"; }
+
+ #badge_group {
+ top: ${pos_group}px; display: ${pos_group > -1 ? 'flex' : 'none'};
+ }
+
+ #badge_group::before {
+ content: "${badge_time}"; display: ${badge_time ? 'block' : 'none'};
+ padding: 4px 8px; font-size: 10px; letter-spacing: 0.5px; white-space: nowrap;
+ opacity: 0.9; text-transform: uppercase; font-weight: 500;
+ background: rgba(${color}, 0.15); color: var(--primary-text-color, #fff);
+ border-top: ${b_border}; border-bottom: ${b_border};
+ border-left: ${b_border}; border-right: ${b_br_time};
+ border-radius: 6px !important;
+ }
+
+ #badge_group::after {
+ content: "${badge_temp}"; display: ${badge_temp ? 'block' : 'none'};
+ padding: 4px 8px; font-size: 10px; letter-spacing: 0.5px; white-space: nowrap;
+ opacity: 0.9; text-transform: uppercase; font-weight: 500;
+ background: ${b_bg_temp}; color: var(--primary-text-color, #fff);
+ border-top: ${b_border}; border-bottom: ${b_border};
+ border-left: ${b_bl_temp}; border-right: ${b_br_temp};
+ border-radius: 6px !important;
+ }
+
+ @keyframes flame-burn {
+ 0% { transform: translateX(-50%) rotate(45deg) scale(1.0); }
+ 100% { transform: translateX(-50%) rotate(45deg) scale(0.95); }
+ }
+ @keyframes flame-burn-two {
+ 0% { transform: translateX(-50%) rotate(45deg) scale(0.9); }
+ 100% { transform: translateX(-50%) rotate(45deg) scale(1.0); }
+ }
+ `;
+ ]]]
+
+```
+
+
---
# Cards (Dumb)
@@ -3464,6 +3863,367 @@ extra_styles: |
---
+
+6 - Oven (smart plug)
+
+```yaml
+type: custom:button-card
+entity: binary_sensor.oven_active_delay
+name: Dumb Oven
+show_state: false
+show_label: true
+variables:
+ sensor_helper: binary_sensor.oven_active_delay
+ sensor_plug: switch.aqara_plug_h2
+ sensor_power: sensor.aqara_plug_h2_power
+ sensor_temp: sensor.oven_current_temperature
+ sensor_door: binary_sensor.oven_door
+ thresh_heat: 1500
+ target_temp: 250
+ size_icon: 45px
+ size_shape: 65px
+ size_card_height: 95px
+ font_primary: 15px
+ font_secondary: 12px
+ font_badge: 11px
+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: all 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
+ icon:
+ - width: var(--config-icon-size)
+ - height: var(--config-icon-size)
+ - color: var(--primary-text-color)
+ - z-index: 5
+ - filter: drop-shadow(0 2px 2px rgba(0,0,0,0.5))
+ img_cell:
+ - width: var(--config-shape-size)
+ - height: var(--config-shape-size)
+ - border-radius: 50%
+ - border: 1px solid rgba(128, 128, 128, 0.2) !important
+ - position: relative
+ - overflow: hidden !important
+ - justify-self: start
+ name:
+ - justify-self: start
+ - font-size: var(--config-font-primary)
+ - font-weight: 500
+ - align-self: end
+ - margin-bottom: 2px
+ - position: relative
+ label:
+ - justify-self: start
+ - font-size: var(--config-font-secondary)
+ - opacity: 0.7
+ - align-self: start
+ - margin-top: 2px
+ - position: relative
+ custom_fields:
+ badge_prog:
+ - position: absolute
+ - right: 10px
+ - padding: 2px 10px
+ - border-radius: 12px
+ - font-size: var(--config-font-badge)
+ - font-weight: 600
+ - text-transform: uppercase
+ - letter-spacing: 0.5px
+ - white-space: nowrap
+ - max-width: 150px
+ - overflow: hidden
+ - text-overflow: ellipsis
+ - z-index: 1
+ - transition: all 0.4s ease
+ badge_group:
+ - position: absolute
+ - right: 10px
+ - display: flex
+ - gap: 6px
+ - flex-direction: row
+ - z-index: 5
+ - transition: all 0.4s ease
+ bar:
+ - position: absolute
+ - bottom: 0
+ - left: 0
+ - height: 3.5px
+ - width: 100%
+ - background: >-
+ linear-gradient(90deg, #FFEB3B 0%, #FFEB3B 15%, #FF9800 35%, #FF9800
+ 50%, #E65100 70%, #E65100 80%, #F44336 100%)
+ - clip-path: inset(0 calc(100% - var(--appliance-level)) 0 0)
+ - transition: clip-path 0.5s ease
+tap_action:
+ action: more-info
+label: |
+ [[[
+ let helper = states[variables.sensor_helper];
+ if (helper && helper.state === 'on') return 'Running';
+ return 'Not Running';
+ ]]]
+icon: mdi:stove
+custom_fields:
+ badge_prog: ' '
+ badge_group: ' '
+ bar: ' '
+extra_styles: |
+ [[[
+ let ent_sw = variables.sensor_plug;
+ let ent_pwr = variables.sensor_power;
+ let ent_temp = variables.sensor_temp;
+ let ent_door = variables.sensor_door;
+ let ent_helper = variables.sensor_helper;
+
+ let t_heat = parseFloat(variables.thresh_heat);
+ let targ = parseFloat(variables.target_temp);
+
+ let sw_state = states[ent_sw] ? states[ent_sw].state.toLowerCase() : 'unknown';
+ let pwr_raw = states[ent_pwr] ? parseFloat(states[ent_pwr].state) : NaN;
+ let temp_raw = states[ent_temp] ? parseFloat(states[ent_temp].state) : NaN;
+ let uom = states[ent_temp] && states[ent_temp].attributes ? states[ent_temp].attributes.unit_of_measurement : '°';
+ let is_f = String(uom).toLowerCase().includes('f');
+
+ let helper_obj = states[ent_helper];
+ let is_running = helper_obj && helper_obj.state === 'on';
+
+ let pwr = isNaN(pwr_raw) ? 0 : Math.round(pwr_raw);
+ let temp = isNaN(temp_raw) ? 0 : temp_raw;
+
+ let time_str = '';
+ if (is_running && helper_obj.last_changed) {
+ let start_time = new Date(helper_obj.last_changed);
+ let now = new Date();
+ let diff_sec = Math.max(0, Math.floor((now - start_time) / 1000));
+ let h = Math.floor(diff_sec / 3600);
+ let m = Math.floor((diff_sec % 3600) / 60);
+ time_str = diff_sec > 60 ? `${h}h ${m.toString().padStart(2, '0')}m` : 'Started';
+ }
+
+ let is_offline = sw_state === 'off' || sw_state === 'unavailable' || sw_state === 'unknown';
+ let is_heating = is_running && pwr >= t_heat && !is_offline;
+ let is_active = is_running && pwr < t_heat && !is_offline;
+ let is_idle = !is_running && !is_offline;
+
+ let status_text = 'Idle';
+ let color = '158, 158, 158';
+ let flame_op = '0';
+ let flame_speed = '0s';
+ let icon_bg = 'rgba(128, 128, 128, 0.1)';
+
+ if (is_offline) {
+ status_text = 'Plug Off';
+ color = '244, 67, 54'; // Red
+ } else if (is_heating) {
+ status_text = 'Heating';
+ color = '244, 67, 54'; // Red
+ flame_op = '1';
+ flame_speed = '0.1s';
+ icon_bg = '#221111';
+ } else if (is_active) {
+ status_text = 'Active';
+ color = '255, 152, 0'; // Orange
+ flame_op = '0.6';
+ flame_speed = '0.3s';
+ icon_bg = '#221111';
+ } else if (is_idle) {
+ status_text = 'Idle';
+ color = '158, 158, 158';
+ }
+
+ let door_state = (ent_door && states[ent_door]) ? states[ent_door].state.toLowerCase() : null;
+ let corner_color = '158, 158, 158';
+ let corner_display = 'block';
+
+ if (ent_door && door_state && door_state !== 'unknown' && door_state !== 'unavailable') {
+ if (door_state === 'on' || door_state === 'open') {
+ corner_color = '244, 67, 54';
+ }
+ } else {
+ corner_display = 'none';
+ }
+
+ let badge_prog = !is_offline ? `${status_text} • ${pwr}W` : status_text;
+ let badge_time = time_str;
+ let badge_temp = '';
+
+ let is_dual_temp = false;
+ if (temp > 0) {
+ badge_temp = `${temp}${uom}`;
+ if (!is_offline && !isNaN(targ) && targ > 0) {
+ badge_temp += ` ➔ ${targ}${uom}`;
+ is_dual_temp = true;
+ }
+ }
+
+ let progress = 0;
+ if (is_running && !is_offline && temp > 0 && targ > 0) {
+ progress = Math.floor((temp / targ) * 100);
+ progress = Math.max(2, Math.min(100, progress));
+ }
+
+ let offset_y = 10;
+ let pos_prog = -1; let pos_group = -1;
+
+ if (badge_prog) {
+ pos_prog = offset_y;
+ offset_y += 28;
+ }
+ if (badge_time || badge_temp) {
+ pos_group = offset_y;
+ }
+
+ let temp_color = '158, 158, 158';
+ if (temp > 0) {
+ let warm_threshold = is_f ? 120 : 50;
+ let hot_threshold = is_f ? 300 : 150;
+ if (temp >= hot_threshold) temp_color = '244, 67, 54';
+ else if (temp >= warm_threshold) temp_color = '255, 152, 0';
+ }
+
+ let targ_color = '158, 158, 158';
+ if (is_running) {
+ targ_color = '244, 67, 54';
+ }
+
+ let b_border = `1px solid rgba(128,128,128, 0.2)`;
+ let b_br_time = `2px solid rgb(${color})`;
+
+ let b_bg_temp = `rgba(${temp_color}, 0.15)`;
+ let b_bl_temp = b_border;
+ let b_br_temp = `2px solid rgb(${temp_color})`;
+
+ if (is_dual_temp) {
+ b_bg_temp = `linear-gradient(90deg, rgba(${temp_color}, 0.15) 0%, rgba(${targ_color}, 0.15) 100%)`;
+ b_bl_temp = `2px solid rgb(${temp_color})`;
+ b_br_temp = `2px solid rgb(${targ_color})`;
+ }
+
+ return `
+ #card {
+ --appliance-color: ${color};
+ --appliance-level: ${progress}%;
+ --appliance-bg: ${icon_bg};
+ --oven-flame-op: ${flame_op};
+ --oven-flame-speed: ${flame_speed};
+ --door-corner-color: rgb(${corner_color});
+ --door-corner-display: ${corner_display};
+ }
+
+ #card::after {
+ content: ''; display: var(--door-corner-display); position: absolute;
+ top: -0.5px; left: -0.5px; opacity: 0.9; width: 15px; height: 15px;
+ border-top: 5px solid var(--door-corner-color); border-left: 5px solid var(--door-corner-color);
+ border-top-left-radius: var(--ha-card-border-radius, 12px); pointer-events: none; transition: border-color 0.3s ease;
+ }
+
+ #img-cell { background: var(--appliance-bg) !important; transition: background 0.5s ease; }
+
+ #img-cell::before {
+ content: ""; position: absolute; left: 50%; bottom: -60%; width: 80%; height: 80%;
+ background: radial-gradient(circle at center, #ffff00 0%, #ff9800 100%);
+ transform: translateX(-50%) rotate(45deg); box-shadow: 0 0 20px #ff5722;
+ border-radius: 20px 0 20px 0; z-index: 1; opacity: var(--oven-flame-op);
+ animation: flame-burn var(--oven-flame-speed) infinite alternate;
+ display: ${flame_op === '0' ? 'none' : 'block'};
+ }
+
+ #img-cell::after {
+ content: ""; position: absolute; left: 45%; bottom: -65%; width: 90%; height: 90%;
+ background: rgba(255, 87, 34, 0.6); transform: translateX(-50%) rotate(45deg);
+ border-radius: 30px 0 30px 0; z-index: 0; opacity: var(--oven-flame-op);
+ animation: flame-burn-two 0.3s infinite alternate-reverse;
+ display: ${flame_op === '0' ? 'none' : 'block'};
+ }
+
+ #badge_prog {
+ top: ${pos_prog}px; display: ${pos_prog > -1 ? 'block' : 'none'};
+ background: rgba(${color}, 0.15); color: rgb(var(--appliance-color));
+ border: 1px solid rgba(var(--appliance-color), 0.3);
+ }
+ #badge_prog::before { content: "${badge_prog}"; }
+
+ #badge_group {
+ top: ${pos_group}px; display: ${pos_group > -1 ? 'flex' : 'none'};
+ }
+
+ #badge_group::before {
+ content: "${badge_time}"; display: ${badge_time ? 'block' : 'none'};
+ padding: 4px 8px; font-size: 10px; letter-spacing: 0.5px; white-space: nowrap;
+ opacity: 0.9; text-transform: uppercase; font-weight: 500;
+ background: rgba(${color}, 0.15); color: var(--primary-text-color, #fff);
+ border-top: ${b_border}; border-bottom: ${b_border};
+ border-left: ${b_border}; border-right: ${b_br_time};
+ border-radius: 6px !important;
+ }
+
+ #badge_group::after {
+ content: "${badge_temp}"; display: ${badge_temp ? 'block' : 'none'};
+ padding: 4px 8px; font-size: 10px; letter-spacing: 0.5px; white-space: nowrap;
+ opacity: 0.9; text-transform: uppercase; font-weight: 500;
+ background: ${b_bg_temp}; color: var(--primary-text-color, #fff);
+ border-top: ${b_border}; border-bottom: ${b_border};
+ border-left: ${b_bl_temp}; border-right: ${b_br_temp};
+ border-radius: 6px !important;
+ }
+
+ @keyframes flame-burn {
+ 0% { transform: translateX(-50%) rotate(45deg) scale(1.0); }
+ 100% { transform: translateX(-50%) rotate(45deg) scale(0.95); }
+ }
+ @keyframes flame-burn-two {
+ 0% { transform: translateX(-50%) rotate(45deg) scale(0.9); }
+ 100% { transform: translateX(-50%) rotate(45deg) scale(1.0); }
+ }
+ `;
+ ]]]
+
+```
+
+
+
+Dumb Oven (Helper/Template)
+
+```yaml
+ - binary_sensor:
+ - name: "Oven Active Delay"
+ unique_id: oven_active_delay
+ device_class: running
+ icon: mdi:stove
+ # Change the power entity below to match your actual smart plug power entity
+ state: >
+ {% set power = states('sensor.aqara_plug_h2_power') %}
+ {% if power in ['unavailable', 'unknown', 'none'] %}
+ {{ this.state }}
+ {% else %}
+ {{ power | float(0) > 10 }}
+ {% endif %}
+ delay_off: "00:10:00"
+```
+
+
+---
+
+
+
[paypal_me_shield]: https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white