feat v1.42.1: room-card font sizes — 3 sliders with a live preview

- space.settings.card_font_scale (tier 2 base) + room.settings
  name_scale/label_scale (tier 3), 50-300%, multiplied together and
  stacking with resize-k and kiosk per-screen multipliers
- live sample card in both the space and room dialogs
- backend schema; smoke_font_scales.mjs (9 checks); docs same-commit
This commit is contained in:
Matysh
2026-07-27 10:12:22 +03:00
parent 19e19b5c5f
commit 10d4084d17
16 changed files with 404 additions and 70 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ PLANS_DIR = "houseplan/plans" # relative to the HA configuration directory
FILES_URL = "/houseplan_files/files" FILES_URL = "/houseplan_files/files"
FILES_DIR = "houseplan/files" FILES_DIR = "houseplan/files"
CONF_ADMIN_ONLY = "admin_only" CONF_ADMIN_ONLY = "admin_only"
VERSION = "1.42.0" VERSION = "1.42.1"
DEFAULT_CONFIG: dict = { DEFAULT_CONFIG: dict = {
"spaces": [], "spaces": [],
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -16,5 +16,5 @@
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues", "issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
"requirements": [], "requirements": [],
"single_config_entry": true, "single_config_entry": true,
"version": "1.42.0" "version": "1.42.1"
} }
@@ -76,6 +76,8 @@ ROOM_SCHEMA = vol.All(
vol.Optional("fill_mode"): vol.Any(None, vol.In(["none", "lqi", "light", "temp"])), vol.Optional("fill_mode"): vol.Any(None, vol.In(["none", "lqi", "light", "temp"])),
vol.Optional("temp_source"): vol.Any(str, None), vol.Optional("temp_source"): vol.Any(str, None),
vol.Optional("hum_source"): vol.Any(str, None), vol.Optional("hum_source"): vol.Any(str, None),
vol.Optional("name_scale"): vol.Any(None, vol.All(vol.Coerce(float), vol.Range(min=0.5, max=3))),
vol.Optional("label_scale"): vol.Any(None, vol.All(vol.Coerce(float), vol.Range(min=0.5, max=3))),
}, },
extra=vol.ALLOW_EXTRA, extra=vol.ALLOW_EXTRA,
), ),
@@ -104,6 +106,7 @@ SPACE_DISPLAY_SCHEMA = vol.Schema(
vol.Optional("label_hum"): bool, vol.Optional("label_hum"): bool,
vol.Optional("label_lqi"): bool, vol.Optional("label_lqi"): bool,
vol.Optional("label_light"): bool, vol.Optional("label_light"): bool,
vol.Optional("card_font_scale"): vol.All(vol.Coerce(float), vol.Range(min=0.5, max=3)),
}, },
extra=vol.ALLOW_EXTRA, extra=vol.ALLOW_EXTRA,
) )
+58
View File
@@ -0,0 +1,58 @@
import { launch } from './serve.mjs';
const { page, browser } = await launch();
const res = await page.evaluate(async () => {
const out = {};
const c = window.__card;
const sr = () => c.shadowRoot || c.renderRoot;
const spId = c._space;
c._serverCfg = { ...c._serverCfg, spaces: c._serverCfg.spaces.map((s) => s.id !== spId ? s : ({
...s, settings: { ...(s.settings || {}), show_names: true, label_temp: true } })) };
c._setMode('view'); c.requestUpdate(); await c.updateComplete;
await new Promise((r) => setTimeout(r, 100));
const lbl = () => sr().querySelector('.roomlabel');
const nameSize = () => parseFloat(getComputedStyle(lbl().querySelector('.rlname')).fontSize);
const metaEl = () => lbl().querySelector('.rlmetrics');
const n0 = nameSize();
const m0 = metaEl() ? parseFloat(getComputedStyle(metaEl()).fontSize) : null;
out.baseline = n0 > 0;
// 1) пер-комнатный name_scale ×2 — имя удвоилось, подписи нет
const room = c._curSpaceCfg.rooms.find((r) => r.name && r.area);
room.settings = { ...(room.settings || {}), name_scale: 2 };
c.requestUpdate(); await c.updateComplete;
const lblR = [...sr().querySelectorAll('.roomlabel')].find((l) => l.textContent.includes(room.name));
const n1 = parseFloat(getComputedStyle(lblR.querySelector('.rlname')).fontSize);
out.nameDoubled = Math.abs(n1 / n0 - 2) < 0.05;
const meta1 = lblR.querySelector('.rlmetrics');
out.metaUntouched = meta1 ? Math.abs(parseFloat(getComputedStyle(meta1).fontSize) - m0) < 0.5 : 'no-meta';
// 2) label_scale ×2 — подписи удвоились
room.settings.label_scale = 2;
c.requestUpdate(); await c.updateComplete;
const meta2 = lblR.querySelector('.rlmetrics');
out.metaDoubled = meta2 && m0 ? Math.abs(parseFloat(getComputedStyle(meta2).fontSize) / m0 - 2) < 0.05 : 'no-meta';
// 3) базовый множитель пространства ×1.5 — умножает всё
c._serverCfg = { ...c._serverCfg, spaces: c._serverCfg.spaces.map((s) => s.id !== spId ? s : ({
...s, settings: { ...(s.settings || {}), card_font_scale: 1.5 } })) };
c.requestUpdate(); await c.updateComplete;
const lblR2 = [...sr().querySelectorAll('.roomlabel')].find((l) => l.textContent.includes(room.name));
const n2 = parseFloat(getComputedStyle(lblR2.querySelector('.rlname')).fontSize);
out.spaceMultiplies = Math.abs(n2 / n0 - 3) < 0.1; // 2 × 1.5
// 4) диалог комнаты: слайдеры + живой пример реагирует
c._setMode('plan'); await c.updateComplete;
const model = c._spaceModel().rooms.find((r) => r.id === room.id);
c._openRoomEdit(model); await c.updateComplete;
out.slidersPrefilled = c._roomNameScale === 2 && c._roomLabelScale === 2;
const pv = () => sr().querySelector('.cardpreview .cpname');
const p0 = parseFloat(pv().style.fontSize);
c._roomNameScale = 1; c.requestUpdate(); await c.updateComplete;
const p1 = parseFloat(pv().style.fontSize);
out.previewLive = Math.abs(p0 / p1 - 2) < 0.05;
c._roomDialogCancel(); await c.updateComplete;
// 5) превью есть и в диалоге пространства
c._openSpaceDialog('edit', spId); await c.updateComplete;
out.spacePreview = !!sr().querySelector('.cardpreview');
out.spaceSlider = [...sr().querySelectorAll('.dialog label')].some((l) => l.textContent === c._t('space.card_font'));
c._spaceDialog = null; await c.updateComplete;
return out;
});
console.log(JSON.stringify(res, null, 1));
await browser.close();
File diff suppressed because one or more lines are too long
+74 -20
View File
File diff suppressed because one or more lines are too long
+9
View File
@@ -1,5 +1,14 @@
# Changelog # Changelog
## v1.42.1 — 2026-07-26 (room-card font sizes)
- Closing the "can't change the font size" feedback: **three sliders**.
Space settings gained a base room-card font size for the whole space;
Room settings gained independent sizes for the room NAME and the METRICS
line (50300% each). Effects multiply — and stack with the card's corner
resize and the kiosk per-screen multiplier as before.
- Both dialogs show a **live sample card** that follows the sliders as you
drag them.
## v1.42.0 — 2026-07-26 (room settings — the third tier) ## v1.42.0 — 2026-07-26 (room settings — the third tier)
- **Settings now have four tiers**: global → space → room → device; the more - **Settings now have four tiers**: global → space → room → device; the more
specific tier overrides the more general one (owner's principle, fixed in specific tier overrides the more general one (owner's principle, fixed in
+5
View File
@@ -140,6 +140,11 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi
(explicit ripple color still wins); off/white lights unchanged [auto] (explicit ripple color still wins); off/white lights unchanged [auto]
- [ ] Alarm pulse (v1.27.0): leak/smoke/gas/CO/siren in 'on' pulse a red ring over any - [ ] Alarm pulse (v1.27.0): leak/smoke/gas/CO/siren in 'on' pulse a red ring over any
display mode; clears on 'off'; unavailable never alarms [auto]; reduced-motion static display mode; clears on 'off'; unavailable never alarms [auto]; reduced-motion static
- [ ] Card font scales (v1.42.1): three sliders — space-level base (space
dialog) plus per-room name and metrics sizes (room settings), 50300%,
multiplied together and on top of resize-k and kiosk multipliers; the
live sample card in both dialogs follows the sliders instantly; name and
metrics scale independently [auto]
- [ ] Room settings, tier 3 (v1.42.0): a gear on every room card in the Plan - [ ] Room settings, tier 3 (v1.42.0): a gear on every room card in the Plan
editor opens Room settings (name, HA area incl. the current one, fill editor opens Room settings (name, HA area incl. the current one, fill
override, temp/hum source); the creation dialog has the same section; override, temp/hum source); the creation dialog has the same section;
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "houseplan-card", "name": "houseplan-card",
"version": "1.42.0", "version": "1.42.1",
"description": "Interactive house plan Lovelace card for Home Assistant", "description": "Interactive house plan Lovelace card for Home Assistant",
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
+57 -2
View File
@@ -32,7 +32,7 @@ import './space-card';
import { cardStyles } from './styles'; import { cardStyles } from './styles';
import { langOf, t, type I18nKey } from './i18n'; import { langOf, t, type I18nKey } from './i18n';
const CARD_VERSION = '1.42.0'; const CARD_VERSION = '1.42.1';
const LS_KEY = 'houseplan_card_layout_v1'; const LS_KEY = 'houseplan_card_layout_v1';
const LS_CFG = 'houseplan_card_cfg_v1'; // cache of the server config+layout for instant rendering const LS_CFG = 'houseplan_card_cfg_v1'; // cache of the server config+layout for instant rendering
const LS_ZOOM = 'houseplan_card_zoom_v1'; const LS_ZOOM = 'houseplan_card_zoom_v1';
@@ -163,6 +163,8 @@ class HouseplanCard extends LitElement {
private _roomHumSrc = ''; private _roomHumSrc = '';
private _roomSrcOpen: 'temp' | 'hum' | null = null; private _roomSrcOpen: 'temp' | 'hum' | null = null;
private _roomSrcFilter = ''; private _roomSrcFilter = '';
private _roomNameScale = 1;
private _roomLabelScale = 1;
// plan zoom/pan (zoom is saved per space, locally) // plan zoom/pan (zoom is saved per space, locally)
private _zoom = 1; private _zoom = 1;
private _view: { x: number; y: number; w: number; h: number } | null = null; // current SVG viewBox (vb coordinates) private _view: { x: number; y: number; w: number; h: number } | null = null; // current SVG viewBox (vb coordinates)
@@ -226,6 +228,7 @@ class HouseplanCard extends LitElement {
tempMin: number; tempMin: number;
tempMax: number; tempMax: number;
showLqi: boolean; showLqi: boolean;
cardFontScale: number;
labelTemp: boolean; labelTemp: boolean;
labelHum: boolean; labelHum: boolean;
labelLqi: boolean; labelLqi: boolean;
@@ -301,6 +304,8 @@ class HouseplanCard extends LitElement {
_roomHumSrc: { state: true }, _roomHumSrc: { state: true },
_roomSrcOpen: { state: true }, _roomSrcOpen: { state: true },
_roomSrcFilter: { state: true }, _roomSrcFilter: { state: true },
_roomNameScale: { state: true },
_roomLabelScale: { state: true },
_spaceDialog: { state: true }, _spaceDialog: { state: true },
_infoCard: { state: true }, _infoCard: { state: true },
_rulesDialog: { state: true }, _rulesDialog: { state: true },
@@ -2629,6 +2634,7 @@ class HouseplanCard extends LitElement {
roomColor: disp.color, roomOpacity: disp.opacity, fillMode: disp.fill, roomColor: disp.color, roomOpacity: disp.opacity, fillMode: disp.fill,
tempMin: disp.tempMin, tempMax: disp.tempMax, tempMin: disp.tempMin, tempMax: disp.tempMax,
showLqi: disp.showLqi ?? this._config?.show_signal ?? true, showLqi: disp.showLqi ?? this._config?.show_signal ?? true,
cardFontScale: disp.cardFontScale,
labelTemp: disp.labelTemp, labelHum: disp.labelHum, labelTemp: disp.labelTemp, labelHum: disp.labelHum,
labelLqi: disp.labelLqi, labelLight: disp.labelLight, labelLqi: disp.labelLqi, labelLight: disp.labelLight,
cellCm: Number(sp.cell_cm) > 0 ? Number(sp.cell_cm) : 5, cellCm: Number(sp.cell_cm) > 0 ? Number(sp.cell_cm) : 5,
@@ -2642,6 +2648,7 @@ class HouseplanCard extends LitElement {
roomColor: DEFAULT_ROOM_COLOR, roomOpacity: DEFAULT_ROOM_OPACITY, fillMode: 'none', roomColor: DEFAULT_ROOM_COLOR, roomOpacity: DEFAULT_ROOM_OPACITY, fillMode: 'none',
tempMin: DEFAULT_TEMP_MIN, tempMax: DEFAULT_TEMP_MAX, tempMin: DEFAULT_TEMP_MIN, tempMax: DEFAULT_TEMP_MAX,
showLqi: this._config?.show_signal ?? true, showLqi: this._config?.show_signal ?? true,
cardFontScale: 1,
labelTemp: false, labelHum: false, labelLqi: false, labelLight: false, labelTemp: false, labelHum: false, labelLqi: false, labelLight: false,
cellCm: 5, cellCm: 5,
busy: false, busy: false,
@@ -2727,6 +2734,7 @@ class HouseplanCard extends LitElement {
temp_min: Number.isFinite(d.tempMin) ? Math.min(d.tempMin, d.tempMax) : DEFAULT_TEMP_MIN, temp_min: Number.isFinite(d.tempMin) ? Math.min(d.tempMin, d.tempMax) : DEFAULT_TEMP_MIN,
temp_max: Number.isFinite(d.tempMax) ? Math.max(d.tempMin, d.tempMax) : DEFAULT_TEMP_MAX, temp_max: Number.isFinite(d.tempMax) ? Math.max(d.tempMin, d.tempMax) : DEFAULT_TEMP_MAX,
show_lqi: d.showLqi, show_lqi: d.showLqi,
card_font_scale: d.cardFontScale !== 1 ? d.cardFontScale : undefined,
label_temp: d.labelTemp, label_temp: d.labelTemp,
label_hum: d.labelHum, label_hum: d.labelHum,
label_lqi: d.labelLqi, label_lqi: d.labelLqi,
@@ -2823,6 +2831,7 @@ class HouseplanCard extends LitElement {
roomColor: DEFAULT_ROOM_COLOR, roomOpacity: DEFAULT_ROOM_OPACITY, fillMode: 'none', roomColor: DEFAULT_ROOM_COLOR, roomOpacity: DEFAULT_ROOM_OPACITY, fillMode: 'none',
tempMin: DEFAULT_TEMP_MIN, tempMax: DEFAULT_TEMP_MAX, tempMin: DEFAULT_TEMP_MIN, tempMax: DEFAULT_TEMP_MAX,
showLqi: this._config?.show_signal ?? true, showLqi: this._config?.show_signal ?? true,
cardFontScale: 1,
labelTemp: false, labelHum: false, labelLqi: false, labelLight: false, labelTemp: false, labelHum: false, labelLqi: false, labelLight: false,
cellCm: 5, cellCm: 5,
busy: false, busy: false,
@@ -3562,6 +3571,8 @@ class HouseplanCard extends LitElement {
this._roomHumSrc = ''; this._roomHumSrc = '';
this._roomSrcOpen = null; this._roomSrcOpen = null;
this._roomSrcFilter = ''; this._roomSrcFilter = '';
this._roomNameScale = 1;
this._roomLabelScale = 1;
} }
/** Open the room dialog for an EXISTING room (the gear on its card). */ /** Open the room dialog for an EXISTING room (the gear on its card). */
@@ -3573,6 +3584,8 @@ class HouseplanCard extends LitElement {
this._roomFill = (r.settings?.fill_mode as any) || ''; this._roomFill = (r.settings?.fill_mode as any) || '';
this._roomTempSrc = r.settings?.temp_source || ''; this._roomTempSrc = r.settings?.temp_source || '';
this._roomHumSrc = r.settings?.hum_source || ''; this._roomHumSrc = r.settings?.hum_source || '';
this._roomNameScale = clampScale(r.settings?.name_scale);
this._roomLabelScale = clampScale(r.settings?.label_scale);
this._roomSrcOpen = null; this._roomSrcOpen = null;
this._roomSrcFilter = ''; this._roomSrcFilter = '';
this._roomDialog = true; this._roomDialog = true;
@@ -3584,6 +3597,8 @@ class HouseplanCard extends LitElement {
if (this._roomFill) st.fill_mode = this._roomFill; if (this._roomFill) st.fill_mode = this._roomFill;
if (this._roomTempSrc) st.temp_source = this._roomTempSrc; if (this._roomTempSrc) st.temp_source = this._roomTempSrc;
if (this._roomHumSrc) st.hum_source = this._roomHumSrc; if (this._roomHumSrc) st.hum_source = this._roomHumSrc;
if (this._roomNameScale !== 1) st.name_scale = this._roomNameScale;
if (this._roomLabelScale !== 1) st.label_scale = this._roomLabelScale;
return Object.keys(st).length ? st : null; return Object.keys(st).length ? st : null;
} }
@@ -3777,7 +3792,7 @@ class HouseplanCard extends LitElement {
} }
} }
return html`<div class="roomlabel ${rows.length ? 'card' : ''}" return html`<div class="roomlabel ${rows.length ? 'card' : ''}"
style="left:${left}%;top:${top}%;color:${disp.color};opacity:${op};--rl-scale:${k}" style="left:${left}%;top:${top}%;color:${disp.color};opacity:${op};--rl-scale:${k};--rl-space:${disp.cardFontScale};--rl-name:${clampScale(r.settings?.name_scale)};--rl-meta:${clampScale(r.settings?.label_scale)}"
@pointerdown=${(e: PointerEvent) => this._labelDown(e, r, space.id)} @pointerdown=${(e: PointerEvent) => this._labelDown(e, r, space.id)}
@pointermove=${(e: PointerEvent) => this._labelMove(e, r, space.id)} @pointermove=${(e: PointerEvent) => this._labelMove(e, r, space.id)}
@pointerup=${() => this._labelUp(r)} @pointerup=${() => this._labelUp(r)}
@@ -4652,6 +4667,13 @@ class HouseplanCard extends LitElement {
<span>${this._t(k)}</span> <span>${this._t(k)}</span>
</label>`, </label>`,
)} )}
<label>${this._t('space.card_font')}</label>
<div class="colorrow gsrow">
<input type="range" min="50" max="300" step="5" .value=${String(Math.round(d.cardFontScale * 100))}
@input=${(e: Event) => (this._spaceDialog = { ...d, cardFontScale: Number((e.target as HTMLInputElement).value) / 100 })} />
<span class="opv">${Math.round(d.cardFontScale * 100)}%</span>
</div>
${this._renderCardPreview(d.cardFontScale, 1, 1)}
<label>${this._t('space.room_color')}</label> <label>${this._t('space.room_color')}</label>
<div class="colorrow"> <div class="colorrow">
<input type="color" .value=${d.roomColor} <input type="color" .value=${d.roomColor}
@@ -4739,6 +4761,20 @@ class HouseplanCard extends LitElement {
</div>`; </div>`;
} }
/** Live sample of a room card at the given multipliers (dialog preview). */
private _renderCardPreview(spaceScale: number, nameScale: number, labelScale: number): TemplateResult {
const base = 18 * spaceScale;
return html`<div class="cardpreview">
<span class="cpname" style="font-size:${(base * nameScale).toFixed(1)}px">
${this._t('preview.room_name')}</span>
<span class="cpmeta" style="font-size:${(base * 0.62 * labelScale).toFixed(1)}px">
<ha-icon icon="mdi:thermometer"></ha-icon>22.4° ·
<ha-icon icon="mdi:water-percent"></ha-icon>45% ·
<ha-icon icon="mdi:lightbulb-on"></ha-icon>${this._t('roomcard.light_partial', { on: 1, total: 3 })}
</span>
</div>`;
}
/** One measurement-source control (average vs an explicit device/entity). */ /** One measurement-source control (average vs an explicit device/entity). */
private _renderRoomSource(kind: 'temp' | 'hum'): TemplateResult { private _renderRoomSource(kind: 'temp' | 'hum'): TemplateResult {
const val = kind === 'temp' ? this._roomTempSrc : this._roomHumSrc; const val = kind === 'temp' ? this._roomTempSrc : this._roomHumSrc;
@@ -4828,6 +4864,25 @@ class HouseplanCard extends LitElement {
)} )}
${this._renderRoomSource('temp')} ${this._renderRoomSource('temp')}
${this._renderRoomSource('hum')} ${this._renderRoomSource('hum')}
<label class="dispsection">${this._t('room.sizes_section')}</label>
<label>${this._t('room.name_scale')}</label>
<div class="colorrow gsrow">
<input type="range" min="50" max="300" step="5" .value=${String(Math.round(this._roomNameScale * 100))}
@input=${(e: Event) => { this._roomNameScale = Number((e.target as HTMLInputElement).value) / 100; this.requestUpdate(); }} />
<span class="opv">${Math.round(this._roomNameScale * 100)}%</span>
</div>
<label>${this._t('room.label_scale')}</label>
<div class="colorrow gsrow">
<input type="range" min="50" max="300" step="5" .value=${String(Math.round(this._roomLabelScale * 100))}
@input=${(e: Event) => { this._roomLabelScale = Number((e.target as HTMLInputElement).value) / 100; this.requestUpdate(); }} />
<span class="opv">${Math.round(this._roomLabelScale * 100)}%</span>
</div>
${this._renderCardPreview(
spaceDisplayOf(this._curSpaceCfg).cardFontScale,
this._roomNameScale,
this._roomLabelScale,
)}
</div> </div>
<div class="row"> <div class="row">
<button class="btn ghost" @click=${this._roomDialogCancel}>${this._t('btn.cancel')}</button> <button class="btn ghost" @click=${this._roomDialogCancel}>${this._t('btn.cancel')}</button>
+6 -1
View File
@@ -317,5 +317,10 @@
"room.src_average": "Average over the room's sensors (default)", "room.src_average": "Average over the room's sensors (default)",
"room.src_pick": "A specific HA device or entity", "room.src_pick": "A specific HA device or entity",
"room.src_ph": "Choose a source…", "room.src_ph": "Choose a source…",
"toast.room_updated": "Room updated" "toast.room_updated": "Room updated",
"space.card_font": "Room-card font size (whole space)",
"room.sizes_section": "Font sizes",
"room.name_scale": "Room name size",
"room.label_scale": "Metrics size",
"preview.room_name": "Living room"
} }
+6 -1
View File
@@ -317,5 +317,10 @@
"room.src_average": "Средняя по датчикам комнаты (по умолчанию)", "room.src_average": "Средняя по датчикам комнаты (по умолчанию)",
"room.src_pick": "Конкретное устройство или сущность HA", "room.src_pick": "Конкретное устройство или сущность HA",
"room.src_ph": "Выберите источник…", "room.src_ph": "Выберите источник…",
"toast.room_updated": "Комната обновлена" "toast.room_updated": "Комната обновлена",
"space.card_font": "Размер шрифта карточек комнат (всё пространство)",
"room.sizes_section": "Размеры шрифтов",
"room.name_scale": "Размер названия",
"room.label_scale": "Размер подписей",
"preview.room_name": "Гостиная"
} }
+5
View File
@@ -552,6 +552,8 @@ export interface SpaceDisplay {
tempMax: number; // comfort range upper bound, °C tempMax: number; // comfort range upper bound, °C
/** Per-space LQI badges near zigbee devices; null = follow the card option. */ /** Per-space LQI badges near zigbee devices; null = follow the card option. */
showLqi: boolean | null; showLqi: boolean | null;
/** Base font multiplier for room cards (tier 2; rooms multiply on top). */
cardFontScale: number;
/** Room-card metrics under the room name (all default off). */ /** Room-card metrics under the room name (all default off). */
labelTemp: boolean; labelTemp: boolean;
labelHum: boolean; labelHum: boolean;
@@ -577,6 +579,9 @@ export function spaceDisplayOf(spaceCfg: any): SpaceDisplay {
tempMin: typeof s.temp_min === 'number' ? s.temp_min : DEFAULT_TEMP_MIN, tempMin: typeof s.temp_min === 'number' ? s.temp_min : DEFAULT_TEMP_MIN,
tempMax: typeof s.temp_max === 'number' ? s.temp_max : DEFAULT_TEMP_MAX, tempMax: typeof s.temp_max === 'number' ? s.temp_max : DEFAULT_TEMP_MAX,
showLqi: typeof s.show_lqi === 'boolean' ? s.show_lqi : null, showLqi: typeof s.show_lqi === 'boolean' ? s.show_lqi : null,
cardFontScale: typeof s.card_font_scale === 'number' && s.card_font_scale > 0
? Math.min(3, Math.max(0.5, s.card_font_scale))
: 1,
labelTemp: s.label_temp === true, labelTemp: s.label_temp === true,
labelHum: s.label_hum === true, labelHum: s.label_hum === true,
labelLqi: s.label_lqi === true, labelLqi: s.label_lqi === true,
+27 -3
View File
@@ -364,7 +364,7 @@ export const cardStyles = css`
pointer-events: none; /* draggable only in plan mode (rule below) */ pointer-events: none; /* draggable only in plan mode (rule below) */
position: absolute; position: absolute;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
font-size: calc(var(--icon-size, 2.5cqw) * 0.5 * var(--rl-scale, 1) * var(--rl-font, 1)); font-size: calc(var(--icon-size, 2.5cqw) * 0.5 * var(--rl-scale, 1) * var(--rl-font, 1) * var(--rl-space, 1));
font-weight: 700; font-weight: 700;
letter-spacing: 0.04em; letter-spacing: 0.04em;
white-space: nowrap; white-space: nowrap;
@@ -377,7 +377,12 @@ export const cardStyles = css`
gap: 0.15em; gap: 0.15em;
text-align: center; text-align: center;
} }
.rlname { display: inline-flex; align-items: center; gap: 0.25em; } .rlname {
display: inline-flex;
align-items: center;
gap: 0.25em;
font-size: calc(1em * var(--rl-name, 1));
}
.rlgear { .rlgear {
--mdc-icon-size: 0.9em; --mdc-icon-size: 0.9em;
display: inline-flex; display: inline-flex;
@@ -401,7 +406,7 @@ export const cardStyles = css`
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.55em; gap: 0.55em;
font-size: 0.62em; font-size: calc(0.62em * var(--rl-meta, 1));
font-weight: 600; font-weight: 600;
letter-spacing: 0.02em; letter-spacing: 0.02em;
opacity: 0.9; opacity: 0.9;
@@ -469,6 +474,25 @@ export const cardStyles = css`
.ctrlstate { display: inline-flex; align-items: center; gap: 5px; color: var(--hp-muted); } .ctrlstate { display: inline-flex; align-items: center; gap: 5px; color: var(--hp-muted); }
.ctrlstate.on { color: var(--hp-txt); } .ctrlstate.on { color: var(--hp-txt); }
.ctrlstate ha-icon { --mdc-icon-size: 15px; } .ctrlstate ha-icon { --mdc-icon-size: 15px; }
.cardpreview {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
margin: 8px 0 2px;
padding: 10px;
border: 1px dashed var(--hp-muted);
border-radius: 8px;
}
.cardpreview .cpname { font-weight: 700; letter-spacing: 0.04em; }
.cardpreview .cpmeta {
display: inline-flex;
align-items: center;
gap: 0.3em;
font-weight: 600;
opacity: 0.85;
}
.cardpreview .cpmeta ha-icon { --mdc-icon-size: 1.05em; }
.iconauto { .iconauto {
display: flex; display: flex;
align-items: center; align-items: center;
+3
View File
@@ -10,6 +10,9 @@ export interface RoomCfg {
/** 'device:<id>' or 'entity:<eid>'; unset = average over the room sensors. */ /** 'device:<id>' or 'entity:<eid>'; unset = average over the room sensors. */
temp_source?: string | null; temp_source?: string | null;
hum_source?: string | null; hum_source?: string | null;
/** Font multipliers for THIS room's card (0.5-3, unset = 1). */
name_scale?: number | null;
label_scale?: number | null;
} | null; } | null;
id?: string; id?: string;
name: string; name: string;