Add HA Lovelace cards replicating Luma glass UI
CI / Identify a merged version PR (push) Canceled after 0s
CI / Rust checks, rendering, and native package (push) Canceled after 0s
CI / Publish release and Homebrew feed (push) Canceled after 0s
Release PR / Maintain Version Packages PR (push) Canceled after 0s

Custom card bundle (luma-card, luma-weather-card) with slide-in
panels for light/climate/cover/media/vacuum, matching the TV
launcher's design tokens. Includes theme, dashboard YAML for all
8 rooms, and deployment handoff doc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kaloyan DanchevandClaude Opus 4.6 committed 2026-10-27 23:11:43 +02:00
1 parent bde779d759
commit 89ff17a3bc
6 files changed
+1766

No files matched your search

+112
View File
@@ -0,0 +1,112 @@
# Luma HA Cards — Deployment Handoff
## What this is
Custom Lovelace card bundle that replicates the Luma TV launcher's glass UI as a
Home Assistant dashboard. Built in a single session on 2026-10-27.
## Files in `ha-cards/`
| File | Size | What it does |
|------|------|-------------|
| `luma-cards.js` | 43 KB | Main card element (`<luma-card>`) — glass cards with accent glow per domain, brightness bars, curtain SVGs, media artwork. Click opens a slide-in panel (420px, right edge) with full controls for each domain. |
| `luma-weather-card.js` | 7 KB | Weather card (`<luma-weather-card>`) — current conditions + 5-day forecast row. |
| `luma-theme.yaml` | 2.5 KB | HA theme (`luma_glass`) — dark glass palette, gold accent, Inter font. |
| `dashboard.yaml` | 7 KB | Full dashboard YAML — all 8 rooms from `ha-home/config.json`, native HA grid cards, no HACS dependencies. |
| `README.md` | 3 KB | Installation steps (also summarised below). |
## Slide-in panel controls per domain
| Domain | Panel sections |
|--------|---------------|
| **light** | Brightness slider + presets (Off/25/50/100%) + colour temp + colour swatches + effects grid |
| **climate** | Target temp + HVAC mode icons + fan auto (Auto/Quiet/Turbo) + fan manual slider + airflow buttons |
| **cover** | Position slider + Open/Stop/Close + presets (Open/60/45/30/Closed) |
| **media_player** | Now playing (art + title + artist) + progress bar + transport (prev/play-pause/next) + volume |
| **vacuum** | Status + battery + fan mode buttons + Start/Pause/Dock |
| **switch/scene/script** | No panel — click toggles directly |
## Design token mapping
All colours, radii, shadows, and sizes come from `design-system/tokens.json`.
Key values hardcoded in the JS `T` object:
- Card fill: `rgba(34,38,46,0.88)`, border `rgba(255,255,255,0.10)`, radius 22
- Panel fill: `rgba(30,34,42,0.98)`, width 420, radius 28
- Accents: light `#F5B85A`, heat `#F06A4A`, cool `#4C9BF0`, curtain `#DCD2B4`, media `#9B7DDB`, vacuum `#60C090`
## Deployment steps (on Unraid SSH → HA)
### 1. Copy JS + theme files to HA
From Unraid (which has the repo or can pull from Gitea):
```bash
# Clone or pull on Unraid
cd /tmp && git clone https://jazzymc:<password>@git.xtrm-lab.org/jazzymc/luma-home.git 2>/dev/null || (cd /tmp/luma-home && git pull)
# SCP to HA (needs HA SSH addon password or key)
scp -P 22 /tmp/luma-home/ha-cards/luma-cards.js /tmp/luma-home/ha-cards/luma-weather-card.js root@192.168.10.102:/config/www/luma/
# Or if the HA Terminal addon is used, from inside HA:
mkdir -p /config/www/luma
# then pull files via scp from Unraid
```
### 2. Register Lovelace resources
In HA → **Settings → Dashboards → Resources** (top-right ⋮ menu), add:
| URL | Type |
|-----|------|
| `/local/luma/luma-cards.js` | JavaScript Module |
| `/local/luma/luma-weather-card.js` | JavaScript Module |
Or in `configuration.yaml`:
```yaml
lovelace:
resources:
- url: /local/luma/luma-cards.js
type: module
- url: /local/luma/luma-weather-card.js
type: module
```
### 3. Install theme
Copy theme file:
```bash
mkdir -p /config/themes
cp /tmp/luma-home/ha-cards/luma-theme.yaml /config/themes/luma_glass.yaml
```
Or merge into existing themes config. Then reload themes in **Developer Tools → YAML → Themes**.
### 4. Create dashboard
**Settings → Dashboards → Add Dashboard** → set title "Luma Home", icon `mdi:television`.
Switch to YAML mode and paste `dashboard.yaml` contents.
Or add to `configuration.yaml`:
```yaml
lovelace:
dashboards:
luma:
mode: yaml
title: Luma Home
icon: mdi:television
show_in_sidebar: true
filename: dashboards/luma.yaml
```
Then `cp ha-cards/dashboard.yaml /config/dashboards/luma.yaml`.
### 5. Activate theme
Profile (avatar) → Theme → **Luma Glass**.
## What's NOT deployed yet (TV binary)
The Luma TV binary with all 11 fixes from this session was compiled and is at
`/mnt/user/appdata/luma-build/target/armv7-unknown-linux-gnueabi/release/luma` on Unraid
and `/tmp/luma-deploy` on Mac. The TV (192.168.30.124) was offline during this session.
Deploy when it comes back online — see the main project README for the deploy process.
+108
View File
@@ -0,0 +1,108 @@
# Luma Home — HA Lovelace Cards
Custom Lovelace cards that replicate the Luma TV launcher design for Home Assistant.
## Files
| File | Purpose |
|------|---------|
| `luma-cards.js` | Main card bundle — `luma-card` element with slide-in panels |
| `luma-weather-card.js` | Weather card with 5-day forecast |
| `luma-theme.yaml` | HA theme matching the Luma glass aesthetic |
| `dashboard.yaml` | Full dashboard config with all rooms |
## Installation
### 1. Copy card JS files
Copy both JS files to your HA `www/` directory:
```bash
scp ha-cards/luma-cards.js ha-cards/luma-weather-card.js \
root@192.168.10.102:/config/www/luma/
```
Or via the File Editor addon, create `/config/www/luma/` and upload both files.
### 2. Register as resources
Go to **Settings → Dashboards → Resources** (or add to `configuration.yaml`):
```yaml
# configuration.yaml
lovelace:
resources:
- url: /local/luma/luma-cards.js
type: module
- url: /local/luma/luma-weather-card.js
type: module
```
### 3. Install the theme
Add to your `themes:` section in `configuration.yaml`, or merge into an existing themes file:
```yaml
# configuration.yaml
frontend:
themes: !include_dir_merge_named themes/
```
Then copy `luma-theme.yaml` to `/config/themes/luma_glass.yaml`.
### 4. Import the dashboard
Go to **Settings → Dashboards → Add Dashboard**, then switch to YAML mode and paste the contents of `dashboard.yaml`.
Or add it as a separate dashboard in `configuration.yaml`:
```yaml
lovelace:
mode: storage
dashboards:
luma:
mode: yaml
title: Luma Home
icon: mdi:television
show_in_sidebar: true
filename: dashboards/luma.yaml
```
Then copy `dashboard.yaml` to `/config/dashboards/luma.yaml`.
### 5. Activate the theme
Go to your **Profile** (click your avatar) → **Theme** → select **Luma Glass**.
### Dependencies
None — uses only native HA grid cards and custom elements. No HACS cards required.
## Card types
### `luma-card`
General-purpose card for lights, climate, covers, switches, scenes, media, and vacuum.
```yaml
type: custom:luma-card
entity: light.living_room
name: Living Room # optional, defaults to friendly_name
icon: mdi:lightbulb # optional, auto-detected from domain
type_hint: range # optional, helps with icon/accent selection
```
Click opens a slide-in panel with full controls (brightness, color temp, color swatches, presets, effects for lights; target temp, mode, fan speed, airflow for climate; position/presets for covers; transport/volume for media; rooms/mode/controls for vacuum).
Toggle-only entities (switches, scenes, scripts, lights without color modes) toggle on click instead of opening a panel.
### `luma-weather-card`
```yaml
type: custom:luma-weather-card
entity: weather.forecast_home
```
## Customization
All colors are defined in the JS files' `T` (tokens) object and in the theme YAML. Adjust there to match your preference.
+248
View File
@@ -0,0 +1,248 @@
title: Luma Home
views:
# ── Home ──────────────────────────────────────────────
- title: Home
path: home
cards:
- type: grid
columns: 3
square: false
cards:
- type: custom:luma-weather-card
entity: weather.forecast_home
- type: custom:luma-card
entity: media_player.music_assistant
name: Music
icon: mdi:music
type_hint: media
- type: custom:luma-card
entity: vacuum.living_room_roborock_s7_pro_ultra
name: Roborock
icon: mdi:robot-vacuum
type_hint: vacuum
# ── Living Area ───────────────────────────────────────
- title: Living Area
path: living-area
cards:
- type: grid
columns: 3
square: false
cards:
# Climate
- type: custom:luma-card
entity: climate.living_room_thermostat
name: Thermostat
- type: custom:luma-card
entity: climate.living_room_radiator
name: Radiator
# Lights
- type: custom:luma-card
entity: light.living_room_light
name: Ceiling Light
- type: custom:luma-card
entity: light.tv_lights
name: TV Lights
- type: custom:luma-card
entity: light.rgbic_strip_lights_z1
name: LED Strip 1
- type: custom:luma-card
entity: light.rgbic_strip_lights_z2
name: LED Strip 2
- type: custom:luma-card
entity: light.picture_frame_lamp
name: Picture Lamp
# Curtain
- type: custom:luma-card
entity: cover.living_room_curtain
name: Curtain
icon: mdi:curtains
type_hint: curtain
# More lights
- type: custom:luma-card
entity: light.dining_room_light
name: Dining Light
- type: custom:luma-card
entity: light.entrance_light_1
name: Entrance
- type: custom:luma-card
entity: light.entrance_mirror_light
name: Mirror
# Scenes & Actions row
- type: grid
columns: 4
square: false
cards:
- type: custom:luma-card
entity: scene.tv_mode
name: TV Mode
icon: mdi:television
type_hint: action
- type: custom:luma-card
entity: scene.living_room_100
name: "100%"
icon: mdi:brightness-7
type_hint: action
- type: custom:luma-card
entity: scene.living_room_lights_10
name: "10%"
icon: mdi:brightness-4
type_hint: action
- type: custom:luma-card
entity: script.tv_ambient_on
name: Ambient On
icon: mdi:led-strip-variant
type_hint: action
- type: custom:luma-card
entity: script.tv_ambient_off
name: Ambient Off
icon: mdi:led-strip-variant
type_hint: action
- type: custom:luma-card
entity: script.living_dining_all_off
name: All Off
icon: mdi:lightbulb-off
type_hint: action
- type: custom:luma-card
entity: scene.curtains_close
name: Close Curtains
icon: mdi:curtains-closed
type_hint: action
# ── Kitchen ───────────────────────────────────────────
- title: Kitchen
path: kitchen
cards:
- type: grid
columns: 3
square: false
cards:
- type: custom:luma-card
entity: climate.kitchen_termostat
name: Thermostat
- type: custom:luma-card
entity: switch.dishwasher_power
name: Dishwasher
icon: mdi:dishwasher
# ── Dad's Bedroom ─────────────────────────────────────
- title: Dad's Bedroom
path: bedroom
cards:
- type: grid
columns: 3
square: false
cards:
- type: custom:luma-card
entity: climate.main_bedroom_radiator
name: Radiator
- type: custom:luma-card
entity: light.bedroom_ceiling_light
name: Ceiling
- type: custom:luma-card
entity: light.bedroom_desk_lights
name: Desk
- type: custom:luma-card
entity: light.bedroom_led_strip
name: LED Strip
- type: custom:luma-card
entity: scene.desk_mac
name: "Desk: Mac"
icon: mdi:apple
type_hint: action
- type: custom:luma-card
entity: scene.desk_pc
name: "Desk: PC"
icon: mdi:desktop-tower-monitor
type_hint: action
# ── Dari's Bedroom ────────────────────────────────────
- title: Dari's Bedroom
path: dari-bedroom
cards:
- type: grid
columns: 3
square: false
cards:
- type: custom:luma-card
entity: climate.girls_bedroom_radiator
name: Radiator
# ── Teo's Bedroom ─────────────────────────────────────
- title: Teo's Bedroom
path: teo-bedroom
cards:
- type: grid
columns: 3
square: false
cards:
- type: custom:luma-card
entity: light.boys_smartlight
name: Light
# ── Main Bathroom ─────────────────────────────────────
- title: Main Bathroom
path: main-bathroom
cards:
- type: grid
columns: 3
square: false
cards:
- type: custom:luma-card
entity: light.bathroom_light_1
name: Light 1
- type: custom:luma-card
entity: light.bathroom_light_2
name: Light 2
# ── Kids Bathroom ─────────────────────────────────────
- title: Kids Bathroom
path: kids-bathroom
cards:
- type: grid
columns: 3
square: false
cards:
- type: custom:luma-card
entity: light.kids_bathroom_light_1
name: Light 1
- type: custom:luma-card
entity: light.kids_bathroom_light_2
name: Light 2
- type: custom:luma-card
entity: switch.washing_machine_power
name: Washing Machine
icon: mdi:washing-machine
- type: custom:luma-card
entity: switch.dryer_power
name: Dryer
icon: mdi:tumble-dryer
File diff suppressed because it is too large. Load diff
+78
View File
@@ -0,0 +1,78 @@
luma_glass:
# ── Background ──
lovelace-background: "linear-gradient(135deg, #080B12 0%, #11141C 50%, #080B12 100%)"
primary-background-color: "#0D1017"
secondary-background-color: "#11141C"
card-background-color: "rgba(34,38,46,0.88)"
# ── Text ──
primary-text-color: "#FFFFFF"
secondary-text-color: "rgba(255,255,255,0.72)"
text-primary-color: "#FFFFFF"
disabled-text-color: "rgba(255,255,255,0.40)"
# ── Accent ──
primary-color: "#F5B85A"
accent-color: "#F5B85A"
light-primary-color: "rgba(245,184,90,0.24)"
# ── Cards ──
ha-card-background: "rgba(34,38,46,0.88)"
ha-card-border-color: "rgba(255,255,255,0.10)"
ha-card-border-radius: "22px"
ha-card-box-shadow: "0 2px 8px rgba(0,0,0,0.25)"
ha-card-header-color: "#FFFFFF"
ha-card-header-font-size: "19px"
# ── Sidebar ──
sidebar-background-color: "#0D1017"
sidebar-text-color: "#FFFFFF"
sidebar-icon-color: "rgba(255,255,255,0.60)"
sidebar-selected-icon-color: "#F5B85A"
sidebar-selected-text-color: "#F5B85A"
# ── Header ──
app-header-background-color: "rgba(13,16,23,0.95)"
app-header-text-color: "#FFFFFF"
# ── Dividers ──
divider-color: "rgba(255,255,255,0.10)"
# ── Inputs / controls ──
input-fill-color: "rgba(255,255,255,0.06)"
input-ink-color: "#FFFFFF"
input-label-ink-color: "rgba(255,255,255,0.60)"
input-idle-line-color: "rgba(255,255,255,0.20)"
input-hover-line-color: "rgba(255,255,255,0.40)"
input-dropdown-icon-color: "rgba(255,255,255,0.60)"
# ── Toggles ──
switch-checked-color: "#F5B85A"
switch-checked-track-color: "rgba(245,184,90,0.40)"
switch-unchecked-button-color: "rgba(255,255,255,0.60)"
switch-unchecked-track-color: "rgba(255,255,255,0.20)"
# ── Sliders ──
paper-slider-knob-color: "#F5B85A"
paper-slider-active-color: "#F5B85A"
paper-slider-container-color: "rgba(255,255,255,0.14)"
# ── Badges / chips ──
label-badge-background-color: "#1E222A"
label-badge-text-color: "#FFFFFF"
# ── Scrollbar ──
scrollbar-thumb-color: "rgba(255,255,255,0.20)"
# ── Dialogs / popups ──
dialog-backdrop-color: "rgba(0,0,0,0.45)"
# ── Misc ──
state-icon-color: "rgba(255,255,255,0.60)"
state-icon-active-color: "#F5B85A"
paper-item-icon-color: "rgba(255,255,255,0.60)"
paper-item-icon-active-color: "#F5B85A"
mdc-theme-primary: "#F5B85A"
# ── Font ──
primary-font-family: "'Inter', system-ui, -apple-system, sans-serif"
+207
View File
@@ -0,0 +1,207 @@
/**
* Luma Weather Card — custom Lovelace card
* Shows current conditions + 5-day forecast in the Luma glass style.
*/
const WEATHER_VERSION = '1.0.0';
const WEATHER_ICONS = {
'clear-night': 'mdi:weather-night',
'cloudy': 'mdi:weather-cloudy',
'fog': 'mdi:weather-fog',
'hail': 'mdi:weather-hail',
'lightning': 'mdi:weather-lightning',
'lightning-rainy': 'mdi:weather-lightning-rainy',
'partlycloudy': 'mdi:weather-partly-cloudy',
'pouring': 'mdi:weather-pouring',
'rainy': 'mdi:weather-rainy',
'snowy': 'mdi:weather-snowy',
'snowy-rainy': 'mdi:weather-snowy-rainy',
'sunny': 'mdi:weather-sunny',
'windy': 'mdi:weather-windy',
'windy-variant': 'mdi:weather-windy-variant',
'exceptional': 'mdi:alert-circle-outline',
};
const CONDITION_LABELS = {
'clear-night': 'Clear Night',
'cloudy': 'Cloudy',
'fog': 'Foggy',
'hail': 'Hail',
'lightning': 'Thunderstorm',
'lightning-rainy': 'Thunderstorm',
'partlycloudy': 'Partly Cloudy',
'pouring': 'Heavy Rain',
'rainy': 'Rainy',
'snowy': 'Snowy',
'snowy-rainy': 'Sleet',
'sunny': 'Sunny',
'windy': 'Windy',
'windy-variant': 'Windy',
'exceptional': 'Exceptional',
};
class LumaWeatherCard extends HTMLElement {
set hass(hass) {
this._hass = hass;
if (!this._config) return;
this._render();
}
setConfig(config) {
if (!config.entity) throw new Error('Missing entity');
this._config = config;
}
_render() {
const hass = this._hass;
const config = this._config;
const stateObj = hass.states[config.entity];
if (!stateObj) {
this.innerHTML = `<ha-card><div style="padding:16px;color:#888">Entity not found: ${config.entity}</div></ha-card>`;
return;
}
const condition = stateObj.state;
const temp = stateObj.attributes.temperature;
const unit = stateObj.attributes.temperature_unit || '°C';
const humidity = stateObj.attributes.humidity;
const windSpeed = stateObj.attributes.wind_speed;
const windUnit = stateObj.attributes.wind_speed_unit || 'km/h';
const icon = WEATHER_ICONS[condition] || 'mdi:weather-partly-cloudy';
const label = CONDITION_LABELS[condition] || condition;
const forecast = stateObj.attributes.forecast || [];
if (!this.shadowRoot) {
this.attachShadow({ mode: 'open' });
const style = document.createElement('style');
style.textContent = WEATHER_STYLES;
this.shadowRoot.appendChild(style);
const wrap = document.createElement('div');
wrap.className = 'lw-wrap';
this.shadowRoot.appendChild(wrap);
}
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const forecastDays = forecast.slice(0, 5);
this.shadowRoot.querySelector('.lw-wrap').innerHTML = `
<div class="lw-card">
<div class="lw-now">
<div class="lw-icon-wrap">
<ha-icon icon="${icon}" style="--mdc-icon-size:56px;color:#F5B85A"></ha-icon>
</div>
<div class="lw-now-info">
<div class="lw-temp">${temp !== undefined ? Math.round(temp) : '–'}°</div>
<div class="lw-condition">${label}</div>
</div>
</div>
<div class="lw-details">
<div class="lw-detail">
<ha-icon icon="mdi:water-percent" style="--mdc-icon-size:20px;color:rgba(255,255,255,0.60)"></ha-icon>
<span>${humidity !== undefined ? humidity + '%' : '–'}</span>
</div>
<div class="lw-detail">
<ha-icon icon="mdi:weather-windy" style="--mdc-icon-size:20px;color:rgba(255,255,255,0.60)"></ha-icon>
<span>${windSpeed !== undefined ? windSpeed + ' ' + windUnit : '–'}</span>
</div>
</div>
${forecastDays.length > 0 ? `
<div class="lw-divider"></div>
<div class="lw-forecast">
${forecastDays.map(f => {
const d = new Date(f.datetime);
const day = days[d.getDay()];
const fIcon = WEATHER_ICONS[f.condition] || 'mdi:weather-partly-cloudy';
const lo = f.templow !== undefined ? Math.round(f.templow) : '–';
const hi = f.temperature !== undefined ? Math.round(f.temperature) : '–';
return `
<div class="lw-day">
<div class="lw-day-name">${day}</div>
<ha-icon icon="${fIcon}" style="--mdc-icon-size:24px;color:rgba(255,255,255,0.72)"></ha-icon>
<div class="lw-day-range">
<span class="lw-hi">${hi}°</span>
<span class="lw-lo">${lo}°</span>
</div>
</div>
`;
}).join('')}
</div>
` : ''}
</div>
`;
}
getCardSize() { return 4; }
static getStubConfig() { return { entity: 'weather.forecast_home' }; }
}
const WEATHER_STYLES = `
:host { display: block; }
.lw-card {
background: rgba(34,38,46,0.88);
border: 2px solid rgba(255,255,255,0.10);
border-radius: 22px;
padding: 22px;
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
box-sizing: border-box;
}
.lw-now {
display: flex; align-items: center; gap: 16px;
margin-bottom: 16px;
}
.lw-icon-wrap {
width: 72px; height: 72px; border-radius: 50%;
background: rgba(245,184,90,0.18);
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
.lw-now-info { flex: 1; }
.lw-temp {
font-size: 48px; font-weight: 700; color: #fff;
line-height: 1; letter-spacing: -0.03em;
}
.lw-condition {
font-size: 17px; font-weight: 500; color: rgba(255,255,255,0.72);
margin-top: 4px;
}
.lw-details {
display: flex; gap: 20px; margin-bottom: 16px;
}
.lw-detail {
display: flex; align-items: center; gap: 6px;
font-size: 15px; font-weight: 500; color: rgba(255,255,255,0.72);
}
.lw-divider {
height: 1px; background: rgba(255,255,255,0.10);
margin-bottom: 16px;
}
.lw-forecast {
display: flex; justify-content: space-between;
}
.lw-day {
display: flex; flex-direction: column; align-items: center; gap: 6px;
flex: 1;
}
.lw-day-name {
font-size: 13px; font-weight: 600; color: rgba(255,255,255,0.60);
text-transform: uppercase; letter-spacing: 0.04em;
}
.lw-day-range {
display: flex; flex-direction: column; align-items: center; gap: 2px;
}
.lw-hi { font-size: 16px; font-weight: 700; color: #fff; }
.lw-lo { font-size: 13px; font-weight: 500; color: rgba(255,255,255,0.50); }
`;
customElements.define('luma-weather-card', LumaWeatherCard);
window.customCards = window.customCards || [];
window.customCards.push({
type: 'luma-weather-card',
name: 'Luma Weather Card',
description: 'Weather card with 5-day forecast in the Luma glass design.',
preview: true,
});
console.info(`%c LUMA-WEATHER %c v${WEATHER_VERSION} `, 'background:#F5B85A;color:#15181E;font-weight:700', 'background:#22262E;color:#F5B85A');