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. 40-high +ezgif-29d7d8690b9de402 + + `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