mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 04:09:17 +00:00
refactor: extract the devices surface styles (#266 slice 3/5)
82 блока устройств/маркеров/пылесосов → src/styles/devices.styles.ts, склейка [inline, devices, chrome, dialogs]. Три мутантных якоря (.dev .valtext, --dev-size, capsule radius) переадресованы на новый файл. Refactor-proof diff пуст; golden 129/129; npm test 1315/0. Issue: #266 User-Visible: no
This commit is contained in:
File diff suppressed because one or more lines are too long
Vendored
+286
-285
File diff suppressed because one or more lines are too long
@@ -2106,7 +2106,7 @@ export const MUTANTS = [
|
||||
because: 'Text and Double must expose the complete dynamic value; the new shared shell '
|
||||
+ 'must not regress to the old clipped satellite',
|
||||
patches: [{
|
||||
file: 'src/styles.ts',
|
||||
file: 'src/styles/devices.styles.ts',
|
||||
find: ' .dev .valtext {\n overflow: visible;',
|
||||
replace: ' .dev .valtext {\n overflow: hidden;\n text-overflow: ellipsis;',
|
||||
}],
|
||||
@@ -2129,7 +2129,7 @@ export const MUTANTS = [
|
||||
because: 'the current effective size must be resolved once at the surface boundary; '
|
||||
+ 'restoring a late face-level 0.9 factor would shrink every marker a second time',
|
||||
patches: [{
|
||||
file: 'src/styles.ts',
|
||||
file: 'src/styles/devices.styles.ts',
|
||||
find: ' --dev-size: calc(var(--device-base-size, 2.25cqw) * var(--dev-scale, 1));',
|
||||
replace: ' --dev-size: calc(var(--device-base-size, 2.25cqw) * var(--dev-scale, 1) * 0.9);',
|
||||
}],
|
||||
@@ -2141,7 +2141,7 @@ export const MUTANTS = [
|
||||
because: 'a wide Text core must keep a radius based on its height instead of reverting '
|
||||
+ 'to the elliptical 50% rule',
|
||||
patches: [{
|
||||
file: 'src/styles.ts',
|
||||
file: 'src/styles/devices.styles.ts',
|
||||
find: ' border-radius: calc(var(--dev-size, var(--icon-size, 2.5cqw)) / 2);',
|
||||
replace: ' border-radius: 50%;',
|
||||
}],
|
||||
|
||||
+3
-537
@@ -1,10 +1,11 @@
|
||||
/** Styles of the House Plan card — being split by surface (#266). */
|
||||
import { css } from 'lit';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
import { devicesStyles } from './styles/devices.styles';
|
||||
import { chromeStyles } from './styles/chrome.styles';
|
||||
import { dialogsStyles } from './styles/dialogs.styles';
|
||||
|
||||
export { chromeStyles, dialogsStyles };
|
||||
export { devicesStyles, chromeStyles, dialogsStyles };
|
||||
|
||||
const inlineStyles = css`
|
||||
:host {
|
||||
@@ -599,47 +600,6 @@ const inlineStyles = css`
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.op-leaf, .op-arc { transition: none; }
|
||||
}
|
||||
/* Unified device activity: alarm, short event and continuous state all
|
||||
share this one renderer. No static rings are allowed. */
|
||||
.dev ha-icon {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.device-pulse {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: var(--dev-size);
|
||||
height: var(--dev-size);
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
.device-pulse i {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--ripple-color, var(--hp-accent));
|
||||
opacity: 0;
|
||||
}
|
||||
/* A witnessed edge: exactly three waves over the 3.3 s runtime window. */
|
||||
.device-pulse.short i {
|
||||
animation: hp-pulse-short 1.1s cubic-bezier(.22,.61,.36,1) 1 forwards;
|
||||
}
|
||||
.device-pulse.short i:nth-child(2) { animation-delay: 1.1s; }
|
||||
.device-pulse.short i:nth-child(3) { animation-delay: 2.2s; }
|
||||
.device-pulse.short.gen2 i { animation-name: hp-pulse-short-b; }
|
||||
.device-pulse.continuous i:nth-child(n + 2),
|
||||
.device-pulse.alarm i:nth-child(n + 3) { display: none; }
|
||||
.device-pulse.continuous i:first-child {
|
||||
animation: hp-pulse-continuous 3.6s cubic-bezier(.45,.05,.55,.95) infinite;
|
||||
}
|
||||
.device-pulse.alarm i {
|
||||
border-width: 3px;
|
||||
border-color: #F0410C;
|
||||
animation: hp-pulse-alarm 2.4s cubic-bezier(.22,.61,.36,1) infinite;
|
||||
}
|
||||
.device-pulse.alarm i:nth-child(2) { animation-delay: 1.2s; }
|
||||
@keyframes hp-pulse-short {
|
||||
0% { transform: scale(1); opacity: 0.55; }
|
||||
70% { opacity: 0.22; }
|
||||
@@ -660,20 +620,6 @@ const inlineStyles = css`
|
||||
0% { transform: scale(1); opacity: 0.72; }
|
||||
100% { transform: scale(1.5); opacity: 0; }
|
||||
}
|
||||
.activity-dot {
|
||||
position: absolute;
|
||||
right: 8%;
|
||||
bottom: 8%;
|
||||
width: 18%;
|
||||
height: 18%;
|
||||
min-width: 4px;
|
||||
min-height: 4px;
|
||||
border-radius: 50%;
|
||||
background: var(--ripple-color, var(--hp-accent));
|
||||
border: 1px solid color-mix(in srgb, var(--hp-bg) 82%, transparent);
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
}
|
||||
.roomlabel {
|
||||
pointer-events: none; /* draggable only in plan mode (rule below) */
|
||||
position: absolute;
|
||||
@@ -834,24 +780,6 @@ const inlineStyles = css`
|
||||
color: #4bd28f;
|
||||
border-color: #4bd28f;
|
||||
}
|
||||
.kioskdots {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: var(--sp-5);
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: var(--sp-4);
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.kdot {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
background: var(--hp-muted);
|
||||
opacity: 0.55;
|
||||
}
|
||||
.kdot.on { background: var(--hp-accent); opacity: 1; }
|
||||
.measurelabel {
|
||||
position: absolute;
|
||||
transform: translate(12px, -150%);
|
||||
@@ -1455,23 +1383,6 @@ const inlineStyles = css`
|
||||
forced-color-adjust: auto;
|
||||
}
|
||||
}
|
||||
.dev.valonly {
|
||||
/* Saved coordinates remain the centre of a Text shell. The shell may
|
||||
expand, while the invisible anchor stays one core diameter wide. */
|
||||
width: var(--dev-size, var(--icon-size, 2.5cqw));
|
||||
}
|
||||
.dev.valonly .device-core {
|
||||
width: max-content;
|
||||
min-width: var(--dev-size, var(--icon-size, 2.5cqw));
|
||||
padding-inline: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.16);
|
||||
border-radius: calc(var(--dev-size, var(--icon-size, 2.5cqw)) / 2);
|
||||
}
|
||||
.dev .valtext {
|
||||
overflow: visible;
|
||||
font-size: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * var(--value-font-scale, .45));
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* RGB lights: the bulb takes the light's actual color */
|
||||
/* v1.52.0: the RGB tint of the icon/border is gone — a lamp's colour
|
||||
lives ONLY in its glow spot (owner's rule). The ripple-color fallback
|
||||
@@ -1500,246 +1411,14 @@ const inlineStyles = css`
|
||||
.sunlayer, .sunlayer.out { animation: none; }
|
||||
.sunlayer.out { opacity: 0; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.device-pulse { display: none; }
|
||||
}
|
||||
.dev .newdot {
|
||||
position: absolute;
|
||||
top: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * -0.12);
|
||||
right: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * -0.12);
|
||||
width: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.34);
|
||||
height: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.34);
|
||||
border-radius: 50%;
|
||||
background: #f0301f;
|
||||
border: 2px solid var(--card-background-color, var(--hp-bg));
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
.devlayer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
z-index: 6;
|
||||
}
|
||||
.dev {
|
||||
position: absolute;
|
||||
/* The surface resolves compatibility icon_size units once. The shared
|
||||
face receives an effective base and only applies the per-device size. */
|
||||
--dev-size: calc(var(--device-base-size, 2.25cqw) * var(--dev-scale, 1));
|
||||
/* 101.5/80 is the package shell/core ratio, including its stroke. */
|
||||
--device-shell-size: calc(var(--dev-size) * 1.26875);
|
||||
--device-shell-inset: calc(var(--dev-size) * 0.134375);
|
||||
--device-shell-stroke-ratio: 0.01875;
|
||||
--device-shell-border-width: max(1px, calc(var(--dev-size) * var(--device-shell-stroke-ratio)));
|
||||
--device-core-bg: var(--card-background-color, #fff);
|
||||
--device-core-fg: var(--primary-text-color, #252525);
|
||||
--device-core-bg: light-dark(#fff, #252525);
|
||||
--device-core-fg: light-dark(#252525, #fff);
|
||||
--device-face-bg: var(--device-core-bg);
|
||||
--device-face-fg: var(--device-core-fg);
|
||||
--device-shell-base-stroke: light-dark(#BCBCBC, rgb(37 37 37 / 75%));
|
||||
--device-shell-stroke: var(--device-shell-base-stroke);
|
||||
--device-shell-shadow:
|
||||
0 calc(var(--dev-size) * .025) calc(var(--dev-size) * .05) rgb(37 40 45 / 12%),
|
||||
0 calc(var(--dev-size) * .1) calc(var(--dev-size) * .175)
|
||||
calc(var(--dev-size) * -.025) rgb(37 40 45 / 18%);
|
||||
--device-core-inset-shadow: 0 0 0 0 transparent;
|
||||
--device-ring-color: transparent;
|
||||
--device-ring-width: 0px;
|
||||
/* The saved point is the exact centre of the icon core. */
|
||||
width: var(--dev-size);
|
||||
height: var(--dev-size);
|
||||
margin: calc(var(--dev-size) / -2) 0 0 calc(var(--dev-size) / -2);
|
||||
border: 0;
|
||||
background: transparent;
|
||||
display: block;
|
||||
color: var(--device-core-fg);
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
transition: opacity 0.2s;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
z-index: 2;
|
||||
}
|
||||
.dev.theme-light {
|
||||
--device-core-bg: #fff;
|
||||
--device-core-fg: #000;
|
||||
--device-shell-base-stroke: #BCBCBC;
|
||||
--device-shell-stroke: var(--device-shell-base-stroke);
|
||||
--device-shell-shadow:
|
||||
0 calc(var(--dev-size) * .025) calc(var(--dev-size) * .05) rgb(37 40 45 / 12%),
|
||||
0 calc(var(--dev-size) * .1) calc(var(--dev-size) * .175)
|
||||
calc(var(--dev-size) * -.025) rgb(37 40 45 / 18%);
|
||||
--device-core-inset-shadow: 0 0 0 0 transparent;
|
||||
}
|
||||
.dev.theme-dark {
|
||||
--device-core-bg: #252525;
|
||||
--device-core-fg: #fff;
|
||||
--device-shell-base-stroke: rgb(37 37 37 / 75%);
|
||||
--device-shell-stroke: var(--device-shell-base-stroke);
|
||||
--device-shell-shadow:
|
||||
0 calc(var(--dev-size) * .025) calc(var(--dev-size) * .0375) rgb(37 40 45 / 12%),
|
||||
0 calc(var(--dev-size) * .1) calc(var(--dev-size) * .175)
|
||||
calc(var(--dev-size) * -.025) rgb(37 40 45 / 18%);
|
||||
--device-core-inset-shadow:
|
||||
inset 0 calc(var(--dev-size) * 0.0125) calc(var(--dev-size) * 0.0125) rgb(255 255 255 / 70%);
|
||||
}
|
||||
.dev::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: max(44px, var(--device-shell-size));
|
||||
height: max(44px, var(--device-shell-size));
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
pointer-events: auto;
|
||||
z-index: 3;
|
||||
}
|
||||
.device-shell {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(var(--dev-size) * 0.1);
|
||||
padding: 0;
|
||||
min-width: var(--dev-size);
|
||||
min-height: var(--dev-size);
|
||||
border: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
transition: opacity .2s;
|
||||
pointer-events: none;
|
||||
}
|
||||
.device-shell-frame {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
box-sizing: border-box;
|
||||
inset: calc(var(--device-shell-inset) / -1);
|
||||
border: var(--device-shell-border-width) solid var(--device-shell-stroke);
|
||||
/* A saturating radius is resolved from the final border box. Using a
|
||||
second fractional length here lets border-box and radius quantise on
|
||||
different sides of a device pixel at some zoom/DPR combinations. */
|
||||
border-radius: 9999px;
|
||||
background: transparent;
|
||||
box-shadow: var(--device-shell-shadow);
|
||||
transition: border-color .15s, box-shadow .15s, opacity .2s;
|
||||
pointer-events: auto;
|
||||
/* Normative production fallback: never add a per-marker backdrop blur. */
|
||||
backdrop-filter: none;
|
||||
}
|
||||
.device-shell:not(.with-values) {
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.device-shell:not(.with-values):not(.text-shell) .device-shell-frame {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.device-shell.with-values.pos-right {
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.device-shell.with-values.pos-left {
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.device-shell.with-values.pos-bottom {
|
||||
left: 50%;
|
||||
top: 0;
|
||||
transform: translateX(-50%);
|
||||
flex-direction: column;
|
||||
}
|
||||
.device-shell.with-values.pos-top {
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
transform: translateX(-50%);
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
.device-core {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
flex: 0 0 auto;
|
||||
width: var(--dev-size);
|
||||
height: var(--dev-size);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: var(--device-face-bg);
|
||||
color: var(--device-face-fg);
|
||||
box-shadow:
|
||||
var(--device-core-inset-shadow),
|
||||
0 0 0 var(--device-ring-width) var(--device-ring-color);
|
||||
line-height: 0;
|
||||
transition: background .15s, color .15s, box-shadow .15s, opacity .2s;
|
||||
pointer-events: none;
|
||||
}
|
||||
.device-sections {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
gap: calc(var(--dev-size) * .08);
|
||||
min-width: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.device-shell.pos-top .device-sections,
|
||||
.device-shell.pos-bottom .device-sections {
|
||||
flex-direction: row;
|
||||
}
|
||||
.dev ha-icon {
|
||||
/* from --dev-size, NOT --icon-size: the per-device size multiplier must
|
||||
scale the GLYPH with its badge. Pinned to the base size, "make this
|
||||
icon bigger" grew an empty box around a default-size glyph (user
|
||||
report via the owner, 2026-07-29). */
|
||||
--mdc-icon-size: calc(var(--dev-size, var(--device-base-size, 2.25cqw)) * 0.55);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 0;
|
||||
}
|
||||
.stage.mode-devices .dev { cursor: grab; }
|
||||
.stage.mode-devices .dev:active { cursor: grabbing; }
|
||||
.dev.on {
|
||||
--device-face-bg: #F0A00C;
|
||||
--device-face-fg: light-dark(#fff, #252525);
|
||||
--device-shell-stroke: #F0A00C;
|
||||
}
|
||||
.dev.theme-light.on { --device-face-fg: #fff; }
|
||||
.dev.theme-dark.on {
|
||||
--device-face-fg: #252525;
|
||||
--device-shell-stroke-ratio: .0375;
|
||||
}
|
||||
.dev.open {
|
||||
--device-face-bg: var(--hp-open);
|
||||
--device-face-fg: light-dark(#fff, #252525);
|
||||
}
|
||||
.dev.theme-light.open { --device-face-fg: #fff; }
|
||||
.dev.theme-dark.open { --device-face-fg: #252525; }
|
||||
.dev.lock-locked {
|
||||
--device-face-bg: #66D17A;
|
||||
--device-face-fg: light-dark(#fff, #252525);
|
||||
--device-shell-stroke: #66D17A;
|
||||
}
|
||||
.dev.theme-light.lock-locked { --device-face-fg: #fff; }
|
||||
.dev.theme-dark.lock-locked {
|
||||
--device-face-fg: #252525;
|
||||
--device-shell-stroke-ratio: .025;
|
||||
}
|
||||
.dev.lock-unlocked {
|
||||
--device-face-bg: #F0410C;
|
||||
--device-face-fg: light-dark(#fff, #252525);
|
||||
--device-shell-stroke: #F0410C;
|
||||
}
|
||||
.dev.theme-light.lock-unlocked { --device-face-fg: #fff; }
|
||||
.dev.theme-dark.lock-unlocked {
|
||||
--device-face-fg: #252525;
|
||||
--device-shell-stroke-ratio: .025;
|
||||
}
|
||||
/* Interaction wins ordinary state colours. Alarm keeps priority through
|
||||
the more-specific rule below. Unavailable has no visual hover. */
|
||||
:host([data-pointer-hover]) .dev:not(.unavail):hover {
|
||||
@@ -1751,11 +1430,6 @@ const inlineStyles = css`
|
||||
:host([data-pointer-hover]) .dev.theme-dark:not(.unavail):hover { --device-face-fg: #252525; }
|
||||
:host([data-pointer-hover]) .dev:hover,
|
||||
.dev:focus-visible { z-index: 5; }
|
||||
.dev.unavail {
|
||||
opacity: 0.35;
|
||||
--device-face-bg: #B5BAC1;
|
||||
--device-shell-stroke: var(--device-shell-base-stroke);
|
||||
}
|
||||
.physical-hit {
|
||||
fill: transparent;
|
||||
stroke: transparent;
|
||||
@@ -1808,68 +1482,6 @@ const inlineStyles = css`
|
||||
.drawwall.invalid .rangehint { color: var(--error-color, #db4437); opacity: 1; }
|
||||
.stage.markup.tool-partition,
|
||||
.stage.markup.tool-column { cursor: crosshair; }
|
||||
.dev.virtual .device-shell-frame {
|
||||
border-style: dashed;
|
||||
}
|
||||
/* "hide from plan" flag, shown only in the device editor with the
|
||||
"show hidden devices" toggle on (docs/FILTERING.md). BLUE, so a hidden
|
||||
device cannot be mistaken for an unavailable one (translucent dark) —
|
||||
and no live-state paint at all: a ghost is configuration, not status
|
||||
(owner's request). */
|
||||
.dev.ghost {
|
||||
opacity: 0.6;
|
||||
color: var(--hp-accent);
|
||||
}
|
||||
.dev.ghost .device-shell-frame {
|
||||
border-style: dashed;
|
||||
border-color: var(--hp-accent);
|
||||
box-shadow: none;
|
||||
}
|
||||
.dev.ghost .device-core {
|
||||
background: rgba(62, 166, 255, 0.22); /* fallback for old WebViews */
|
||||
background: color-mix(in srgb, var(--hp-accent) 30%, var(--card-background-color, #1c2530));
|
||||
color: var(--hp-accent);
|
||||
}
|
||||
/* HA-disabled is not a user hide: neutral grey + a power-off badge keeps
|
||||
the two ghosts distinguishable without relying on colour alone. */
|
||||
.dev.ghost.ha-disabled {
|
||||
opacity: 0.62;
|
||||
color: var(--secondary-text-color, #9aa0aa);
|
||||
}
|
||||
.dev.ghost.ha-disabled .device-shell-frame {
|
||||
border-color: var(--secondary-text-color, #9aa0aa);
|
||||
}
|
||||
.dev.ghost.ha-disabled .device-core {
|
||||
background: rgba(120, 124, 134, 0.2);
|
||||
background: color-mix(in srgb, var(--secondary-text-color, #9aa0aa) 24%, var(--card-background-color, #1c2530));
|
||||
color: var(--secondary-text-color, #9aa0aa);
|
||||
}
|
||||
.dev .habadge {
|
||||
position: absolute;
|
||||
right: -18%;
|
||||
bottom: -18%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 46%;
|
||||
height: 46%;
|
||||
min-width: 12px;
|
||||
min-height: 12px;
|
||||
border-radius: 50%;
|
||||
background: var(--card-background-color, #242832);
|
||||
border: 1px solid currentColor;
|
||||
}
|
||||
.dev .habadge ha-icon { width: 72%; height: 72%; }
|
||||
.dev.sel {
|
||||
--device-ring-color: #F0A00C;
|
||||
--device-ring-width: calc(var(--dev-size) * 0.0375);
|
||||
}
|
||||
.dev:focus-visible {
|
||||
--device-ring-color: #0C82F0;
|
||||
--device-ring-width: calc(var(--dev-size) * 0.0375);
|
||||
}
|
||||
.dev:not(.on):not(.open):not(.alarm):not(.lock-locked):not(.lock-unlocked):not(.unavail):focus-visible {
|
||||
--device-face-fg: #0C82F0;
|
||||
}
|
||||
/* Alert stays above focus, selection, hover and ordinary semantic paint. */
|
||||
.dev.alarm,
|
||||
:host([data-pointer-hover]) .dev.alarm:hover,
|
||||
@@ -1878,142 +1490,9 @@ const inlineStyles = css`
|
||||
--device-face-fg: light-dark(#fff, #252525);
|
||||
--device-shell-stroke: #F0410C;
|
||||
}
|
||||
.dev.theme-light.alarm { --device-face-fg: #fff; }
|
||||
.dev.theme-dark.alarm {
|
||||
--device-face-fg: #252525;
|
||||
--device-shell-stroke-ratio: .025;
|
||||
}
|
||||
.dev .value-badge {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
box-sizing: border-box;
|
||||
width: max-content;
|
||||
min-width: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * .7875);
|
||||
height: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * .7875);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: visible;
|
||||
background: var(--device-core-bg);
|
||||
border: 0;
|
||||
border-radius: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.39375);
|
||||
padding: 0 calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.14);
|
||||
font-size: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * var(--value-font-scale, .45));
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
color: var(--device-core-fg);
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
}
|
||||
.dev .value-badge.unavailable,
|
||||
.dev .value-badge.missing { opacity: 0.66; }
|
||||
.dev .lqi {
|
||||
position: absolute;
|
||||
top: calc(50% + var(--device-shell-size) / 2 + var(--dev-size) * .05);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin-top: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.05);
|
||||
font-size: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.38);
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
text-shadow: 0 0 3px rgba(0, 0, 0, 0.9), 0 0 2px rgba(0, 0, 0, 0.9);
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
}
|
||||
.dev .lqi.below-value-badge {
|
||||
margin-top: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.8875);
|
||||
}
|
||||
:host([data-pointer-hover]) .tab:hover .tabedit {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.temprange {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-3);
|
||||
margin-left: auto;
|
||||
color: var(--hp-muted);
|
||||
font-size: var(--fs-s);
|
||||
}
|
||||
/* live vacuum: a round puck, no badge plate, soft pulse (docs/VACUUM.md) */
|
||||
.vacpuck {
|
||||
position: absolute;
|
||||
/* the base badge, but round and 20% smaller — the owner's wording:
|
||||
«иконка похожа на иконку базы, только круглая и чуть меньше» */
|
||||
--puck-size: calc(var(--device-base-size, 2.25cqw) * 0.8);
|
||||
width: var(--puck-size);
|
||||
height: var(--puck-size);
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
--vac-core-bg: light-dark(#fff, #252525);
|
||||
--vac-core-fg: light-dark(#252525, #fff);
|
||||
background: var(--vac-core-bg);
|
||||
border: 1px solid #BCBCBC;
|
||||
box-shadow:
|
||||
0 1px 2px rgb(37 40 45 / 12%),
|
||||
0 4px 8px -1.07px rgb(37 40 45 / 18%);
|
||||
color: var(--vac-core-fg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 6;
|
||||
/* glide between sparse position updates; .jump disables it so the robot
|
||||
never appears to drive through walls after a data gap */
|
||||
transition: left 1.2s linear, top 1.2s linear;
|
||||
animation: vacpulse 2.2s ease-out infinite;
|
||||
}
|
||||
.vacpuck.theme-light { --vac-core-bg: #fff; --vac-core-fg: #252525; }
|
||||
.vacpuck.theme-dark { --vac-core-bg: #252525; --vac-core-fg: #fff; }
|
||||
.vacpuck.jump { transition: none; }
|
||||
.vacpuck.stale { opacity: 0.45; animation: none; }
|
||||
.vacpuck ha-icon {
|
||||
--mdc-icon-size: calc(var(--puck-size) * 0.68);
|
||||
color: var(--vac-core-fg);
|
||||
/* same centering recipe as .dev ha-icon: without flex + line-height 0
|
||||
the glyph sits on its text baseline and appears to float around the
|
||||
circle (owner report 2026-07-31) */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 0;
|
||||
width: var(--mdc-icon-size);
|
||||
height: var(--mdc-icon-size);
|
||||
}
|
||||
@keyframes vacpulse {
|
||||
0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--hp-accent) 45%, transparent); }
|
||||
70% { box-shadow: 0 0 0 12px transparent; }
|
||||
100% { box-shadow: 0 0 0 0 transparent; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.vacpuck { animation: none; }
|
||||
}
|
||||
.vactrail {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
overflow: visible;
|
||||
}
|
||||
.vactrail polyline,
|
||||
.vactrail path {
|
||||
fill: none;
|
||||
stroke-linejoin: round;
|
||||
stroke-linecap: round;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
/* dark halo + light core: neutral, and one of the two always contrasts
|
||||
with whatever fill is underneath */
|
||||
.vactrail g.prev { opacity: 0.4; }
|
||||
.vactrail .case {
|
||||
stroke: rgba(0, 0, 0, 0.4);
|
||||
stroke-width: 2.25;
|
||||
}
|
||||
.vactrail .core {
|
||||
stroke: rgba(255, 255, 255, 0.82);
|
||||
stroke-width: 0.9;
|
||||
}
|
||||
/* room resize tool (docs/RESIZE.md) */
|
||||
/* wall handle: invisible finger-sized hit circle (HP-1550-04 hit priority
|
||||
kept), the visible glyph lives in the sibling .rszicon */
|
||||
@@ -2107,19 +1586,6 @@ const inlineStyles = css`
|
||||
:host([data-pointer-hover]) .rrow .ract.del:hover { color: #ff7a5c; }
|
||||
.alignmsg { margin: 0 0 8px; font-size: 13px; line-height: 1.45; }
|
||||
:host([data-pointer-hover]) .aboutlink:hover { text-decoration: underline; }
|
||||
.devicepreview-empty {
|
||||
min-height: 82px;
|
||||
margin: 4px 0 14px;
|
||||
padding: var(--sp-4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--sp-3);
|
||||
border: 1px dashed var(--hp-line);
|
||||
border-radius: var(--rad-m);
|
||||
color: var(--hp-muted);
|
||||
text-align: center;
|
||||
}
|
||||
:host([data-pointer-hover]) .menu .it:hover {
|
||||
background: rgba(127, 127, 127, 0.15);
|
||||
}
|
||||
@@ -2142,5 +1608,5 @@ const inlineStyles = css`
|
||||
`;
|
||||
|
||||
export const cardStyles: CSSResultGroup = [
|
||||
inlineStyles, chromeStyles, dialogsStyles,
|
||||
inlineStyles, devicesStyles, chromeStyles, dialogsStyles,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,540 @@
|
||||
/** Device markers, shells, pulses and vacuum presentation (#266, split from styles.ts). */
|
||||
import { css } from 'lit';
|
||||
|
||||
export const devicesStyles = css`
|
||||
/* Unified device activity: alarm, short event and continuous state all
|
||||
share this one renderer. No static rings are allowed. */
|
||||
.dev ha-icon {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.device-pulse {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: var(--dev-size);
|
||||
height: var(--dev-size);
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
.device-pulse i {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--ripple-color, var(--hp-accent));
|
||||
opacity: 0;
|
||||
}
|
||||
/* A witnessed edge: exactly three waves over the 3.3 s runtime window. */
|
||||
.device-pulse.short i {
|
||||
animation: hp-pulse-short 1.1s cubic-bezier(.22,.61,.36,1) 1 forwards;
|
||||
}
|
||||
.device-pulse.short i:nth-child(2) { animation-delay: 1.1s; }
|
||||
.device-pulse.short i:nth-child(3) { animation-delay: 2.2s; }
|
||||
.device-pulse.short.gen2 i { animation-name: hp-pulse-short-b; }
|
||||
.device-pulse.continuous i:nth-child(n + 2),
|
||||
.device-pulse.alarm i:nth-child(n + 3) { display: none; }
|
||||
.device-pulse.continuous i:first-child {
|
||||
animation: hp-pulse-continuous 3.6s cubic-bezier(.45,.05,.55,.95) infinite;
|
||||
}
|
||||
.device-pulse.alarm i {
|
||||
border-width: 3px;
|
||||
border-color: #F0410C;
|
||||
animation: hp-pulse-alarm 2.4s cubic-bezier(.22,.61,.36,1) infinite;
|
||||
}
|
||||
.device-pulse.alarm i:nth-child(2) { animation-delay: 1.2s; }
|
||||
.activity-dot {
|
||||
position: absolute;
|
||||
right: 8%;
|
||||
bottom: 8%;
|
||||
width: 18%;
|
||||
height: 18%;
|
||||
min-width: 4px;
|
||||
min-height: 4px;
|
||||
border-radius: 50%;
|
||||
background: var(--ripple-color, var(--hp-accent));
|
||||
border: 1px solid color-mix(in srgb, var(--hp-bg) 82%, transparent);
|
||||
pointer-events: none;
|
||||
z-index: 3;
|
||||
}
|
||||
.kioskdots {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: var(--sp-5);
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: var(--sp-4);
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.kdot {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
background: var(--hp-muted);
|
||||
opacity: 0.55;
|
||||
}
|
||||
.kdot.on { background: var(--hp-accent); opacity: 1; }
|
||||
.dev.valonly {
|
||||
/* Saved coordinates remain the centre of a Text shell. The shell may
|
||||
expand, while the invisible anchor stays one core diameter wide. */
|
||||
width: var(--dev-size, var(--icon-size, 2.5cqw));
|
||||
}
|
||||
.dev.valonly .device-core {
|
||||
width: max-content;
|
||||
min-width: var(--dev-size, var(--icon-size, 2.5cqw));
|
||||
padding-inline: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.16);
|
||||
border-radius: calc(var(--dev-size, var(--icon-size, 2.5cqw)) / 2);
|
||||
}
|
||||
.dev .valtext {
|
||||
overflow: visible;
|
||||
font-size: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * var(--value-font-scale, .45));
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.device-pulse { display: none; }
|
||||
}
|
||||
.dev .newdot {
|
||||
position: absolute;
|
||||
top: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * -0.12);
|
||||
right: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * -0.12);
|
||||
width: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.34);
|
||||
height: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.34);
|
||||
border-radius: 50%;
|
||||
background: #f0301f;
|
||||
border: 2px solid var(--card-background-color, var(--hp-bg));
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
.dev {
|
||||
position: absolute;
|
||||
/* The surface resolves compatibility icon_size units once. The shared
|
||||
face receives an effective base and only applies the per-device size. */
|
||||
--dev-size: calc(var(--device-base-size, 2.25cqw) * var(--dev-scale, 1));
|
||||
/* 101.5/80 is the package shell/core ratio, including its stroke. */
|
||||
--device-shell-size: calc(var(--dev-size) * 1.26875);
|
||||
--device-shell-inset: calc(var(--dev-size) * 0.134375);
|
||||
--device-shell-stroke-ratio: 0.01875;
|
||||
--device-shell-border-width: max(1px, calc(var(--dev-size) * var(--device-shell-stroke-ratio)));
|
||||
--device-core-bg: var(--card-background-color, #fff);
|
||||
--device-core-fg: var(--primary-text-color, #252525);
|
||||
--device-core-bg: light-dark(#fff, #252525);
|
||||
--device-core-fg: light-dark(#252525, #fff);
|
||||
--device-face-bg: var(--device-core-bg);
|
||||
--device-face-fg: var(--device-core-fg);
|
||||
--device-shell-base-stroke: light-dark(#BCBCBC, rgb(37 37 37 / 75%));
|
||||
--device-shell-stroke: var(--device-shell-base-stroke);
|
||||
--device-shell-shadow:
|
||||
0 calc(var(--dev-size) * .025) calc(var(--dev-size) * .05) rgb(37 40 45 / 12%),
|
||||
0 calc(var(--dev-size) * .1) calc(var(--dev-size) * .175)
|
||||
calc(var(--dev-size) * -.025) rgb(37 40 45 / 18%);
|
||||
--device-core-inset-shadow: 0 0 0 0 transparent;
|
||||
--device-ring-color: transparent;
|
||||
--device-ring-width: 0px;
|
||||
/* The saved point is the exact centre of the icon core. */
|
||||
width: var(--dev-size);
|
||||
height: var(--dev-size);
|
||||
margin: calc(var(--dev-size) / -2) 0 0 calc(var(--dev-size) / -2);
|
||||
border: 0;
|
||||
background: transparent;
|
||||
display: block;
|
||||
color: var(--device-core-fg);
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
transition: opacity 0.2s;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
z-index: 2;
|
||||
}
|
||||
.dev.theme-light {
|
||||
--device-core-bg: #fff;
|
||||
--device-core-fg: #000;
|
||||
--device-shell-base-stroke: #BCBCBC;
|
||||
--device-shell-stroke: var(--device-shell-base-stroke);
|
||||
--device-shell-shadow:
|
||||
0 calc(var(--dev-size) * .025) calc(var(--dev-size) * .05) rgb(37 40 45 / 12%),
|
||||
0 calc(var(--dev-size) * .1) calc(var(--dev-size) * .175)
|
||||
calc(var(--dev-size) * -.025) rgb(37 40 45 / 18%);
|
||||
--device-core-inset-shadow: 0 0 0 0 transparent;
|
||||
}
|
||||
.dev.theme-dark {
|
||||
--device-core-bg: #252525;
|
||||
--device-core-fg: #fff;
|
||||
--device-shell-base-stroke: rgb(37 37 37 / 75%);
|
||||
--device-shell-stroke: var(--device-shell-base-stroke);
|
||||
--device-shell-shadow:
|
||||
0 calc(var(--dev-size) * .025) calc(var(--dev-size) * .0375) rgb(37 40 45 / 12%),
|
||||
0 calc(var(--dev-size) * .1) calc(var(--dev-size) * .175)
|
||||
calc(var(--dev-size) * -.025) rgb(37 40 45 / 18%);
|
||||
--device-core-inset-shadow:
|
||||
inset 0 calc(var(--dev-size) * 0.0125) calc(var(--dev-size) * 0.0125) rgb(255 255 255 / 70%);
|
||||
}
|
||||
.dev::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: max(44px, var(--device-shell-size));
|
||||
height: max(44px, var(--device-shell-size));
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
pointer-events: auto;
|
||||
z-index: 3;
|
||||
}
|
||||
.device-shell {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(var(--dev-size) * 0.1);
|
||||
padding: 0;
|
||||
min-width: var(--dev-size);
|
||||
min-height: var(--dev-size);
|
||||
border: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
transition: opacity .2s;
|
||||
pointer-events: none;
|
||||
}
|
||||
.device-shell-frame {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
box-sizing: border-box;
|
||||
inset: calc(var(--device-shell-inset) / -1);
|
||||
border: var(--device-shell-border-width) solid var(--device-shell-stroke);
|
||||
/* A saturating radius is resolved from the final border box. Using a
|
||||
second fractional length here lets border-box and radius quantise on
|
||||
different sides of a device pixel at some zoom/DPR combinations. */
|
||||
border-radius: 9999px;
|
||||
background: transparent;
|
||||
box-shadow: var(--device-shell-shadow);
|
||||
transition: border-color .15s, box-shadow .15s, opacity .2s;
|
||||
pointer-events: auto;
|
||||
/* Normative production fallback: never add a per-marker backdrop blur. */
|
||||
backdrop-filter: none;
|
||||
}
|
||||
.device-shell:not(.with-values) {
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.device-shell:not(.with-values):not(.text-shell) .device-shell-frame {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.device-shell.with-values.pos-right {
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.device-shell.with-values.pos-left {
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.device-shell.with-values.pos-bottom {
|
||||
left: 50%;
|
||||
top: 0;
|
||||
transform: translateX(-50%);
|
||||
flex-direction: column;
|
||||
}
|
||||
.device-shell.with-values.pos-top {
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
transform: translateX(-50%);
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
.device-core {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
flex: 0 0 auto;
|
||||
width: var(--dev-size);
|
||||
height: var(--dev-size);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: var(--device-face-bg);
|
||||
color: var(--device-face-fg);
|
||||
box-shadow:
|
||||
var(--device-core-inset-shadow),
|
||||
0 0 0 var(--device-ring-width) var(--device-ring-color);
|
||||
line-height: 0;
|
||||
transition: background .15s, color .15s, box-shadow .15s, opacity .2s;
|
||||
pointer-events: none;
|
||||
}
|
||||
.device-sections {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
gap: calc(var(--dev-size) * .08);
|
||||
min-width: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.device-shell.pos-top .device-sections,
|
||||
.device-shell.pos-bottom .device-sections {
|
||||
flex-direction: row;
|
||||
}
|
||||
.dev ha-icon {
|
||||
/* from --dev-size, NOT --icon-size: the per-device size multiplier must
|
||||
scale the GLYPH with its badge. Pinned to the base size, "make this
|
||||
icon bigger" grew an empty box around a default-size glyph (user
|
||||
report via the owner, 2026-07-29). */
|
||||
--mdc-icon-size: calc(var(--dev-size, var(--device-base-size, 2.25cqw)) * 0.55);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 0;
|
||||
}
|
||||
.dev.on {
|
||||
--device-face-bg: #F0A00C;
|
||||
--device-face-fg: light-dark(#fff, #252525);
|
||||
--device-shell-stroke: #F0A00C;
|
||||
}
|
||||
.dev.theme-light.on { --device-face-fg: #fff; }
|
||||
.dev.theme-dark.on {
|
||||
--device-face-fg: #252525;
|
||||
--device-shell-stroke-ratio: .0375;
|
||||
}
|
||||
.dev.open {
|
||||
--device-face-bg: var(--hp-open);
|
||||
--device-face-fg: light-dark(#fff, #252525);
|
||||
}
|
||||
.dev.theme-light.open { --device-face-fg: #fff; }
|
||||
.dev.theme-dark.open { --device-face-fg: #252525; }
|
||||
.dev.lock-locked {
|
||||
--device-face-bg: #66D17A;
|
||||
--device-face-fg: light-dark(#fff, #252525);
|
||||
--device-shell-stroke: #66D17A;
|
||||
}
|
||||
.dev.theme-light.lock-locked { --device-face-fg: #fff; }
|
||||
.dev.theme-dark.lock-locked {
|
||||
--device-face-fg: #252525;
|
||||
--device-shell-stroke-ratio: .025;
|
||||
}
|
||||
.dev.lock-unlocked {
|
||||
--device-face-bg: #F0410C;
|
||||
--device-face-fg: light-dark(#fff, #252525);
|
||||
--device-shell-stroke: #F0410C;
|
||||
}
|
||||
.dev.theme-light.lock-unlocked { --device-face-fg: #fff; }
|
||||
.dev.theme-dark.lock-unlocked {
|
||||
--device-face-fg: #252525;
|
||||
--device-shell-stroke-ratio: .025;
|
||||
}
|
||||
.dev.unavail {
|
||||
opacity: 0.35;
|
||||
--device-face-bg: #B5BAC1;
|
||||
--device-shell-stroke: var(--device-shell-base-stroke);
|
||||
}
|
||||
.dev.virtual .device-shell-frame {
|
||||
border-style: dashed;
|
||||
}
|
||||
/* "hide from plan" flag, shown only in the device editor with the
|
||||
"show hidden devices" toggle on (docs/FILTERING.md). BLUE, so a hidden
|
||||
device cannot be mistaken for an unavailable one (translucent dark) —
|
||||
and no live-state paint at all: a ghost is configuration, not status
|
||||
(owner's request). */
|
||||
.dev.ghost {
|
||||
opacity: 0.6;
|
||||
color: var(--hp-accent);
|
||||
}
|
||||
.dev.ghost .device-shell-frame {
|
||||
border-style: dashed;
|
||||
border-color: var(--hp-accent);
|
||||
box-shadow: none;
|
||||
}
|
||||
.dev.ghost .device-core {
|
||||
background: rgba(62, 166, 255, 0.22); /* fallback for old WebViews */
|
||||
background: color-mix(in srgb, var(--hp-accent) 30%, var(--card-background-color, #1c2530));
|
||||
color: var(--hp-accent);
|
||||
}
|
||||
/* HA-disabled is not a user hide: neutral grey + a power-off badge keeps
|
||||
the two ghosts distinguishable without relying on colour alone. */
|
||||
.dev.ghost.ha-disabled {
|
||||
opacity: 0.62;
|
||||
color: var(--secondary-text-color, #9aa0aa);
|
||||
}
|
||||
.dev.ghost.ha-disabled .device-shell-frame {
|
||||
border-color: var(--secondary-text-color, #9aa0aa);
|
||||
}
|
||||
.dev.ghost.ha-disabled .device-core {
|
||||
background: rgba(120, 124, 134, 0.2);
|
||||
background: color-mix(in srgb, var(--secondary-text-color, #9aa0aa) 24%, var(--card-background-color, #1c2530));
|
||||
color: var(--secondary-text-color, #9aa0aa);
|
||||
}
|
||||
.dev .habadge {
|
||||
position: absolute;
|
||||
right: -18%;
|
||||
bottom: -18%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 46%;
|
||||
height: 46%;
|
||||
min-width: 12px;
|
||||
min-height: 12px;
|
||||
border-radius: 50%;
|
||||
background: var(--card-background-color, #242832);
|
||||
border: 1px solid currentColor;
|
||||
}
|
||||
.dev .habadge ha-icon { width: 72%; height: 72%; }
|
||||
.dev.sel {
|
||||
--device-ring-color: #F0A00C;
|
||||
--device-ring-width: calc(var(--dev-size) * 0.0375);
|
||||
}
|
||||
.dev:focus-visible {
|
||||
--device-ring-color: #0C82F0;
|
||||
--device-ring-width: calc(var(--dev-size) * 0.0375);
|
||||
}
|
||||
.dev:not(.on):not(.open):not(.alarm):not(.lock-locked):not(.lock-unlocked):not(.unavail):focus-visible {
|
||||
--device-face-fg: #0C82F0;
|
||||
}
|
||||
.dev.theme-light.alarm { --device-face-fg: #fff; }
|
||||
.dev.theme-dark.alarm {
|
||||
--device-face-fg: #252525;
|
||||
--device-shell-stroke-ratio: .025;
|
||||
}
|
||||
.dev .value-badge {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
box-sizing: border-box;
|
||||
width: max-content;
|
||||
min-width: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * .7875);
|
||||
height: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * .7875);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: visible;
|
||||
background: var(--device-core-bg);
|
||||
border: 0;
|
||||
border-radius: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.39375);
|
||||
padding: 0 calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.14);
|
||||
font-size: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * var(--value-font-scale, .45));
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
color: var(--device-core-fg);
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
}
|
||||
.dev .value-badge.unavailable,
|
||||
.dev .value-badge.missing { opacity: 0.66; }
|
||||
.dev .lqi {
|
||||
position: absolute;
|
||||
top: calc(50% + var(--device-shell-size) / 2 + var(--dev-size) * .05);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin-top: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.05);
|
||||
font-size: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.38);
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
text-shadow: 0 0 3px rgba(0, 0, 0, 0.9), 0 0 2px rgba(0, 0, 0, 0.9);
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
}
|
||||
.dev .lqi.below-value-badge {
|
||||
margin-top: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.8875);
|
||||
}
|
||||
.temprange {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-3);
|
||||
margin-left: auto;
|
||||
color: var(--hp-muted);
|
||||
font-size: var(--fs-s);
|
||||
}
|
||||
/* live vacuum: a round puck, no badge plate, soft pulse (docs/VACUUM.md) */
|
||||
.vacpuck {
|
||||
position: absolute;
|
||||
/* the base badge, but round and 20% smaller — the owner's wording:
|
||||
«иконка похожа на иконку базы, только круглая и чуть меньше» */
|
||||
--puck-size: calc(var(--device-base-size, 2.25cqw) * 0.8);
|
||||
width: var(--puck-size);
|
||||
height: var(--puck-size);
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
--vac-core-bg: light-dark(#fff, #252525);
|
||||
--vac-core-fg: light-dark(#252525, #fff);
|
||||
background: var(--vac-core-bg);
|
||||
border: 1px solid #BCBCBC;
|
||||
box-shadow:
|
||||
0 1px 2px rgb(37 40 45 / 12%),
|
||||
0 4px 8px -1.07px rgb(37 40 45 / 18%);
|
||||
color: var(--vac-core-fg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 6;
|
||||
/* glide between sparse position updates; .jump disables it so the robot
|
||||
never appears to drive through walls after a data gap */
|
||||
transition: left 1.2s linear, top 1.2s linear;
|
||||
animation: vacpulse 2.2s ease-out infinite;
|
||||
}
|
||||
.vacpuck.theme-light { --vac-core-bg: #fff; --vac-core-fg: #252525; }
|
||||
.vacpuck.theme-dark { --vac-core-bg: #252525; --vac-core-fg: #fff; }
|
||||
.vacpuck.jump { transition: none; }
|
||||
.vacpuck.stale { opacity: 0.45; animation: none; }
|
||||
.vacpuck ha-icon {
|
||||
--mdc-icon-size: calc(var(--puck-size) * 0.68);
|
||||
color: var(--vac-core-fg);
|
||||
/* same centering recipe as .dev ha-icon: without flex + line-height 0
|
||||
the glyph sits on its text baseline and appears to float around the
|
||||
circle (owner report 2026-07-31) */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 0;
|
||||
width: var(--mdc-icon-size);
|
||||
height: var(--mdc-icon-size);
|
||||
}
|
||||
@keyframes vacpulse {
|
||||
0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--hp-accent) 45%, transparent); }
|
||||
70% { box-shadow: 0 0 0 12px transparent; }
|
||||
100% { box-shadow: 0 0 0 0 transparent; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.vacpuck { animation: none; }
|
||||
}
|
||||
.vactrail {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
overflow: visible;
|
||||
}
|
||||
.vactrail polyline,
|
||||
.vactrail path {
|
||||
fill: none;
|
||||
stroke-linejoin: round;
|
||||
stroke-linecap: round;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
/* dark halo + light core: neutral, and one of the two always contrasts
|
||||
with whatever fill is underneath */
|
||||
.vactrail g.prev { opacity: 0.4; }
|
||||
.vactrail .case {
|
||||
stroke: rgba(0, 0, 0, 0.4);
|
||||
stroke-width: 2.25;
|
||||
}
|
||||
.vactrail .core {
|
||||
stroke: rgba(255, 255, 255, 0.82);
|
||||
stroke-width: 0.9;
|
||||
}
|
||||
.devicepreview-empty {
|
||||
min-height: 82px;
|
||||
margin: 4px 0 14px;
|
||||
padding: var(--sp-4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--sp-3);
|
||||
border: 1px dashed var(--hp-line);
|
||||
border-radius: var(--rad-m);
|
||||
color: var(--hp-muted);
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
Reference in New Issue
Block a user