Painel de cortinas com janela animada pelo clima (button-card + shutter-card + weather)

🪟 Painel de Cortinas com Janela Animada pelo Clima

button-card + shutter-card + layout-card + entidade weather


Olá, pessoal! 👋

Quero compartilhar o painel que montei para controlar as 4 cortinas motorizadas da minha casa: Cozinha Sala Quarto Escada.

A ideia foi sair do card padrão de cover e criar algo mais visual. Cada cortina aparece como uma janela de verdade, que você abre e fecha arrastando a alça. E pelo vidro dá para ver o tempo lá fora: sol, nuvens, chuva, neblina, tempestade, neve e céu estrelado à noite, tudo animado em CSS e alimentado pela integração de clima do Home Assistant.

📸 Como ficou

Screen_Recording_20261002_214450_HomeAssistant-ezgif.com-speed

Screen_Recording_20261002_210050_HomeAssistant-ezgif.com-video-to-gif-converter


🎯 Por que precisei de cards personalizados

Os cards nativos resolvem o controle, mas não entregavam o que eu queria:

  • Arrastar a cortina como se fosse física, e não só botões ou um slider genérico.
  • Várias informações num mesmo card: nome, estado com porcentagem, a janela, o clima e uma dica de uso, num layout em grade controlado por mim.
  • Estilo dinâmico: borda dourada quando a cortina está aberta, contorno tracejado quando está indisponível, céu que muda conforme o clima e o dia/noite.
  • Botões inteligentes, que ficam esmaecidos e sem ação quando a cortina não suporta aquele comando.
  • Layout responsivo, funcionando bem no celular e no tablet.

📦 Dependências (HACS)

CardAutorPara que usei
button-cardRomRiderBase de tudo: templates JavaScript, custom_fields, variables, state_display, extra_styles.
layout-cardthomaslovenGrade responsiva com auto-fit + minmax(): 1 coluna no celular, várias no tablet/PC.
shutter-cardDeejayfoolA cortina arrastável. Aproveito o arraste nativo dele e redesenho todo o visual por CSS.

Também uso duas cenas já existentes: scene.cortinas_aberta e scene.cortinas_fechadas.


🌦️ A integração com o clima (weather.casa)

O clima vem de uma entidade weather do Home Assistant. No meu caso ela se chama weather.casa.

⚠️ Atenção: se a sua entidade tiver outro nome, troque weather.casa no código. Como segurança, se ela não existir, o código usa automaticamente a primeira entidade weather.* que encontrar.

Também uso o sun.sun para saber se é dia ou noite (sol acima ou abaixo do horizonte). Se ele não existir, o código cai para um horário fixo: antes das 6h ou depois das 18h é noite.

Com isso, cada janela mostra:

  • 🏷️ Um chip com ícone, temperatura e condição em português (ex.: ☁ 18 Nublado).
  • 🖼️ O "céu" dentro do vidro, com duas camadas de efeito animadas atrás da cortina.

Mapeamento das condições

Condição do HAEfeito no vidro
sunny☀️ Céu azul, sol pulsando e raios de luz
clear-night🌙 Céu escuro com estrelas piscando e lua
partlycloudy⛅ Nuvens passando + sol (à noite: nuvens + lua)
cloudy☁️ Duas camadas de nuvens em velocidades diferentes
windy / windy-variant💨 Nuvens passando rápido
fog🌫️ Faixas de neblina subindo e descendo
rainy🌧️ Chuva caindo e gotas escorrendo no vidro
pouring🌧️ Chuva mais densa e rápida
lightning / lightning-rainy⛈️ Chuva forte com clarões de relâmpago
snowy / snowy-rainy / hail❄️ Flocos de neve em duas camadas
sem dados⚠️ Céu neutro e ícone de alerta

💡 Resultado: quando o sol se põe, o céu escurece sozinho dentro de todas as janelas.


🧩 Como o código está organizado

1️⃣ Cabeçalho (largura total)

Um button-card que mostra:

  • Relógio atualizado a cada segundo (update_timer: 1000).
  • Saudação (Bom dia / Boa tarde / Boa noite) e a data por extenso em pt-BR.
  • Resumo: quantas cortinas estão abertas, em movimento e indisponíveis, mais a temperatura lá fora.
  • Um botão "Previsão da semana" (button-card aninhado via custom_fields) que abre o more-info do clima.

2️⃣ Botões globais

