BG: opaque plan paper — the scene background never bleeds through the plan

Owner request 2026-08-03: bg_color (and the daynight sky) used to shine
through the plan itself — a hand-drawn plan's translucent room fills sat
directly on the scene colour, and a transparent backdrop image let it
through too. An opaque rect.hp-paper now sits under everything the plan
draws and hugs the plan's extents (the backdrop image rect, or the drawn
content bounds the opening view fits). Its colour is the pre-bg_color
canvas: white for drawn plans (.stage.noplan), the theme card background
under an image and on the static space-card. The daynight night keeps
dimming the plan via the zoomwrap brightness filter ONLY — the paper's
alpha never changes. The scene colour is visible strictly AROUND the
plan, in view/kiosk/editors and the static card alike.

smoke_bg_color grew the contract (sections 11–13): paper presence,
geometry and opacity in view/editors/night, the white drawn-plan paper,
the static card's paper, plus a pixel proof against an acid #ff00ff
background (screenshot → canvas: no acid admixture inside the plan, acid
right outside it). The suite fails on the previous build.

docs: SUN.md background contract + TESTING.md checklist item.
This commit is contained in:
Matysh
2026-08-03 08:16:48 +03:00
parent 1e0295a370
commit a8bb145ffb
10 changed files with 385 additions and 94 deletions
+21
View File
@@ -1596,6 +1596,23 @@ class HouseplanCard extends LitElement {
return b ? [b.x, b.y, b.w, b.h] : m.vb;
}
/**
* Opaque plan "paper" (owner 2026-08-03): the scene background — bg_color or
* the 'daynight' sky — must never bleed through the plan itself, so an
* opaque rect sits under everything the plan draws. It hugs the plan's
* extents: the backdrop image when there is one, otherwise the same content
* bounds the opening view fits (an empty drawn space papers its whole
* canvas — exactly the pre-bg_color look). Its colour is the pre-bg_color
* canvas (styles.ts .hp-paper): white for drawn plans, the theme card
* background under an image. At night 'daynight' dims it via the zoomwrap
* brightness filter ONLY — its alpha stays 1 (docs/SUN.md).
*/
private _paperRect(m: SpaceModel): { x: number; y: number; w: number; h: number } {
if (m.bg) return { x: m.bg.x, y: m.bg.y, w: m.bg.w, h: m.bg.h };
const b = this._baseVb();
return { x: b[0], y: b[1], w: b[2], h: b[3] };
}
/** Aspect ratio of the scene (width/height, px). */
private _stageAspect(): number {
const s = this._stageEl;
@@ -5047,6 +5064,10 @@ class HouseplanCard extends LitElement {
<div class="zoomwrap ${this._slide ? 'slide-' + this._slide : ''}"
style="${dayNight ? `filter:brightness(${(1 - planDim).toFixed(3)})` : ''}">
<svg viewBox="${view.x} ${view.y} ${view.w} ${view.h}" preserveAspectRatio="xMidYMid meet">
${(() => {
const pp = this._paperRect(space);
return svg`<rect class="hp-paper" x="${pp.x}" y="${pp.y}" width="${pp.w}" height="${pp.h}" pointer-events="none"></rect>`;
})()}
${this._editing ? this._renderMarkupDefs(vb) : nothing}
${this._editing && !this._markup
? svg`<rect x="${vb[0]}" y="${vb[1]}" width="${vb[2]}" height="${vb[3]}" fill="url(#hp-grid)" pointer-events="none"></rect>`
+6
View File
@@ -205,6 +205,12 @@ class HouseplanSpaceCard extends LitElement {
height: 100%;
display: block;
}
/* Opaque plan paper — the scene bg_color/daynight sky shows only AROUND
the plan (owner 2026-08-03). The static card keeps its historical
canvas colour: the theme card background. */
.hp-paper {
fill: var(--ha-card-background, var(--card-background-color, #111));
}
.hp-static-stage .devlayer {
position: absolute;
inset: 0;
+14 -1
View File
@@ -14,7 +14,7 @@ import { t, type Lang } from './i18n';
import { bgModeOf, northDegOf, sunStateOf, dayPhase } from './sun';
import type { ServerConfig } from './types';
import {
spaceModels, roomCenter, defaultPositions, markerPos, labelPos, type Layout,
spaceModels, roomCenter, defaultPositions, markerPos, labelPos, contentBounds, type Layout,
} from './space-geometry';
export { spaceModels } from './space-geometry';
@@ -158,9 +158,22 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
}
const stageBg = sunBg || stageBgOf(o.cfg?.settings, disp);
// Opaque plan paper (owner 2026-08-03), same contract as the full card: the
// scene background never bleeds through the plan. The rect hugs the backdrop
// image, or the drawn content's bounds (marker positions included, as on the
// full card); an empty space papers its whole canvas.
const paperPts = spaceDevs.map((d) => {
const p = markerPos(d, o.layout, o.cfg, defPos, space);
return [p.x, p.y] as const;
});
const paper = space.bg
? { x: space.bg.x, y: space.bg.y, w: space.bg.w, h: space.bg.h }
: contentBounds(space, 0.05, paperPts) || { x: vb[0], y: vb[1], w: vb[2], h: vb[3] };
return html`
<div class="hp-static-stage" style="aspect-ratio:${vb[2]}/${vb[3]}${stageBg ? ';background:' + stageBg : ''}">
<svg viewBox="${vb[0]} ${vb[1]} ${vb[2]} ${vb[3]}" preserveAspectRatio="xMidYMid meet">
${svg`<rect class="hp-paper" x="${paper.x}" y="${paper.y}" width="${paper.w}" height="${paper.h}"></rect>`}
${bgHref
? svg`<image href="${bgHref}" x="${space.bg!.x}" y="${space.bg!.y}" width="${space.bg!.w}" height="${space.bg!.h}" preserveAspectRatio="none" />`
: nothing}
+11
View File
@@ -313,6 +313,17 @@ export const cardStyles = css`
height: 100%;
display: block;
}
/* Opaque plan paper (owner 2026-08-03): the scene bg_color / daynight sky
shows ONLY around the plan, never through it. The colour is the
pre-bg_color canvas — the theme card background under an image plan,
plain white for a hand-drawn one (.stage.noplan). Night dimming comes
from the .zoomwrap brightness filter, never from alpha (docs/SUN.md). */
.hp-paper {
fill: var(--ha-card-background, var(--card-background-color, #111));
}
.stage.noplan .hp-paper {
fill: #ffffff;
}
.room {
transition: 0.12s;
cursor: default; /* v1.40.1: rooms are not clickable — the label's link icon is */