Tema Doug Futurista: passo a passo, cartões fx_ e como criar o seu
Instale o tema Doug Futurista, ajuste cor, vidro e cantos, cole os 9 cartões fx_ no seu painel e aprenda a criar um cartão novo do zero com o button-card.
Prefere ver? Assista ao vídeoEste é o passo a passo escrito do vídeo do tema Doug Futurista: instalar o tema, deixar com a sua cara, colar os cartões fx_ no seu painel e, no fim, criar um cartão novo do zero usando o mesmo molde. Tudo o que aparece aqui está no repositório ha_tema_futurista e nos códigos prontos para copiar no fim da página.
O que você precisa
| Item | Para quê | Obrigatório? |
|---|---|---|
| Tema Doug Futurista (Marketplace) | cores, vidro azul-marinho, menu e barra do topo | sim |
| button-card (Marketplace) | é a base de todos os cartões fx_ |
para os cartões |
| layout-card (Marketplace) | a grade da Home que se adapta ao celular | para a Home de exemplo |
| card-mod (Marketplace) | o desfoque de vidro atrás dos cartões | não |
No Home Assistant 2026.11 ou mais novo, tudo isso fica em Configurações → Marketplace. Em versões anteriores, o caminho é o mesmo pelo HACS.
Etapa 1: instalar o tema
-
Configurações → Marketplace, procure Doug Futurista e instale.
-
Garanta que o
configuration.yamlcarrega a pasta de temas (o bloco completo, já com o card-mod, está no código configuration.yaml do tema):frontend: themes: !include_dir_merge_named themes -
Rode a ação
frontend.reload_themes(ou reinicie o Home Assistant).
Só para você ou para a casa toda
-
Só para você: clique no seu nome, no canto de baixo do menu → Tema → Doug Futurista.
-
Para a casa toda: em Ferramentas de desenvolvedor → Ações, rode:
action: frontend.set_theme data: name: Doug FuturistaRode de novo com
mode: darkpara valer também no modo escuro. Quem estiver com "Padrão do servidor" passa a ver o tema.
Etapa 2: ajustar e personalizar
Vidro fosco (card-mod)
O desfoque atrás dos cartões usa o card-mod. Para ele valer no tema (e não só dentro de um cartão), ele precisa ser carregado como módulo do frontend:
frontend:
themes: !include_dir_merge_named themes
extra_module_url:
- /local/community/lovelace-card-mod/card-mod.jsReinicie uma vez. Sem esse passo o tema funciona igual, só sem o desfoque.
Cor de destaque, transparência, cantos e fundo
Tudo fica no arquivo do tema (doug-futurista.yaml):
| O que mudar | Onde | Dica |
|---|---|---|
| Cor de destaque | as duas linhas marcadas com DESTAQUE (primary-color e accent-color) |
botões, abas, menu, interruptores, bordas e a grade do fundo seguem essas duas |
| Transparência | os 0.78 em card-background-color e ha-card-background |
número menor = mais vidro |
| Cantos | ha-card-border-radius (18px) |
menor = mais quadrado |
| Imagem no fundo | lovelace-background |
center / cover no-repeat url("/local/fundo.jpg") fixed, com a imagem em www/ |
Depois de cada mudança, frontend.reload_themes.
Não perca suas mudanças
Atualizar o tema pelo Marketplace substitui o arquivo original. Para personalizar com segurança, salve uma cópia com outro nome em themes/ (por exemplo themes/minha-casa.yaml), troque a primeira linha Doug Futurista: por Minha Casa: e recarregue os temas. O tema "Minha Casa" aparece na lista e nenhuma atualização mexe nele.
Etapa 3: colar os cartões no seu painel
Os cartões do vídeo são modelos (templates) do button-card. Você cola os modelos uma vez no topo do painel e depois cada cartão só diz qual modelo usar e qual entidade mostrar.
- Abra o painel → lápis de editar → ⋮ → Editor de configuração RAW.
- Bem no topo, antes de
views:, escrevabutton_card_templates:e cole embaixo, com 2 espaços de recuo, o conteúdo dos modelos fx_. - Dentro de
views:, cole a Home de exemplo. - Troque os
entity_idde exemplo (person.voce,light.sala_teto,sensor.dolar...) pelos da sua casa.
O começo do RAW fica assim:
button_card_templates:
fx_pill:
show_icon: true
# ...resto dos modelos...
views:
- title: Home
path: home-futurista
# ...Se o seu painel é em modo YAML (arquivo), dá para deixar os modelos num arquivo separado:
button_card_templates: !include doug-futurista-cards.yamlEntidade que não existe não quebra o cartão: ele mostra "entidade não encontrada" e o entity_id que faltou, para você achar o erro rápido.
Os 9 modelos fx_ e suas variáveis
| Modelo | O que mostra | entity |
variables |
|---|---|---|---|
fx_pill |
pílula do topo (ícone + texto) | opcional | use name e icon normais do button-card |
fx_pessoa |
foto com anel verde em casa, estado, bateria e passos | person.x |
foto (url), bateria (sensor %), passos (sensor) |
fx_cotacao |
valor em R$ com variação ▲▼ em % | sensor do valor | titulo, icone, cor, anterior (sensor do valor anterior), decimais |
fx_aspirador |
estado em português e botões play, pausa e base | vacuum.x |
bateria, area, tempo (sensores) |
fx_ambiente |
linha de cômodo com contador de luzes acesas | não usa | nome, icone, luzes (lista de entity_id) |
fx_luz |
botão de luz ou plugue: toque liga, segurar abre detalhes | light.x ou switch.x |
nome, icone (opcionais) |
fx_sensor |
porta, janela, movimento, pessoa ou campainha com "há quanto tempo" | binary_sensor.x |
tipo (porta, janela, movimento, pessoa, campainha), nome |
fx_acesso |
botão grande de portão, porta, sirene ou visitas | cover.x, lock.x, switch.x... |
tipo (portao, porta, sirene, visitas), titulo + a sua tap_action |
fx_pilula |
pílula pequena de um switch, laranja quando ligado | switch.x |
rotulo |
Exemplo de uso de cada um:
- type: custom:button-card
template: fx_luz
entity: light.sala_teto
variables: {nome: Sala}
- type: custom:button-card
template: fx_sensor
entity: binary_sensor.porta_entrada
variables: {tipo: porta, nome: Entrada}
- type: custom:button-card
template: fx_cotacao
entity: sensor.dolar
variables: {titulo: Dólar, icone: mdi:currency-usd, cor: '#34e08a', anterior: sensor.dolar_ontem}
- type: custom:button-card
template: fx_acesso
entity: cover.portao
variables: {tipo: portao, titulo: Portão}
tap_action: {action: toggle}Etapa 4: por dentro de um modelo fx_
Todo modelo fx_ tem as mesmas quatro partes. Entendendo isso, você lê qualquer um deles e cria o seu.
1. O vidro (styles.card)
styles:
card:
- border-radius: 18px
- background: linear-gradient(145deg, rgba(14,34,66,0.78), rgba(8,18,40,0.78))
- border: 1px solid rgba(60,200,255,0.25)
- box-shadow: 0 8px 30px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.06)
- backdrop-filter: blur(14px)
- color: '#eaf4ff'É sempre o mesmo bloco: fundo azul-marinho translúcido, borda ciano fina, sombra e desfoque. Copie esse bloco em todo cartão novo e ele já nasce com a cara do tema.
2. A grade (styles.grid)
O button-card monta o cartão numa grade CSS. Cada pedaço ganha um nome e você desenha a posição dele com grid-template-areas:
styles:
grid:
- grid-template-areas: '"foto info"' # uma linha, duas colunas
- grid-template-columns: 76px 1fr # foto fixa, info ocupa o resto
- column-gap: 14px
- align-items: centerDuas linhas? '"topo botoes" "infos botoes"': o botoes repetido nas duas linhas ocupa a altura toda (é assim no fx_aspirador).
3. Os pedaços (custom_fields) em JavaScript
Cada nome da grade é um custom_field. O conteúdo vem de um trecho JavaScript entre [[[ ]]], que devolve HTML:
custom_fields:
info: |
[[[
if (!entity) return 'entidade não encontrada';
const em = entity.state === 'home';
return `<div style="color:${em ? '#34e08a' : '#9fb0cc'}">${em ? 'Em casa' : 'Fora'}</div>`;
]]]Dentro do [[[ ]]] você tem:
entity: a entidade do cartão (estado ementity.state, atributos ementity.attributes);states['sensor.x']: qualquer outra entidade da casa;variables.x: as variáveis que o cartão passou;<ha-icon icon="mdi:...">: os ícones do Home Assistant dentro do HTML.
4. As variáveis (variables)
São os "parâmetros" do modelo. No modelo você define valores padrão; no cartão você passa só o que quer mudar:
# no modelo
fx_cotacao:
variables:
decimais: 2
# no cartão
- type: custom:button-card
template: fx_cotacao
entity: sensor.gasolina
variables: {titulo: Gasolina, icone: mdi:gas-station, cor: '#ffc83c', decimais: 3}Etapa 5: criando um cartão novo (fx_medidor)
Vamos montar um modelo que não existe no vídeo: um medidor com o valor do sensor e uma barra de progresso, que fica vermelha quando passa do limite. Serve para consumo de energia, caixa d'água, bateria, CPU do servidor, qualquer sensor numérico.
Passo 1: desenhe a grade. Duas linhas, uma coluna: topo (ícone, título e valor) e barra.
grid:
- grid-template-areas: '"topo" "barra"'
- grid-template-columns: 1fr
- row-gap: 10pxPasso 2: defina as variáveis com valores padrão, para o cartão funcionar mesmo que a pessoa não passe nada:
variables:
titulo: Medidor
icone: mdi:gauge
cor: '#3cc8ff'
max: 100 # valor que enche a barra
alerta: 80 # % em que a barra fica vermelha
unidade: '' # vazio = usa a unidade do sensor
decimais: 1Passo 3: escreva o topo, tratando a entidade que não existe e o sensor indisponível:
if (!entity) return 'entidade não encontrada';
const v = parseFloat(entity.state);
const un = variables.unidade || entity.attributes.unit_of_measurement || '';
const txt = isNaN(v) ? 'indisponível' : v.toLocaleString('pt-BR') + ' ' + un;Passo 4: escreva a barra, calculando a porcentagem e limitando entre 0 e 100:
const p = Math.max(0, Math.min(100, v / variables.max * 100));
const cor = p >= variables.alerta ? '#ff5a6e' : variables.cor;
return `<div style="height:8px;border-radius:999px;background:rgba(255,255,255,0.08)">
<div style="width:${p}%;height:100%;border-radius:999px;background:${cor}"></div></div>`;Passo 5: junte tudo com o bloco de vidro e cole embaixo de button_card_templates:. O modelo completo, pronto para copiar, está em Cartão fx_medidor. Para usar:
- type: custom:button-card
template: fx_medidor
entity: sensor.consumo_agora
variables:
titulo: Consumo agora
icone: mdi:flash
cor: '#ffc83c'
max: 5000
alerta: 70
decimais: 0Esse é o caminho para qualquer cartão novo: copie o vidro, desenhe a grade, liste as variáveis, escreva um custom_field por pedaço e sempre trate !entity e isNaN.
Layout: a grade da Home
A Home de exemplo usa o custom:grid-layout (do layout-card) com áreas nomeadas. Cada cartão diz em que área fica com view_layout: {grid-area: ...}:
type: custom:grid-layout
layout:
grid-template-columns: repeat(4, minmax(0, 1fr))
grid-template-areas: |
"hero hero hero hero"
"pills pills pills pills"
"p1 p2 asp amb"
"cot cot luz amb"
"seg seg luz amb"
mediaquery:
"(max-width: 620px)":
grid-template-columns: minmax(0, 1fr)
grid-template-areas: |
"hero" "pills" "p1" "p2" "asp" "cot" "luz" "seg" "amb"
"(max-width: 1100px)":
grid-template-columns: repeat(2, minmax(0, 1fr))Quer mudar a ordem? Mude só o desenho das áreas: os cartões não precisam sair do lugar no YAML.
Armadilhas (e como resolver)
- Mediaquery fora de ordem. No layout-card vale a primeira que casar. Coloque a de tela menor (
620px) antes da maior (1100px), senão o celular pega o layout de tablet. minmax(0, 1fr)e não só1fr. Com1fr, um texto comprido estica a coluna e quebra a grade inteira.- Modelo não encontrado.
button_card_templates:tem que estar no topo do RAW, no mesmo nível deviews:, e os modelos com 2 espaços de recuo. - Aspas dentro do JavaScript. Em YAML com aspas simples, uma aspa simples dentro do código vira
''(duas). Prefira o bloco|com o[[[ ]]]em linhas separadas. - Cartão dentro de cartão que não atualiza. Um button-card aninhado dentro de
custom_fieldsàs vezes não redesenha quando o estado muda. Se acontecer, deixe o botão como cartão próprio na grade. - Ícone que some em cartão baixo. Em cartões de altura pequena, o
icondo button-card pode ficar escondido. Coloque o<ha-icon>dentro do HTML donameou docustom_field. - Tema não aparece. Faltou
frontend.reload_themesou othemes: !include_dir_merge_named themesnoconfiguration.yaml. - Sem vidro fosco. O card-mod precisa do
extra_module_urle de um reinício. Com o fundo de grade padrão o efeito é discreto; com uma imagem no fundo ele aparece mais.
Códigos deste vídeo
- Modelos fx_ do button-card (arquivo completo)
- Home de exemplo com grid-layout
- configuration.yaml do tema e do card-mod
- Cartão fx_medidor: valor com barra de progresso
Ficou com dúvida em algum cartão ou quer que eu explique uma tela específica? Deixa nos comentários.
Cartão fx_medidor: valor do sensor com barra de progresso
# fx_medidor: valor de um sensor com barra de progresso (consumo, caixa d'água, bateria, CPU...).
# Cole embaixo de "button_card_templates:", junto com os outros modelos fx_.
# Variáveis (todas opcionais): titulo, icone, cor, max (valor que enche a barra), alerta (% que deixa a barra vermelha),
# unidade (se vazio, usa a do sensor) e decimais.
fx_medidor:
variables:
titulo: Medidor
icone: mdi:gauge
cor: '#3cc8ff'
max: 100
alerta: 80
unidade: ''
decimais: 1
show_icon: false
show_name: false
show_state: false
tap_action:
action: more-info
styles:
card:
- padding: 12px 16px
- border-radius: 18px
- background: linear-gradient(145deg, rgba(14,34,66,0.78), rgba(8,18,40,0.78))
- border: 1px solid rgba(60,200,255,0.25)
- box-shadow: 0 8px 30px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.06)
- backdrop-filter: blur(14px)
- -webkit-backdrop-filter: blur(14px)
- color: '#eaf4ff'
grid:
- grid-template-areas: '"topo" "barra"'
- grid-template-columns: 1fr
- row-gap: 10px
custom_fields:
topo:
- justify-self: start
barra:
- justify-self: stretch
- width: 100%
custom_fields:
topo: |
[[[
if (!entity) return `<div style="color:#ff9a9a;font-size:13px;text-align:left">entidade não encontrada<br><b>${(this._config && this._config.entity) || '(sem entity)'}</b></div>`;
const v = parseFloat(entity.state);
const un = variables.unidade || entity.attributes.unit_of_measurement || '';
const fmt = (n) => n.toLocaleString('pt-BR', {minimumFractionDigits: variables.decimais, maximumFractionDigits: variables.decimais});
const txt = isNaN(v) ? 'indisponível' : fmt(v) + (un ? ' ' + un : '');
return `<div style="display:flex;align-items:center;gap:12px;text-align:left">
<ha-icon icon="${variables.icone}" style="--mdc-icon-size:28px;color:${variables.cor};filter:drop-shadow(0 0 6px ${variables.cor}88)"></ha-icon>
<div><div style="font-size:14px;opacity:.8">${variables.titulo}</div>
<div style="font-size:21px;font-weight:700;white-space:nowrap">${txt}</div></div></div>`;
]]]
barra: |
[[[
const v = entity ? parseFloat(entity.state) : NaN;
const p = isNaN(v) ? 0 : Math.max(0, Math.min(100, v / variables.max * 100));
const cor = p >= variables.alerta ? '#ff5a6e' : variables.cor;
return `<div style="height:8px;border-radius:999px;background:rgba(255,255,255,0.08);overflow:hidden">
<div style="width:${p}%;height:100%;border-radius:999px;background:${cor};box-shadow:0 0 10px ${cor}88;transition:width .6s"></div></div>`;
]]]
# Exemplo de uso (num painel, depois de colar o modelo acima):
#
# - type: custom:button-card
# template: fx_medidor
# entity: sensor.consumo_agora
# variables:
# titulo: Consumo agora
# icone: mdi:flash
# cor: '#ffc83c'
# max: 5000
# alerta: 70
# decimais: 0
Home de exemplo do tema Doug Futurista (grid-layout)
# Doug Futurista · exemplo de Home com os cartões fx_*
# https://github.com/dougbaptista/ha_tema_futurista
#
# Precisa de: button-card, layout-card e card-mod (todos no Marketplace) + os modelos de doug-futurista-cards.yaml.
# Troque TODOS os entity_id de exemplo (person.voce, light.sala_teto, ...) pelos da sua casa.
# Cole como uma view: painel > editar > ⋮ > Editor de configuração RAW, dentro de "views:".
#
# Layout: grade com áreas nomeadas. 4 colunas no computador, 2 no tablet (até 1100 px), 1 no celular (até 620 px).
# No layout-card vale a PRIMEIRA mediaquery que casar: por isso a de tela pequena vem antes.
- title: Home
path: home-futurista
icon: mdi:home-lightning-bolt
type: custom:grid-layout
layout:
grid-template-columns: repeat(4, minmax(0, 1fr))
grid-template-areas: |
"hero hero hero hero"
"pills pills pills pills"
"p1 p2 asp amb"
"cot cot luz amb"
"seg seg luz amb"
align-items: start
grid-gap: 0 4px
padding: 8px 12px 40px
mediaquery:
"(max-width: 620px)":
grid-template-columns: minmax(0, 1fr)
grid-template-areas: |
"hero"
"pills"
"p1"
"p2"
"asp"
"cot"
"luz"
"seg"
"amb"
"(max-width: 1100px)":
grid-template-columns: repeat(2, minmax(0, 1fr))
grid-template-areas: |
"hero hero"
"pills pills"
"p1 p2"
"asp cot"
"luz luz"
"seg seg"
"amb amb"
cards:
# ---- faixa do topo: nome da casa, data e hora ----
- type: custom:button-card
view_layout: {grid-area: hero}
entity: sensor.date
show_icon: false
show_name: false
show_state: false
styles:
card:
- padding: 18px 24px
- border-radius: 22px
- background: linear-gradient(145deg, rgba(14,34,66,0.78), rgba(8,18,40,0.78))
- border: 1px solid rgba(60,200,255,0.25)
- color: '#eaf4ff'
grid:
- grid-template-areas: '"casa hora"'
- grid-template-columns: 1fr auto
custom_fields:
casa: [justify-self: start]
hora: [justify-self: end]
custom_fields:
casa: |
[[[ return `<div style="font-size:26px;font-weight:800">Minha casa</div><div style="font-size:14px;color:#9fb0cc">tudo em um lugar</div>`; ]]]
hora: |
[[[
const d = new Date();
const dia = d.toLocaleDateString('pt-BR', {weekday: 'short', day: 'numeric', month: 'short'});
const h = d.toLocaleTimeString('pt-BR', {hour: '2-digit', minute: '2-digit'});
return `<div style="text-align:right"><div style="font-size:14px;color:#9fb0cc">${dia}</div><div style="font-size:34px;font-weight:800">${h}</div></div>`;
]]]
# ---- pílulas: resumo da casa (troque pelos seus sensores de contagem) ----
- type: grid
view_layout: {grid-area: pills}
columns: 4
square: false
cards:
- type: custom:button-card
template: fx_pill
entity: alarm_control_panel.alarme
name: '[[[ return !entity ? "Alarme?" : entity.state === "disarmed" ? "Desarmado" : "Armado"; ]]]'
icon: mdi:shield-home
tap_action: {action: more-info}
- type: custom:button-card
template: fx_pill
name: '[[[ const n = ["light.sala_teto","light.cozinha"].filter(e => states[e] && states[e].state === "on").length; return n + (n === 1 ? " luz" : " luzes"); ]]]'
icon: mdi:lightbulb-group
- type: custom:button-card
template: fx_pill
entity: binary_sensor.porta_entrada
name: '[[[ return !entity ? "Porta?" : entity.state === "on" ? "Porta aberta" : "Porta fechada"; ]]]'
icon: mdi:door
tap_action: {action: more-info}
- type: custom:button-card
template: fx_pill
entity: weather.casa
name: '[[[ return entity ? Math.round(entity.attributes.temperature) + " °C" : "Clima?"; ]]]'
icon: mdi:weather-partly-cloudy
tap_action: {action: more-info}
# ---- pessoas ----
- type: custom:button-card
view_layout: {grid-area: p1}
template: fx_pessoa
entity: person.voce
variables:
foto: /local/fotos/voce.jpg
bateria: sensor.celular_voce_bateria
passos: sensor.celular_voce_passos
- type: custom:button-card
view_layout: {grid-area: p2}
template: fx_pessoa
entity: person.outra_pessoa
variables:
foto: /local/fotos/outra.jpg
bateria: sensor.celular_outra_bateria
passos: sensor.celular_outra_passos
# ---- aspirador ----
- type: custom:button-card
view_layout: {grid-area: asp}
template: fx_aspirador
entity: vacuum.aspirador
variables:
bateria: sensor.aspirador_bateria
area: sensor.aspirador_area_limpa
tempo: sensor.aspirador_tempo_limpeza
# ---- cotações ----
- type: grid
view_layout: {grid-area: cot}
columns: 2
square: false
cards:
- type: custom:button-card
template: fx_cotacao
entity: sensor.dolar
variables: {titulo: Dólar, icone: mdi:currency-usd, cor: '#34e08a'}
- type: custom:button-card
template: fx_cotacao
entity: sensor.gasolina
variables: {titulo: Gasolina, icone: mdi:gas-station, cor: '#ffc83c', decimais: 2}
# ---- luzes (toque liga/desliga, segurar abre detalhes) ----
- type: grid
view_layout: {grid-area: luz}
columns: 2
square: false
cards:
- {type: custom:button-card, template: fx_luz, entity: light.sala_teto, variables: {nome: Sala}}
- {type: custom:button-card, template: fx_luz, entity: light.cozinha, variables: {nome: Cozinha}}
- {type: custom:button-card, template: fx_luz, entity: switch.cafeteira, variables: {nome: Cafeteira, icone: mdi:coffee-maker}}
- {type: custom:button-card, template: fx_luz, entity: light.varanda, variables: {nome: Varanda}}
# ---- segurança ----
- type: grid
view_layout: {grid-area: seg}
columns: 3
square: false
cards:
- {type: custom:button-card, template: fx_sensor, entity: binary_sensor.porta_entrada, variables: {tipo: porta, nome: Entrada}}
- {type: custom:button-card, template: fx_sensor, entity: binary_sensor.janela_sala, variables: {tipo: janela, nome: Janela sala}}
- {type: custom:button-card, template: fx_sensor, entity: binary_sensor.movimento_garagem, variables: {tipo: movimento, nome: Garagem}}
- type: custom:button-card
template: fx_acesso
entity: cover.portao
variables: {tipo: portao, titulo: Portão}
tap_action: {action: toggle}
# ---- ambientes: um por cômodo, com contador de luzes ----
- type: vertical-stack
view_layout: {grid-area: amb}
cards:
- {type: custom:button-card, template: fx_ambiente, variables: {nome: Sala, icone: mdi:sofa, luzes: [light.sala_teto]}, tap_action: {action: navigate, navigation_path: /home/areas-sala}}
- {type: custom:button-card, template: fx_ambiente, variables: {nome: Cozinha, icone: mdi:silverware-fork-knife, luzes: [light.cozinha]}}
- {type: custom:button-card, template: fx_ambiente, variables: {nome: Varanda, icone: mdi:balcony, luzes: [light.varanda]}}
Modelos fx_ do tema Doug Futurista (button-card)
# Doug Futurista · cartões (modelos do custom:button-card)
# https://github.com/dougbaptista/ha_tema_futurista
#
# Precisa do button-card (Marketplace). O tema Doug Futurista é opcional, mas é com ele que fica igual ao vídeo.
#
# Como usar:
# - Painel em modo YAML: button_card_templates: !include doug-futurista-cards.yaml (no topo do painel)
# - Painel pela interface: editar painel > ⋮ > Editor de configuração RAW, e colar este conteúdo
# embaixo de uma linha "button_card_templates:" (com 2 espaços de recuo).
#
# Modelos e variáveis:
# fx_pill pílula do topo (ícone + texto). Use name/icon normais do button-card.
# fx_pessoa pessoa (entity: person.x). variables: foto (url), bateria (sensor %), passos (sensor)
# fx_cotacao valor em R$ com variação. variables: titulo, icone, cor, anterior (sensor do valor anterior), decimais
# fx_aspirador aspirador (entity: vacuum.x) com play/pausa/base. variables: bateria, area, tempo (sensores)
# fx_ambiente linha de cômodo com contador de luzes. variables: nome, icone, luzes (lista de entity_id)
# fx_luz botão de luz ou plugue: toque liga/desliga, segurar abre detalhes. variables: nome, icone (opcionais)
# fx_sensor porta/janela/movimento/pessoa/campainha com "há quanto tempo". variables: tipo, nome
# fx_acesso botão grande (portão, porta, sirene, visitas). variables: tipo (portao|porta|sirene|visitas), titulo
# fx_pilula pílula pequena de um switch, laranja quando ligado. variables: rotulo
fx_pill:
show_icon: true
show_name: true
show_state: false
tap_action:
action: none
styles:
card:
- height: 54px
- border-radius: 999px
- padding: 0 18px
- background: linear-gradient(145deg, rgba(14,34,66,0.78), rgba(8,18,40,0.78))
- border: 1px solid rgba(60,200,255,0.25)
- box-shadow: 0 6px 22px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.06)
- backdrop-filter: blur(12px)
- -webkit-backdrop-filter: blur(12px)
grid:
- grid-template-areas: '"i n"'
- grid-template-columns: 26px 1fr
- column-gap: 10px
- align-items: center
icon:
- width: 24px
name:
- font-size: 15px
- font-weight: 600
- justify-self: start
- color: '#eaf4ff'
- white-space: nowrap
fx_pessoa:
show_icon: false
show_name: false
tap_action:
action: more-info
styles:
card:
- padding: 10px 16px
- min-height: 96px
- box-sizing: border-box
- border-radius: 20px
- background: linear-gradient(145deg, rgba(14,34,66,0.78), rgba(8,18,40,0.78))
- border: 1px solid rgba(60,200,255,0.25)
- box-shadow: 0 8px 30px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.06)
- backdrop-filter: blur(14px)
- -webkit-backdrop-filter: blur(14px)
- color: '#eaf4ff'
grid:
- grid-template-areas: '"foto info"'
- grid-template-columns: 76px 1fr
- column-gap: 14px
- align-items: center
custom_fields:
foto:
- justify-self: start
info:
- justify-self: start
- text-align: left
custom_fields:
foto: |
[[[
if (!entity) return '';
const em = entity.state === 'home';
const cor = em ? '#34e08a' : '#7a8aa6';
return `<div style="width:70px;height:70px;border-radius:50%;padding:3px;background:conic-gradient(${cor},${cor}55,${cor});box-shadow:0 0 18px ${cor}66">
<img src="${variables.foto}" style="width:100%;height:100%;border-radius:50%;object-fit:cover;background:#0b1830;display:block"></div>`;
]]]
info: |
[[[
if (!entity) return `<div style="color:#ff9a9a;font-size:13px;line-height:1.3;white-space:normal">entidade não encontrada<br><b>${(this._config && this._config.entity) || '(sem entity)'}</b></div>`;
const nome = entity.attributes.friendly_name || '';
const e = entity.state === 'home' ? 'Em casa' : entity.state === 'not_home' ? 'Fora' : ['unknown', 'unavailable'].includes(entity.state) ? 'Sem localização' : entity.state;
const cor = entity.state === 'home' ? '#34e08a' : '#9fb0cc';
const b = states[variables.bateria] ? parseFloat(states[variables.bateria].state) : NaN;
const passos = states[variables.passos] ? parseFloat(states[variables.passos].state) : NaN;
const nivel = isNaN(b) ? 'mdi:battery-unknown' : (b >= 95 ? 'mdi:battery' : `mdi:battery-${Math.max(10, Math.round(b / 10) * 10)}`);
const corB = isNaN(b) ? '#9fb0cc' : (b <= 20 ? '#ff5a6e' : b <= 40 ? '#ffc83c' : '#cfe3ff');
const fmt = (n) => Math.round(n).toLocaleString('pt-BR');
return `<div style="font-size:19px;font-weight:700;line-height:1.15">${nome}</div>
<div style="font-size:15px;font-weight:600;color:${cor};margin:1px 0 5px">${e}</div>
<div style="display:flex;gap:16px;font-size:14px;color:#cfe3ff;align-items:center">
<span style="display:flex;align-items:center;gap:4px;color:${corB}"><ha-icon icon="${nivel}" style="--mdc-icon-size:18px"></ha-icon>${isNaN(b) ? '--' : Math.round(b) + '%'}</span>
<span style="display:flex;align-items:center;gap:4px"><ha-icon icon="mdi:shoe-print" style="--mdc-icon-size:18px"></ha-icon>${isNaN(passos) ? '--' : fmt(passos)} passos</span>
</div>`;
]]]
fx_cotacao:
show_icon: false
show_name: false
show_state: false
tap_action:
action: more-info
styles:
card:
- padding: 9px 14px
- border-radius: 18px
- background: linear-gradient(145deg, rgba(14,34,66,0.78), rgba(8,18,40,0.78))
- border: 1px solid rgba(60,200,255,0.25)
- box-shadow: 0 8px 30px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.06)
- backdrop-filter: blur(14px)
- -webkit-backdrop-filter: blur(14px)
- color: '#eaf4ff'
grid:
- grid-template-areas: '"ic info"'
- grid-template-columns: 46px 1fr
- column-gap: 12px
- align-items: center
custom_fields:
ic:
- justify-self: start
info:
- justify-self: start
- text-align: left
custom_fields:
ic: |
[[[ return `<div style="width:40px;height:40px;border-radius:50%;background:${variables.cor}22;border:2px solid ${variables.cor};display:flex;align-items:center;justify-content:center"><ha-icon icon="${variables.icone}" style="color:${variables.cor};--mdc-icon-size:23px"></ha-icon></div>`; ]]]
info: |
[[[
const v = entity ? parseFloat(entity.state) : NaN;
if (isNaN(v)) return `<div style="font-size:15px;opacity:.8">${variables.titulo}</div><div style="font-size:20px">indisponível</div>`;
const a = variables.anterior && states[variables.anterior] ? parseFloat(states[variables.anterior].state) : NaN;
const fmt = (n, d) => n.toLocaleString('pt-BR', {minimumFractionDigits: d, maximumFractionDigits: d});
let delta = '';
if (!isNaN(a) && a > 0) {
const p = (v - a) / a * 100;
const c = p >= 0 ? '#34e08a' : '#ff5a6e';
delta = `<span style="font-size:13px;font-weight:700;color:${c};background:${c}22;padding:2px 8px;border-radius:999px;margin-left:8px;white-space:nowrap">${p >= 0 ? '▲' : '▼'} ${fmt(Math.abs(p), 2)}%</span>`;
}
const d = variables.decimais === undefined ? 2 : variables.decimais;
return `<div style="font-size:15px;opacity:.8">${variables.titulo}</div><div style="font-size:20px;font-weight:700;white-space:nowrap">R$ ${fmt(v, d)}${delta}</div>`;
]]]
fx_aspirador:
show_icon: false
show_name: false
show_state: false
tap_action:
action: more-info
styles:
card:
- padding: 10px 16px
- min-height: 96px
- box-sizing: border-box
- border-radius: 20px
- background: linear-gradient(145deg, rgba(14,34,66,0.78), rgba(8,18,40,0.78))
- border: 1px solid rgba(60,200,255,0.25)
- box-shadow: 0 8px 30px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.06)
- backdrop-filter: blur(14px)
- -webkit-backdrop-filter: blur(14px)
- color: '#eaf4ff'
grid:
- grid-template-areas: '"topo botoes" "infos botoes"'
- grid-template-columns: 1fr auto
- row-gap: 4px
- column-gap: 12px
- align-items: center
custom_fields:
topo:
- justify-self: start
infos:
- justify-self: start
botoes:
- justify-self: end
custom_fields:
topo: |
[[[
const m = {docked: ['Na base', '#9fd8ff'], cleaning: ['Limpando', '#34e08a'], returning: ['Voltando à base', '#ffc83c'], paused: ['Pausado', '#ffc83c'], idle: ['Parado', '#9fb0cc'], error: ['Erro', '#ff5a6e'], unavailable: ['Indisponível', '#ff5a6e']};
if (!entity) return `<div style="display:flex;align-items:center;gap:8px;color:#ff9a9a;font-size:13px;text-align:left"><ha-icon icon="mdi:help-circle-outline" style="--mdc-icon-size:22px;margin:0"></ha-icon><span>entidade não encontrada<br><b>${(this._config && this._config.entity) || '(sem entity)'}</b></span></div>`;
const [t, c] = m[entity.state] || [entity.state, '#9fb0cc'];
return `<div style="display:flex;align-items:center;gap:12px"><ha-icon icon="mdi:robot-vacuum" style="--mdc-icon-size:30px;color:${c}"></ha-icon>
<div style="text-align:left"><div style="font-size:18px;font-weight:700">Aspirador</div><div style="font-size:14px;font-weight:600;color:${c}">${t}</div></div></div>`;
]]]
infos: |
[[[
const b = states[variables.bateria];
const a = states[variables.area];
const t = states[variables.tempo];
const item = (ic, txt) => `<span style="display:flex;align-items:center;gap:4px"><ha-icon icon="${ic}" style="--mdc-icon-size:18px"></ha-icon>${txt}</span>`;
return `<div style="display:flex;gap:10px;font-size:13px;color:#cfe3ff;flex-wrap:nowrap;white-space:nowrap">
${item('mdi:battery', b ? Math.round(parseFloat(b.state)) + '%' : '--')}
${item('mdi:texture-box', a ? a.state + ' m²' : '--')}
${item('mdi:timer-outline', t ? parseInt(t.state) + ' min' : '--')}</div>`;
]]]
botoes:
card:
type: horizontal-stack
cards:
- type: custom:button-card
icon: mdi:play
show_name: false
tap_action: {action: call-service, service: vacuum.start, target: {entity_id: '[[[ return entity ? entity.entity_id : '''' ]]]'}}
styles: &fx_btn
card:
- height: 38px
- width: 40px
- border-radius: 12px
- background: rgba(60,200,255,0.14)
- border: 1px solid rgba(60,200,255,0.3)
- box-shadow: none
icon:
- color: '#9fe3ff'
- width: 24px
- type: custom:button-card
icon: mdi:pause
show_name: false
tap_action: {action: call-service, service: vacuum.pause, target: {entity_id: '[[[ return entity ? entity.entity_id : '''' ]]]'}}
styles: *fx_btn
- type: custom:button-card
icon: mdi:home-import-outline
show_name: false
tap_action: {action: call-service, service: vacuum.return_to_base, target: {entity_id: '[[[ return entity ? entity.entity_id : '''' ]]]'}}
styles: *fx_btn
fx_ambiente:
show_icon: false
show_name: false
show_state: false
tap_action:
action: none
styles:
card:
- background: transparent
- border: none
- border-radius: 0
- box-shadow: none
- padding: 5px 4px
- border-bottom: 1px solid rgba(160,200,255,0.12)
grid:
- grid-template-areas: '"i n b"'
- grid-template-columns: 34px 1fr auto
- column-gap: 12px
- align-items: center
custom_fields:
i:
- justify-self: start
n:
- justify-self: start
b:
- justify-self: end
custom_fields:
i: |
[[[ return `<ha-icon icon="${variables.icone}" style="--mdc-icon-size:22px;color:#6fb8ff"></ha-icon>`; ]]]
n: |
[[[ return `<div style="font-size:15px;font-weight:600;color:#eaf4ff">${variables.nome}</div>`; ]]]
b: |
[[[
const l = variables.luzes || [];
if (!l.length) return '';
const on = l.filter((e) => states[e] && states[e].state === 'on').length;
const cor = on ? '#ffd23f' : '#7a8aa6';
const bg = on ? 'rgba(255,210,63,0.18)' : 'rgba(122,138,166,0.15)';
return `<div style="display:flex;align-items:center;gap:6px;padding:2px 10px;border-radius:999px;background:${bg};color:${cor};font-weight:700;font-size:13px"><ha-icon icon="${on ? 'mdi:lightbulb-on' : 'mdi:lightbulb-outline'}" style="--mdc-icon-size:20px"></ha-icon>${on ? on + '/' + l.length : l.length}</div>`;
]]]
# Botão de uma luz (ou plugue): toque liga/desliga, segurar abre os detalhes. Acesa = dourada com brilho.
fx_luz:
show_icon: false
show_name: false
show_state: false
show_label: true
tap_action:
action: toggle
hold_action:
action: more-info
styles:
card:
- padding: 8px 10px
- min-height: 54px
- box-sizing: border-box
- display: flex
- flex-direction: column
- justify-content: center
- border-radius: 14px
- color: '#eaf4ff'
- background: |
[[[ return entity && entity.state === 'on' ? 'linear-gradient(145deg, rgba(255,200,60,0.26), rgba(255,150,30,0.10))' : 'rgba(255,255,255,0.045)'; ]]]
- border: |
[[[ return entity && entity.state === 'on' ? '1px solid rgba(255,200,60,0.55)' : '1px solid rgba(160,200,255,0.16)'; ]]]
- box-shadow: |
[[[ return entity && entity.state === 'on' ? '0 0 18px rgba(255,190,60,0.25)' : 'none'; ]]]
label: |
[[[
if (!entity) return `<div style="display:flex;align-items:center;gap:8px;color:#ff9a9a;font-size:13px;text-align:left"><ha-icon icon="mdi:help-circle-outline" style="--mdc-icon-size:22px;margin:0"></ha-icon><span>entidade não encontrada<br><b>${(this._config && this._config.entity) || '(sem entity)'}</b></span></div>`;
const on = entity.state === 'on';
const indis = ['unavailable', 'unknown'].includes(entity.state);
const dom = entity.entity_id.split('.')[0];
let nome = variables.nome || entity.attributes.friendly_name || entity.entity_id;
nome = nome.replace(/^Luz /i, '');
const br = entity.attributes.brightness;
const sub = indis ? 'indisponível' : on ? (br ? Math.round(br / 2.55) + '%' : 'ligada') : 'apagada';
const ic = variables.icone || (dom === 'switch' ? 'mdi:power-plug' : (on ? 'mdi:lightbulb-on' : 'mdi:lightbulb-outline'));
const cor = indis ? '#ff7a7a' : on ? '#ffd23f' : '#7f93b3';
const brilho = on ? 'filter:drop-shadow(0 0 8px rgba(255,210,63,0.6));' : '';
return `<div style="display:flex;align-items:center;gap:10px;text-align:left">
<ha-icon icon="${ic}" style="--mdc-icon-size:26px;color:${cor};margin:0;flex:none;${brilho}"></ha-icon>
<div style="min-width:0"><div style="font-size:14px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${nome}</div>
<div style="font-size:12px;opacity:.85;color:${on ? '#ffd23f' : '#9fb0cc'}">${sub}</div></div></div>`;
]]]
# Botão de sensor (porta, janela, movimento, pessoa na câmera): cor e texto conforme o estado e há quanto tempo mudou.
fx_sensor:
show_icon: false
show_name: false
show_state: false
show_label: true
tap_action:
action: more-info
styles:
card:
- padding: 8px 10px
- min-height: 54px
- box-sizing: border-box
- display: flex
- flex-direction: column
- justify-content: center
- border-radius: 14px
- color: '#eaf4ff'
- background: |
[[[
const on = !!entity && ['on', 'open', 'opening'].includes(entity.state);
const cor = variables.tipo === 'pessoa' ? '255,90,110' : variables.tipo === 'movimento' ? '60,200,255' : '255,200,60';
return on ? `linear-gradient(145deg, rgba(${cor},0.26), rgba(${cor},0.08))` : 'rgba(255,255,255,0.045)';
]]]
- border: |
[[[
const on = !!entity && ['on', 'open', 'opening'].includes(entity.state);
const cor = variables.tipo === 'pessoa' ? '255,90,110' : variables.tipo === 'movimento' ? '60,200,255' : '255,200,60';
return on ? `1px solid rgba(${cor},0.6)` : '1px solid rgba(160,200,255,0.16)';
]]]
label: |
[[[
if (!entity) return `<div style="display:flex;align-items:center;gap:8px;color:#ff9a9a;font-size:13px;text-align:left"><ha-icon icon="mdi:help-circle-outline" style="--mdc-icon-size:22px;margin:0"></ha-icon><span>entidade não encontrada<br><b>${(this._config && this._config.entity) || '(sem entity)'}</b></span></div>`;
const on = ['on', 'open', 'opening'].includes(entity.state);
const indis = ['unavailable', 'unknown'].includes(entity.state);
const tipo = variables.tipo || 'porta';
const rel = (iso) => { const m = Math.max(0, Math.round((Date.now() - new Date(iso).getTime()) / 60000)); return m < 1 ? 'agora' : m < 60 ? `há ${m} min` : m < 1440 ? `há ${Math.round(m / 60)} h` : `há ${Math.round(m / 1440)} d`; };
const ic = {porta: on ? 'mdi:door-open' : 'mdi:door-closed', janela: on ? 'mdi:window-open-variant' : 'mdi:window-closed-variant', movimento: on ? 'mdi:motion-sensor' : 'mdi:motion-sensor-off', pessoa: on ? 'mdi:account-alert' : 'mdi:account-outline', campainha: on ? 'mdi:bell-ring' : 'mdi:bell-outline'}[tipo] || 'mdi:circle';
const txt = {porta: on ? 'aberta' : 'fechada', janela: on ? 'aberta' : 'fechada', movimento: on ? 'movimento' : 'parado', pessoa: on ? 'pessoa!' : 'nada', campainha: on ? 'tocando' : 'quieta'}[tipo] || entity.state;
const cor = indis ? '#ff7a7a' : on ? (tipo === 'pessoa' ? '#ff5a6e' : tipo === 'movimento' ? '#3cc8ff' : '#ffc83c') : '#7f93b3';
const nome = variables.nome || (entity.attributes.friendly_name || '').replace(/ (Contact|Contato)$/i, '');
const sub = indis ? 'indisponível' : `${txt} · ${rel(entity.last_changed)}`;
return `<div style="display:flex;align-items:center;gap:10px;text-align:left">
<ha-icon icon="${ic}" style="--mdc-icon-size:26px;color:${cor};margin:0;flex:none;${on ? 'filter:drop-shadow(0 0 7px ' + cor + '99)' : ''}"></ha-icon>
<div style="min-width:0"><div style="font-size:14px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${nome}</div>
<div style="font-size:12px;opacity:.85;color:${on ? cor : '#9fb0cc'};white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${sub}</div></div></div>`;
]]]
# Botão grande de acesso (portão, porta externa, sirene, visitas): mostra o estado e aciona ao tocar (cada um define a ação).
fx_acesso:
show_icon: false
show_name: false
show_state: false
show_label: true
styles:
card:
- padding: 12px 12px
- min-height: 84px
- box-sizing: border-box
- display: flex
- flex-direction: column
- justify-content: center
- border-radius: 18px
- color: '#eaf4ff'
- background: |
[[[ return entity && ['on', 'open', 'opening'].includes(entity.state) ? 'linear-gradient(145deg, rgba(255,200,60,0.22), rgba(255,150,30,0.08))' : 'rgba(255,255,255,0.05)'; ]]]
- border: |
[[[ return entity && ['on', 'open', 'opening'].includes(entity.state) ? '1px solid rgba(255,200,60,0.5)' : '1px solid rgba(60,200,255,0.25)'; ]]]
label: |
[[[
if (!entity) return `<div style="display:flex;align-items:center;gap:8px;color:#ff9a9a;font-size:13px;text-align:left"><ha-icon icon="mdi:help-circle-outline" style="--mdc-icon-size:22px;margin:0"></ha-icon><span>entidade não encontrada<br><b>${(this._config && this._config.entity) || '(sem entity)'}</b></span></div>`;
const s = entity.state;
const aberto = ['on', 'open', 'opening'].includes(s);
const rel = (iso) => { const m = Math.max(0, Math.round((Date.now() - new Date(iso).getTime()) / 60000)); return m < 1 ? 'agora' : m < 60 ? `há ${m} min` : m < 1440 ? `há ${Math.round(m / 60)} h` : `há ${Math.round(m / 1440)} d`; };
const tipo = variables.tipo;
let ic, txt, cor;
if (tipo === 'portao') { ic = aberto ? 'mdi:gate-open' : 'mdi:gate'; txt = aberto ? 'aberto ' + rel(entity.last_changed) : 'fechado'; cor = aberto ? '#ffc83c' : '#9fd8ff'; }
else if (tipo === 'porta') { ic = aberto ? 'mdi:door-open' : 'mdi:door'; txt = aberto ? 'aberta ' + rel(entity.last_changed) : 'fechada'; cor = aberto ? '#ffc83c' : '#9fd8ff'; }
else if (tipo === 'sirene') { ic = aberto ? 'mdi:bullhorn' : 'mdi:bullhorn-outline'; txt = aberto ? 'TOCANDO' : 'desligada'; cor = aberto ? '#ff5a6e' : '#9fd8ff'; }
else { ic = aberto ? 'mdi:account-multiple-check' : 'mdi:account-multiple-outline'; txt = aberto ? 'modo visitas ligado' : 'modo visitas desligado'; cor = aberto ? '#34e08a' : '#9fd8ff'; }
return `<div style="display:flex;flex-direction:column;align-items:center;gap:4px;text-align:center">
<ha-icon icon="${ic}" style="--mdc-icon-size:32px;color:${cor};margin:0;${aberto ? 'filter:drop-shadow(0 0 9px ' + cor + '99)' : ''}"></ha-icon>
<div style="font-size:15px;font-weight:700">${variables.titulo}</div>
<div style="font-size:12px;opacity:.85;color:${cor}">${txt}</div></div>`;
]]]
# Pílula pequena de um switch (ex.: apoio elétrico e circulação do boiler).
# Toque abre os detalhes; segurar liga/desliga. Laranja quando ligado.
fx_pilula:
show_icon: true
show_name: true
show_state: false
tap_action:
action: more-info
hold_action:
action: toggle
name: |
[[[ return `${variables.rotulo} · ${entity && entity.state === 'on' ? 'ligado' : entity && entity.state === 'off' ? 'desligado' : 'indisponível'}`; ]]]
styles:
card:
- padding: 3px 10px 3px 7px
- border-radius: 999px
- background: rgba(255,255,255,0.05)
- border: 1px solid rgba(160,200,255,0.18)
- box-shadow: none
- backdrop-filter: none
- width: auto
grid:
- grid-template-areas: '"i n"'
- grid-template-columns: 16px auto
- column-gap: 6px
icon:
- width: 15px
- color: '#7f93b3'
name:
- font-size: 11.5px
- font-weight: 500
- color: '#9fb0cc'
- white-space: nowrap
state:
- value: 'on'
styles:
card:
- background: rgba(255,150,60,0.13)
- border: 1px solid rgba(255,150,60,0.55)
icon:
- color: '#ff963c'
- filter: drop-shadow(0 0 5px rgba(255,150,60,0.7))
name:
- color: '#ffc08a'
configuration.yaml do tema Doug Futurista e do card-mod
# configuration.yaml: carrega os temas da pasta themes/ e o card-mod como módulo do frontend
# (o card-mod é opcional: sem ele o tema funciona igual, só sem o desfoque de vidro).
# Depois de mudar este bloco, reinicie o Home Assistant uma vez.
frontend:
themes: !include_dir_merge_named themes
extra_module_url:
- /local/community/lovelace-card-mod/card-mod.js
# Para aplicar o tema na casa toda (quem estiver com "Padrão do servidor"),
# rode em Ferramentas de desenvolvedor > Ações:
#
# action: frontend.set_theme
# data:
# name: Doug Futurista
#
# e repita com "mode: dark" para valer também no modo escuro.
0 comentários
Carregando comentários…
Também respondo os comentários no YouTube.
Escolhidos para você
Veja também
15:11VídeoCards Incríveis no Home Assistant: Stack-in-Card, Mushroom & Card-Mod
Personalize sua interface do Home Assistant com Mushroom Template Card e Mushroom Chips! 🚀
11:09VídeoSua casa em 3D no Home Assistant: NeonPlan 3D do zero (grátis)
⚠️ AVISO IMPORTANTE: o Marketplace nativo chega na versão 2026.11 do Home Assistant, com lançamento estável previsto para 4 de novembro de 2026. Gravei este vídeo na versão beta.…
11:33VídeoO jogo do seu time no Home Assistant: horário, onde passa e aviso antes da bola rolar
Que horas é o jogo do seu time? No meu Home Assistant está no painel: data, horário, adversário, estádio, campeonato e onde passa, para qualquer time da Série A e da Série B.…
Canal no YouTube
Toda semana, uma automação nova funcionando de verdade.
Passo a passo em português, sem enrolação, testado aqui em casa.