Abrir todas e Fechar todas chamam scene.turn_on e pedem confirmação antes de executar, evitando toques acidentais. Têm feedback háptico e uma leve animação ao apertar.

3️⃣ Um bloco por cortina

Cada cortina é um vertical-stack com três partes:

🅰️ Card principal (button-card)

  • state_display em JS mostra Aberta · 75%, Fechada · 0%, Em movimento, Indisponível ou Não encontrada, validando se current_position é um número entre 0 e 100.
  • variables.clima é um template JS que lê o weather e o sun.sun, escolhe o efeito certo (com variantes noturnas como partlyN, cloudyN, rainN, snowN) e devolve cores, gradientes, tamanhos e animações.
  • triggers_update: [weather.casa, sun.sun] faz o card redesenhar quando o clima ou o sol mudam, mesmo que a cortina não mude.
  • Os valores calculados viram variáveis CSS (--nx-sky, --nx-fx1, --nx-a1, --nx-fx2...) usadas em extra_styles.

💡 Ideia central: o JavaScript decide o quê desenhar; o CSS só desenha.

  • Estilos por estado: borda e brilho dourados em open, texto azul em opening/closing, borda tracejada em unavailable.

🅱️ A janela (shutter-card dentro de um custom_field)

O shutter-card fica embutido no button-card e é totalmente redesenhado por CSS (ele usa light DOM, então os seletores alcançam os elementos internos):

  • Os botões e títulos originais do shutter-card são escondidos.
  • A janela é desenhada com camadas de background: caixa da cortina em madeira, vidro com o céu e divisória central.
  • ::before e ::after ficam exatamente sobre o vidro com os dois efeitos animados, e com z-index abaixo da cortina. Ao fechar, a cortina cobre o clima de verdade.
  • A cortina vira uma persiana listrada em tons de madeira, e a alça ganha uma barra dourada com botão ↕.
  • Se a cortina estiver indisponível ou não suportar posição, a janela fica esmaecida e não aceita toque.

⚠️ Detalhe importante: desativei o efeito de "encolher ao tocar" (transform: scale) nesse card. O shutter-card calcula o arraste por coordenadas da página, e a escala atrapalhava o movimento.

🅲 Botões de controle

  • Abrir Parar Fechar e os atalhos 25% 50% 75%.
  • Cada botão confere o supported_features da entidade com operação bit a bit antes de agir.
  • Se o recurso não é suportado, ou a entidade está indisponível, o botão fica esmaecido e a ação vira none.
BitRecursoBotão
1OPENAbrir
2CLOSEFechar
4SET_POSITION25% / 50% / 75% e arraste
8STOPParar

4️⃣ Acessibilidade e desempenho

  • ♿ Respeita prefers-reduced-motion: se o sistema pedir menos animação, os efeitos de clima e dos botões são desligados.
  • ⚡ Tudo é CSS puro (@keyframes com background-position, opacity e transform), sem imagens externas nem JS rodando em loop.

🔁 Sobre a cortina da Escada: o motor dela está com sentido invertido, então no card dela Abrir chama cover.close_cover e Fechar chama cover.open_cover (a checagem de supported_features acompanha a troca). Se a sua não for invertida, desfaça essa troca.


🛠️ Como adaptar para a sua casa

  1. Instale pelo HACS: button-card, layout-card e shutter-card.
  2. Troque os entity_id: cover.cortina_cozinha, cover.cortina_sala, cover.cortina_quarto, cover.cortina_escada.
  3. Troque weather.casa pela sua entidade de clima (ou deixe, e o fallback usa a primeira weather.*).
  4. Crie ou troque as cenas scene.cortinas_aberta e scene.cortinas_fechadas.
  5. Ajuste a lista de cortinas do resumo no cabeçalho (nx_chips).
  6. Se alguma cortina for invertida, veja a observação da Escada acima.

🚀 Próximos passos

O YAML ficou grande porque cada card de cortina é autossuficiente: o bloco de clima se repete em cada um. A próxima melhoria é mover isso para button_card_templates (ou decluttering-card), deixando uma única definição e cada cortina com poucas linhas.

Sugestões são muito bem-vindas! Se alguém já fez algo parecido, adoraria ver. 🙌


📄 Código completo

O YAML completo (o card e a visualização inteira) está no GitHub:


Qualquer dúvida, é só perguntar. Espero que sirva de inspiração! 🙂

2 curtidas