diff --git a/main.yaml b/main.yaml index 6bb1f3e..187a038 100644 --- a/main.yaml +++ b/main.yaml @@ -4168,6 +4168,469 @@ views: transform: translateY(0px) scale(1); } } + - type: custom:button-card + show_name: false + show_icon: false + show_state: false + grid_options: + columns: 9 + rows: auto + custom_fields: + dining_fan: + card: + type: custom:mushroom-entity-card + entity: fan.shhf_cn_1129134246_sflt11_s_11_fan + name: 餐边扇 + icon: mdi:fan + icon_color: cyan + tap_action: + action: toggle + hold_action: + action: more-info + card_mod: + style: + mushroom-shape-icon$: | + .shape { + {# ========== USER CONFIG ========== #} + {# true = number mode, false = state mode #} + {% set use_number = false %} + + {# STATE MODE SETTINGS #} + {% set state_entity = 'fan.shhf_cn_1129134246_sflt11_s_11_fan' %} + {% set active_value = 'on' %} + + {# OPTIONAL: NUMBER MODE SETTINGS #} + {% set number_entity = 'sensor.plug_power' %} + + {# '>' '<' '=' '>=' '<=' #} + {% set number_operator = '>' %} + {% set threshold = 0.0 %} + {# ========== END USER CONFIG ====== #} + + {# -------- TRIGGER DECISION LOGIC -------- #} + {% if use_number %} + {% set num = states(number_entity) | float(0) %} + + {% if number_operator == '>' %} + {% set trigger_active = (num > threshold) %} + {% elif number_operator == '<' %} + {% set trigger_active = (num < threshold) %} + {% elif number_operator == '=' %} + {% set trigger_active = (num == threshold) %} + {% elif number_operator == '>=' %} + {% set trigger_active = (num >= threshold) %} + {% elif number_operator == '<=' %} + {% set trigger_active = (num <= threshold) %} + {% else %} + {% set trigger_active = false %} + {% endif %} + {% else %} + {% set sensor_entity = state_entity %} + + {% if states(sensor_entity) == active_value %} + {% set trigger_active = true %} + {% else %} + {% set trigger_active = false %} + {% endif %} + {% endif %} + {# ------ END TRIGGER DECISION LOGIC ------ #} + + {# 根据当前风扇的 percentage 属性计算转速 #} + {% set speed = state_attr(config.entity, 'percentage') | int(0) %} + + {% if trigger_active %} + {% if speed == 0 and is_state(config.entity, 'off') %} + --shape-animation: fan-ultra-idle 3s ease-in-out infinite; + {% else %} + {% set dur = 1.2 - (speed / 120) %} + --shape-animation: fan-ultra-spin {{ [0.25, dur] | max | round(2) }}s linear infinite; + {% endif %} + + --fan-warp-animation: fan-warp 1.4s ease-in-out infinite; + --fan-stream-animation: fan-stream 1.6s ease-in-out infinite; + opacity: 1; + {% else %} + --shape-animation: none; + --fan-warp-animation: none; + --fan-stream-animation: none; + opacity: 0.7; + {% endif %} + + transform-origin: 50% 50%; + position: relative; + } + + .shape::before, + .shape::after { + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + pointer-events: none; + } + + .shape::before { + animation: var(--fan-warp-animation); + } + + .shape::after { + mix-blend-mode: screen; + animation: var(--fan-stream-animation); + } + + @keyframes fan-ultra-spin { + 0% { + transform: rotate(0deg) scale(1); + filter: drop-shadow( + 0 0 3px + rgba(var(--rgb-{{ config.icon_color }}), 0.8) + ); + } + + 20% { + transform: rotate(72deg) scale(1.03); + } + + 40% { + transform: rotate(144deg) scale(1.05); + } + + 60% { + transform: rotate(216deg) scale(1.03); + } + + 80% { + transform: rotate(288deg) scale(1.02); + } + + 100% { + transform: rotate(360deg) scale(1); + filter: drop-shadow( + 0 0 5px + rgba(var(--rgb-{{ config.icon_color }}), 1) + ); + } + } + + @keyframes fan-ultra-idle { + 0% { + transform: rotate(-6deg); + } + + 50% { + transform: rotate(6deg); + } + + 100% { + transform: rotate(-6deg); + } + } + + @keyframes fan-warp { + 0% { + box-shadow: + 0 0 0 0 + rgba(var(--rgb-{{ config.icon_color }}), 0.7), + 0 0 0 0 + rgba(var(--rgb-{{ config.icon_color }}), 0.25); + } + + 40% { + box-shadow: + 0 0 0 8px + rgba(var(--rgb-{{ config.icon_color }}), 0.4), + 0 0 24px 6px + rgba(var(--rgb-{{ config.icon_color }}), 0.25); + } + + 100% { + box-shadow: + 0 0 0 18px + rgba(var(--rgb-{{ config.icon_color }}), 0), + 0 0 40px 12px + rgba(var(--rgb-{{ config.icon_color }}), 0); + } + } + + @keyframes fan-stream { + 0% { + box-shadow: + 20px 0 16px -12px + rgba(var(--rgb-{{ config.icon_color }}), 0), + -20px 0 16px -12px + rgba(var(--rgb-{{ config.icon_color }}), 0); + } + + 50% { + box-shadow: + 24px 0 20px -10px + rgba(var(--rgb-{{ config.icon_color }}), 0.6), + -24px 0 20px -10px + rgba(var(--rgb-{{ config.icon_color }}), 0.4); + } + + 100% { + box-shadow: + 20px 0 16px -12px + rgba(var(--rgb-{{ config.icon_color }}), 0), + -20px 0 16px -12px + rgba(var(--rgb-{{ config.icon_color }}), 0); + } + } + .: | + mushroom-shape-icon { + --icon-size: 68px; + display: flex; + margin: -18px 0 10px -20px !important; + padding-right: 10px; + } + + ha-card { + height: 100% !important; + clip-path: inset(0 0 0 0 round var(--ha-card-border-radius, 12px)); + } + xiaoai_fan: + card: + type: custom:mushroom-entity-card + entity: fan.xiaomi_cn_2196552583_p90_s_2_fan + name: 小爱风扇 + icon: mdi:fan + icon_color: cyan + tap_action: + action: toggle + hold_action: + action: more-info + card_mod: + style: + mushroom-shape-icon$: | + .shape { + {# ========== USER CONFIG ========== #} + {# true = number mode, false = state mode #} + {% set use_number = false %} + + {# STATE MODE SETTINGS #} + {% set state_entity = 'fan.xiaomi_cn_2196552583_p90_s_2_fan' %} + {% set active_value = 'on' %} + + {# OPTIONAL: NUMBER MODE SETTINGS #} + {% set number_entity = 'sensor.plug_power' %} + + {# '>' '<' '=' '>=' '<=' #} + {% set number_operator = '>' %} + {% set threshold = 0.0 %} + {# ========== END USER CONFIG ====== #} + + {# -------- TRIGGER DECISION LOGIC -------- #} + {% if use_number %} + {% set num = states(number_entity) | float(0) %} + + {% if number_operator == '>' %} + {% set trigger_active = (num > threshold) %} + {% elif number_operator == '<' %} + {% set trigger_active = (num < threshold) %} + {% elif number_operator == '=' %} + {% set trigger_active = (num == threshold) %} + {% elif number_operator == '>=' %} + {% set trigger_active = (num >= threshold) %} + {% elif number_operator == '<=' %} + {% set trigger_active = (num <= threshold) %} + {% else %} + {% set trigger_active = false %} + {% endif %} + {% else %} + {% set sensor_entity = state_entity %} + + {% if states(sensor_entity) == active_value %} + {% set trigger_active = true %} + {% else %} + {% set trigger_active = false %} + {% endif %} + {% endif %} + {# ------ END TRIGGER DECISION LOGIC ------ #} + + {# 根据当前风扇的 percentage 属性计算转速 #} + {% set speed = state_attr(config.entity, 'percentage') | int(0) %} + + {% if trigger_active %} + {% if speed == 0 and is_state(config.entity, 'off') %} + --shape-animation: fan-ultra-idle 3s ease-in-out infinite; + {% else %} + {% set dur = 1.2 - (speed / 120) %} + --shape-animation: fan-ultra-spin {{ [0.25, dur] | max | round(2) }}s linear infinite; + {% endif %} + + --fan-warp-animation: fan-warp 1.4s ease-in-out infinite; + --fan-stream-animation: fan-stream 1.6s ease-in-out infinite; + opacity: 1; + {% else %} + --shape-animation: none; + --fan-warp-animation: none; + --fan-stream-animation: none; + opacity: 0.7; + {% endif %} + + transform-origin: 50% 50%; + position: relative; + } + + .shape::before, + .shape::after { + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + pointer-events: none; + } + + .shape::before { + animation: var(--fan-warp-animation); + } + + .shape::after { + mix-blend-mode: screen; + animation: var(--fan-stream-animation); + } + + @keyframes fan-ultra-spin { + 0% { + transform: rotate(0deg) scale(1); + filter: drop-shadow( + 0 0 3px + rgba(var(--rgb-{{ config.icon_color }}), 0.8) + ); + } + + 20% { + transform: rotate(72deg) scale(1.03); + } + + 40% { + transform: rotate(144deg) scale(1.05); + } + + 60% { + transform: rotate(216deg) scale(1.03); + } + + 80% { + transform: rotate(288deg) scale(1.02); + } + + 100% { + transform: rotate(360deg) scale(1); + filter: drop-shadow( + 0 0 5px + rgba(var(--rgb-{{ config.icon_color }}), 1) + ); + } + } + + @keyframes fan-ultra-idle { + 0% { + transform: rotate(-6deg); + } + + 50% { + transform: rotate(6deg); + } + + 100% { + transform: rotate(-6deg); + } + } + + @keyframes fan-warp { + 0% { + box-shadow: + 0 0 0 0 + rgba(var(--rgb-{{ config.icon_color }}), 0.7), + 0 0 0 0 + rgba(var(--rgb-{{ config.icon_color }}), 0.25); + } + + 40% { + box-shadow: + 0 0 0 8px + rgba(var(--rgb-{{ config.icon_color }}), 0.4), + 0 0 24px 6px + rgba(var(--rgb-{{ config.icon_color }}), 0.25); + } + + 100% { + box-shadow: + 0 0 0 18px + rgba(var(--rgb-{{ config.icon_color }}), 0), + 0 0 40px 12px + rgba(var(--rgb-{{ config.icon_color }}), 0); + } + } + + @keyframes fan-stream { + 0% { + box-shadow: + 20px 0 16px -12px + rgba(var(--rgb-{{ config.icon_color }}), 0), + -20px 0 16px -12px + rgba(var(--rgb-{{ config.icon_color }}), 0); + } + + 50% { + box-shadow: + 24px 0 20px -10px + rgba(var(--rgb-{{ config.icon_color }}), 0.6), + -24px 0 20px -10px + rgba(var(--rgb-{{ config.icon_color }}), 0.4); + } + + 100% { + box-shadow: + 20px 0 16px -12px + rgba(var(--rgb-{{ config.icon_color }}), 0), + -20px 0 16px -12px + rgba(var(--rgb-{{ config.icon_color }}), 0); + } + } + .: | + mushroom-shape-icon { + --icon-size: 68px; + display: flex; + margin: -18px 0 10px -20px !important; + padding-right: 10px; + } + + ha-card { + height: 100% !important; + clip-path: inset(0 0 0 0 round var(--ha-card-border-radius, 12px)); + } + styles: + card: + - height: 158px + - padding: 6px + - border-radius: 20px + - background: rgba(255, 255, 255, 0.06) + - box-shadow: none + - box-sizing: border-box + grid: + - grid-template-areas: | + "dining_fan" + "xiaoai_fan" + - grid-template-columns: 1fr + - grid-template-rows: repeat(2, minmax(0, 1fr)) + - row-gap: 15px + - height: 100% + custom_fields: + dining_fan: + - height: 100% + - min-height: 0 + - width: 100% + - justify-self: stretch + - text-align: left + xiaoai_fan: + - height: 100% + - min-height: 0 + - width: 100% + - justify-self: stretch + - text-align: left - square: false type: grid cards: