Files
houseplan-card/src/styles.ts
T
Matysh 8db6b2673f room card: the name never moves, the settings button sits at the bottom
The label box is centred on the room point, so anything taking part in its
layout SHIFTS THE NAME: entering the plan editor pushed it down by the gear
button's height (owner's report). The name is the anchor now — the metrics
and the gear button hang below it as absolutes, outside the centring math —
so the name renders in exactly the same spot in view mode and in the editor,
and the settings button is the bottom row of the card. Verified by measuring
the name's vb-coordinates in both modes: identical to the pixel.
2026-07-29 08:12:51 +03:00

1564 lines
42 KiB
TypeScript

/** Styles of the House Plan card (extracted from the component). */
import { css } from 'lit';
export const cardStyles = css`
:host {
--hp-bg: var(--card-background-color, #16212e);
--hp-line: var(--divider-color, #2b3d4f);
--hp-txt: var(--primary-text-color, #e6edf3);
--hp-muted: var(--secondary-text-color, #8aa0b3);
--hp-accent: var(--primary-color, #3ea6ff);
--hp-on: #ffd45c;
--hp-open: #ff9f43;
}
ha-card {
overflow: visible; /* overflow:hidden breaks position:sticky on the header */
}
.empty {
padding: 40px 24px;
color: var(--hp-txt);
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.empty .big {
--mdc-icon-size: 56px;
color: var(--hp-accent);
opacity: 0.7;
}
.empty .muted {
color: var(--hp-muted);
font-size: 13px;
margin: 0;
}
.empty .btn {
margin-top: 8px;
}
.hdr {
position: sticky;
top: var(--header-height, 56px);
z-index: 20;
background: var(--card-background-color, var(--hp-bg));
border-radius: var(--ha-card-border-radius, 12px) var(--ha-card-border-radius, 12px) 0 0;
}
.head {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
border-bottom: 1px solid var(--hp-line);
flex-wrap: wrap;
}
.title {
font-size: 15px;
font-weight: 600;
display: flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.title ha-icon {
color: var(--hp-accent);
--mdc-icon-size: 18px;
}
.tabs {
display: flex;
gap: 4px;
background: rgba(127, 127, 127, 0.12);
padding: 3px;
border-radius: 10px;
flex-wrap: wrap;
}
@media (max-width: 620px) {
.head { gap: 6px; padding: 8px 10px; }
.head .count { display: none; }
.head .title { font-size: 14px; }
}
.tab {
border: 0;
background: transparent;
color: var(--hp-muted);
display: inline-flex;
align-items: center;
padding: 6px 13px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: 0.15s;
font-family: inherit;
}
.tab:hover {
color: var(--hp-txt);
}
.tab.active {
background: var(--hp-accent);
color: var(--text-primary-color, #fff);
}
.count {
font-size: 12px;
color: var(--hp-muted);
}
.spacer {
flex: 1;
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
border: 1px solid var(--hp-line);
background: transparent;
color: var(--hp-txt);
padding: 6px 10px;
border-radius: 8px;
cursor: pointer;
transition: 0.15s;
font-family: inherit;
font-size: 12.5px;
}
.btn ha-icon {
--mdc-icon-size: 17px;
}
.btn:hover {
border-color: var(--hp-accent);
}
.btn.on {
background: var(--hp-accent);
color: var(--text-primary-color, #fff);
border-color: var(--hp-accent);
}
.btn.ghost {
border: none;
}
.btn[disabled] {
opacity: 0.5;
pointer-events: none;
}
.stage.noplan {
background: #ffffff;
}
.stage {
position: relative;
width: 100%;
container-type: inline-size;
overflow: hidden;
touch-action: none; /* custom pinch/pan gestures */
background: var(--ha-card-background, var(--card-background-color, #111));
}
.zoomwrap {
position: absolute;
inset: 0;
}
.zoomctl {
display: inline-flex;
gap: 2px;
background: rgba(127, 127, 127, 0.12);
border-radius: 8px;
padding: 2px;
}
.zoomctl .zb {
border: none;
padding: 5px 7px;
}
.zoomctl .zb[disabled] {
opacity: 0.4;
pointer-events: none;
}
.zoombadge {
position: absolute;
left: 8px;
bottom: 8px;
background: var(--card-background-color, var(--hp-bg));
opacity: 0.92;
color: var(--hp-txt);
border: 1px solid var(--hp-accent);
border-radius: 8px;
padding: 2px 8px;
font-size: 12px;
font-weight: 600;
pointer-events: none;
}
.stage svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
}
.room {
transition: 0.12s;
cursor: default; /* v1.40.1: rooms are not clickable — the label's link icon is */
}
.room.overlay {
fill: transparent;
stroke: transparent;
stroke-width: 2;
}
.room.overlay:not(.styled):hover {
fill: #9aa0a6;
fill-opacity: 0.22;
stroke: var(--hp-muted);
}
.room.yard {
fill: rgba(75, 140, 90, 0.14);
stroke: #4b8c5a;
stroke-width: 2;
}
.room.yard:not(.styled):hover {
fill: rgba(75, 140, 90, 0.24);
stroke: #6fbf86;
}
.room.styled {
stroke: var(--room-stroke, transparent);
stroke-opacity: var(--room-stroke-op, 0);
stroke-width: 2.5;
fill: var(--room-fill, transparent);
fill-opacity: var(--room-fill-op, 0);
}
/* hover: darken the current fill instead of recoloring; grey when unfilled */
.room.styled.filled:hover {
filter: brightness(0.78);
stroke-opacity: 1;
}
.room.styled:not(.filled):hover {
fill: #9aa0a6;
fill-opacity: 0.22;
stroke-opacity: 1;
}
/* doors & windows */
.op-leaf {
transition: transform 0.6s ease;
}
.op-arc {
stroke-width: 1.5;
transition: stroke-dashoffset 0.6s ease;
}
/* hover affordance: a rounded outline hugging the wall strip + a grab cursor */
.op-outline {
fill: none;
stroke: var(--hp-accent);
stroke-width: 1.5;
opacity: 0;
transition: opacity 0.15s;
pointer-events: none;
}
.stage.markup g.opening:hover .op-outline {
opacity: 0.9;
}
/* openings are pure status graphics outside Plan mode: no cursor, no hover,
no hit target — View must not interact with them at all */
.op-hit {
fill: transparent;
pointer-events: none;
cursor: default;
}
.stage.markup .op-hit {
pointer-events: auto;
cursor: grab;
touch-action: none; /* drags, not scrolls, on touch */
}
.stage.markup .op-hit:active {
cursor: grabbing;
}
.oplock {
pointer-events: none; /* inert while editing; clickable in View (rule below) */
position: absolute;
transform: translate(-50%, -50%);
width: calc(var(--icon-size, 2.5cqw) * 0.62);
height: calc(var(--icon-size, 2.5cqw) * 0.62);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: var(--hp-bg);
border: 1px solid var(--hp-line);
z-index: 1;
}
.stage.mode-view .oplock {
pointer-events: auto;
cursor: pointer;
}
.oplock ha-icon {
--mdc-icon-size: calc(var(--icon-size, 2.5cqw) * 0.4);
display: flex;
line-height: 0;
}
.oplock.locked { color: #66d17a; border-color: #66d17a; }
.oplock.unlocked { color: var(--hp-open); border-color: var(--hp-open); }
.oplock.unknown { color: var(--hp-muted); }
.btn.lockact {
width: 100%;
justify-content: center;
display: flex;
align-items: center;
gap: 6px;
margin-top: 8px;
}
.btn.lockact.warn {
color: var(--error-color, #d33);
border-color: var(--error-color, #d33);
}
.oprow {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 0;
}
.oprow b { margin-left: auto; }
.oprow.ok b { color: #66d17a; }
.oprow.warn b { color: var(--hp-open); }
@media (prefers-reduced-motion: reduce) {
.op-leaf, .op-arc { transition: none; }
}
/* presence ripples: opted into per device, drawn around the anchor point */
.dev.noicon {
background: transparent;
border-color: transparent;
box-shadow: none;
}
.dev ha-icon {
position: relative;
z-index: 1;
}
.ripple {
position: absolute;
left: 50%;
top: 50%;
width: calc(var(--dev-size) * var(--ripple-scale, 3));
height: calc(var(--dev-size) * var(--ripple-scale, 3));
transform: translate(-50%, -50%);
pointer-events: none;
z-index: 0;
}
.ripple i {
position: absolute;
inset: 0;
border-radius: 50%;
border: 2px solid var(--ripple-color, var(--hp-accent));
opacity: 0;
}
.ripple.active i {
animation: hp-ripple 2.4s ease-out infinite;
}
.ripple.active i:nth-child(2) { animation-delay: 0.8s; }
.ripple.active i:nth-child(3) { animation-delay: 1.6s; }
/* idle: a faint dot keeps the spot marked without pulling the eye */
.ripple:not(.active) i:nth-child(n + 2) { display: none; }
.ripple:not(.active) i {
inset: calc(50% - 0.15 * var(--dev-size));
opacity: 0.3;
animation: none;
}
@keyframes hp-ripple {
0% { transform: scale(0.18); opacity: 0.7; }
70% { opacity: 0.22; }
100% { transform: scale(1); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.ripple.active i { animation: none; opacity: 0.3; }
.ripple.active i:nth-child(n + 2) { display: none; }
}
.roomlabel {
pointer-events: none; /* draggable only in plan mode (rule below) */
position: absolute;
transform: translate(-50%, -50%);
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;
letter-spacing: 0.04em;
white-space: nowrap;
cursor: grab;
user-select: none;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.15em;
text-align: center;
}
.rlname {
display: inline-flex;
align-items: center;
gap: 0.25em;
font-size: calc(1em * var(--rl-name, 1));
}
/* Switching spaces by swipe or on the kiosk carousel: the plan flies out
the way the finger went and the next one arrives from the other side. */
@keyframes hp-slide-left {
0% { transform: translateX(22%); opacity: 0; }
100% { transform: translateX(0); opacity: 1; }
}
@keyframes hp-slide-right {
0% { transform: translateX(-22%); opacity: 0; }
100% { transform: translateX(0); opacity: 1; }
}
.zoomwrap.slide-left { animation: hp-slide-left 0.26s cubic-bezier(0.22, 0.61, 0.36, 1); }
.zoomwrap.slide-right { animation: hp-slide-right 0.26s cubic-bezier(0.22, 0.61, 0.36, 1); }
@media (prefers-reduced-motion: reduce) {
.zoomwrap.slide-left, .zoomwrap.slide-right { animation: none; }
}
/* The name is the anchor: the label box is centred on the room point, so
anything that takes part in its layout SHIFTS THE NAME. The gear button
and the metrics hang below as absolutes — the name renders in exactly
the same place in view mode and in the plan editor (owner's request),
and the button sits at the very bottom of the card. */
.rlgearbtn {
position: absolute;
top: calc(100% + 0.35em);
left: 50%;
transform: translateX(-50%);
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 8px;
border: 0;
border-radius: 999px;
background: var(--hp-accent);
color: var(--text-primary-color, #fff);
font: inherit;
font-size: 11px;
font-weight: 600;
line-height: 1;
cursor: pointer;
pointer-events: auto;
opacity: 0.92;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
}
.rlgearbtn { transition: opacity 0.15s, filter 0.15s; }
.rlgearbtn:hover { opacity: 1; filter: brightness(1.18); }
.rlgearbtn ha-icon { --mdc-icon-size: 14px; display: inline-flex; }
.rlgear {
--mdc-icon-size: 0.9em;
display: inline-flex;
margin-right: 0.2em;
opacity: 0.6;
cursor: pointer;
pointer-events: auto;
}
.rlgear:hover { opacity: 1; }
.rlgo {
--mdc-icon-size: 0.85em;
display: inline-flex;
opacity: 0.55;
}
.stage.mode-view .rlgo {
pointer-events: auto;
cursor: pointer;
}
.stage.mode-view .rlgo:hover { opacity: 1; }
.roomlabel .rlmetrics {
position: absolute; /* below the name, outside the centring math */
top: calc(100% + 0.15em);
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
display: flex;
align-items: center;
gap: 0.55em;
font-size: calc(0.75em * var(--rl-meta, 1)); /* feedback: 0.62 was unreadable on a tablet */
font-weight: 600;
letter-spacing: 0.02em;
opacity: 0.9;
}
.roomlabel .rlm {
display: inline-flex;
align-items: center;
gap: 0.12em;
}
.roomlabel .rlm ha-icon {
--mdc-icon-size: 1.05em;
display: inline-flex;
}
.roomlabel .rlm.lit { opacity: 1; }
.bindharow {
display: flex;
align-items: center;
gap: 14px;
flex-wrap: wrap;
}
.bindharow .entcheck { opacity: 0.9; }
.dropbtn {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
text-align: left;
border: 1px solid var(--hp-muted);
border-radius: 8px;
background: transparent;
color: var(--hp-txt);
padding: 8px 10px;
cursor: pointer;
font-family: inherit;
font-size: 13px;
margin-top: 6px;
}
.dropbtn .ref { color: var(--hp-muted); font-size: 11px; margin-left: auto; }
.dropbtn ha-icon { --mdc-icon-size: 18px; margin-left: 4px; }
.dropbtn.open { border-color: var(--hp-accent); }
.droppanel {
border: 1px solid var(--hp-accent);
border-top: none;
border-radius: 0 0 8px 8px;
padding: 6px;
margin-top: -4px;
}
.ctrlchips { display: flex; flex-wrap: wrap; gap: 5px; margin: 4px 0; }
.ctrlchip {
display: inline-flex; align-items: center; gap: 4px;
background: var(--hp-accent); color: var(--text-primary-color, #fff);
border-radius: 12px; padding: 3px 8px; font-size: 12px;
}
.ctrlchip ha-icon { --mdc-icon-size: 14px; cursor: pointer; }
.ctrllist { display: flex; flex-direction: column; gap: 2px; margin-top: 4px; }
.ctrlopt {
display: flex; align-items: center; gap: 7px; text-align: left;
border: 0; background: transparent; color: var(--hp-txt);
padding: 5px 7px; border-radius: 6px; cursor: pointer; font-family: inherit; font-size: 13px;
}
.ctrlopt:hover { background: var(--secondary-background-color, rgba(128,128,128,0.15)); }
.ctrlopt .sub { color: var(--hp-muted); font-size: 11px; margin-left: auto; }
.ctrlopt ha-icon { --mdc-icon-size: 16px; }
.ctrlstates { display: flex; flex-direction: column; gap: 3px; }
.ctrlstate { display: inline-flex; align-items: center; gap: 5px; color: var(--hp-muted); }
.ctrlstate.on { color: var(--hp-txt); }
.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 {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
margin: 4px 0 0;
}
.iconauto ha-icon { --mdc-icon-size: 18px; }
.rlhandle {
display: none;
position: absolute;
width: 9px;
height: 9px;
border-radius: 2px;
background: var(--hp-accent);
border: 1px solid var(--card-background-color, #fff);
z-index: 2;
}
.rlhandle.tl { left: -6px; top: -6px; cursor: nwse-resize; }
.rlhandle.br { right: -6px; bottom: -6px; cursor: nwse-resize; }
.rlhandle.tr { right: -6px; top: -6px; cursor: nesw-resize; }
.rlhandle.bl { left: -6px; bottom: -6px; cursor: nesw-resize; }
.stage.markup .roomlabel:hover .rlhandle { display: block; }
.stage.markup .roomlabel { pointer-events: auto; }
.roomlabel:active { cursor: grabbing; }
.measurelayer {
position: absolute;
inset: 0;
pointer-events: none;
}
.alignline {
stroke: var(--hp-accent);
stroke-width: 1.2;
stroke-dasharray: 4 4;
pointer-events: none;
opacity: 0.9;
}
.aligndot {
fill: var(--hp-accent);
pointer-events: none;
}
.measurelabel.on45 {
color: #4bd28f;
border-color: #4bd28f;
}
.hdr.kioskhide { display: none; }
.kioskdots {
position: absolute;
left: 50%;
bottom: 14px;
transform: translateX(-50%);
display: flex;
gap: 8px;
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%);
font-size: 12px;
font-weight: 600;
padding: 1px 6px;
border-radius: 6px;
background: rgba(0, 0, 0, 0.72);
color: #fff;
white-space: nowrap;
pointer-events: none;
user-select: none;
z-index: 3;
}
/* decor (background) layer */
.decorlayer .dshape { pointer-events: none; }
.stage.mode-decor .decorlayer .dshape {
pointer-events: visiblePainted;
cursor: pointer;
}
.stage.mode-decor.dtool-select .decorlayer .dshape { cursor: move; }
.decorlayer .dsel {
filter: drop-shadow(0 0 3px var(--hp-accent));
}
.decorlayer .ddraft {
opacity: 0.75;
stroke-dasharray: 6 5;
pointer-events: none;
}
.decorlayer text {
font-weight: 600;
user-select: none;
dominant-baseline: middle;
text-anchor: middle;
}
.stage.mode-decor {
outline: 2px solid #26a69a;
outline-offset: -2px;
}
.stage.mode-decor.dtool-line, .stage.mode-decor.dtool-rect,
.stage.mode-decor.dtool-ellipse, .stage.mode-decor.dtool-text {
cursor: crosshair;
}
.stage.mode-decor .room, .stage.mode-decor .devlayer { pointer-events: none; }
.stage.mode-decor .oplock { pointer-events: none; }
/* decor mode: everything but the decor itself fades back */
.stage.mode-decor .room,
.stage.mode-decor .devlayer,
.stage.mode-decor .opening,
.stage.mode-decor .rlabel {
opacity: 0.35;
}
.decorbar .dcolor {
width: 30px; height: 26px; padding: 0; border: none; background: none; cursor: pointer;
}
.decorbar .dwidth {
font-family: inherit; font-size: 12px; border-radius: 6px;
background: var(--hp-bg2, transparent); color: var(--hp-txt); border: 1px solid var(--hp-muted);
padding: 3px 5px;
}
.decorbar .dfill {
display: inline-flex; align-items: center; gap: 4px; font-size: 12px; cursor: pointer;
}
.opghost {
stroke: var(--hp-open, #ff9800);
stroke-width: 5;
stroke-linecap: round;
stroke-dasharray: 7 6;
opacity: 0.85;
pointer-events: none;
}
.opghost-dot {
fill: var(--hp-open, #ff9800);
opacity: 0.85;
pointer-events: none;
}
.rlabel {
fill: var(--hp-muted);
font-size: 15px;
font-weight: 600;
pointer-events: none;
text-anchor: middle;
}
.stage.edit .room {
pointer-events: none;
}
.stage.markup {
cursor: crosshair;
}
/* room-picking stages: merge (both clicks) and split before a room is chosen */
.stage.markup.tool-merge,
.stage.markup.tool-split.pickstage,
.stage.markup.tool-delroom {
cursor: pointer;
}
/* open-wall tool: default until a shared wall is under the cursor */
.stage.markup.tool-openwall { cursor: default; }
.stage.markup.tool-openwall.wallhot { cursor: pointer; }
.openwall {
stroke: var(--ow-stroke, var(--hp-muted));
stroke-width: 2.5;
stroke-dasharray: 7 7;
stroke-linecap: butt;
pointer-events: none;
opacity: 0.9;
}
/* rooms with open stretches: the polygon's own stroke is fully off
(hover included) — the trimmed .room-outline path draws the walls */
.room.noedge {
stroke-opacity: 0 !important;
}
.room-outline {
fill: none;
stroke-width: 2.5;
pointer-events: none;
}
/* Plan editor: trimmed outlines use the markup blue */
.room-outline.outlined {
stroke: rgba(62, 166, 255, 0.55);
stroke-opacity: 1;
}
.openwalls.hot .openwall {
stroke: #ffc14d;
opacity: 1;
}
.openwall-preview {
stroke: #ffc14d;
stroke-width: 5;
stroke-dasharray: 7 7;
stroke-linecap: round;
pointer-events: none;
opacity: 0.95;
}
/* an already-open boundary under the cursor: the click will CLOSE it */
.openwall-preview.willclose {
stroke: #f25a4a;
stroke-dasharray: none;
}
.stage.markup .room {
pointer-events: none;
}
.stage.markup .devlayer .dev {
display: none; /* in plan mode the icons do not get in the way; labels stay */
}
/* mode frames: the edit modes are visible at a glance */
.stage.mode-plan {
outline: 2px solid #ffc14d;
outline-offset: -2px;
}
.stage.mode-devices {
outline: 2px solid var(--hp-accent);
outline-offset: -2px;
}
.modes {
display: inline-flex;
gap: 2px;
background: rgba(127, 127, 127, 0.12);
border-radius: 10px;
padding: 3px;
}
.modetab {
display: inline-flex;
align-items: center;
gap: 5px;
border: 0;
background: transparent;
color: var(--hp-muted);
padding: 5px 10px;
border-radius: 8px;
font-size: 12.5px;
font-weight: 600;
cursor: pointer;
font-family: inherit;
}
.modetab ha-icon { --mdc-icon-size: 15px; }
.modetab .closex {
--mdc-icon-size: 13px;
display: inline-flex;
align-items: center;
margin-left: 2px;
opacity: 0.75;
cursor: pointer;
border-radius: 4px;
}
.modetab .closex:hover { opacity: 1; }
.editbar .barclose {
padding: 4px 6px;
margin-left: 6px;
}
.modetab.active {
background: var(--hp-accent);
color: var(--text-primary-color, #fff);
}
@media (max-width: 720px) {
.modetab .ml { display: none; }
}
.room.outlined {
stroke: rgba(62, 166, 255, 0.55);
fill: rgba(62, 166, 255, 0.06);
}
/* AFTER .outlined: same specificity — source order decides (gotcha x4) */
.room.picked {
stroke: #ffc14d;
stroke-width: 3;
fill: rgba(255, 193, 77, 0.25);
}
.griddot {
fill: var(--hp-accent);
opacity: 0.75;
stroke: rgba(0, 0, 0, 0.35);
stroke-width: 0.4;
}
.seg {
stroke: var(--hp-accent);
stroke-width: 2.5;
stroke-linecap: round;
}
.pathline {
stroke: #ffc14d;
stroke-width: 3;
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
.preview {
stroke: #ffc14d;
stroke-width: 2;
stroke-dasharray: 6 5;
opacity: 0.7;
}
.vertex {
fill: #ffc14d;
stroke: #4a2800;
stroke-width: 1;
}
.vertex.first {
fill: #4bd28f;
stroke: #04121f;
}
.areasel,
.namein {
background: var(--hp-bg);
border: 1px solid var(--hp-line);
color: var(--hp-txt);
border-radius: 6px;
padding: 6px 8px;
font-size: 13px;
font-family: inherit;
}
.namein {
width: 130px;
}
.dev.valonly {
width: auto;
min-width: var(--dev-size, var(--icon-size, 2.5cqw));
padding: 0 calc(var(--icon-size, 2.5cqw) * 0.16);
}
.dev.valonly .valtext {
font-size: calc(var(--icon-size, 2.5cqw) * 0.45);
font-weight: 700;
white-space: nowrap;
}
/* RGB lights: the bulb takes the light's actual color */
.dev.rgb ha-icon { color: var(--light-color); }
.dev.rgb.on {
box-shadow: 0 0 10px var(--light-color);
border-color: var(--light-color);
background: var(--hp-bg);
color: var(--light-color);
}
/* alarms pulse red over everything */
.dev.alarm::after {
content: '';
position: absolute;
inset: calc(var(--icon-size, 2.5cqw) * -0.35);
border: 3px solid #f25a4a;
border-radius: 50%;
animation: hp-alarm 1s ease-out infinite;
pointer-events: none;
}
@keyframes hp-alarm {
0% { transform: scale(0.7); opacity: 1; }
100% { transform: scale(1.25); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.dev.alarm::after { animation: none; opacity: 0.9; }
}
.dev .newdot {
position: absolute;
top: calc(var(--icon-size, 2.5cqw) * -0.12);
right: calc(var(--icon-size, 2.5cqw) * -0.12);
width: calc(var(--icon-size, 2.5cqw) * 0.34);
height: calc(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;
}
.dev {
position: absolute;
/* per-device multiplier on top of the card-wide icon size */
--dev-size: calc(var(--icon-size, 2.5cqw) * var(--dev-scale, 1));
/* центр квадрата (включая рамку 1px) точно на точке привязки: -(size/2 + border) */
width: var(--dev-size);
height: var(--dev-size);
margin: calc(var(--dev-size) / -2 - 1px) 0 0 calc(var(--dev-size) / -2 - 1px);
border-radius: 22%;
background: var(--hp-bg);
border: 1px solid var(--hp-line);
display: flex;
align-items: center;
justify-content: center;
color: var(--hp-txt);
cursor: pointer;
pointer-events: auto;
transition: background 0.15s, border-color 0.15s, opacity 0.2s;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
z-index: 2;
}
.dev ha-icon {
--mdc-icon-size: calc(var(--icon-size, 2.5cqw) * 0.62);
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:hover {
background: var(--hp-accent);
color: var(--text-primary-color, #fff);
z-index: 5;
}
.dev.on {
background: var(--hp-on);
border-color: var(--hp-on);
color: #503c00;
box-shadow: 0 0 8px rgba(255, 212, 92, 0.7);
}
.dev.open {
background: var(--hp-open);
border-color: var(--hp-open);
color: #4a2800;
}
.dev.unavail {
opacity: 0.35;
}
.dev.virtual {
border-style: dashed;
}
.dev.sel {
border-color: #ffc14d;
box-shadow: 0 0 0 3px rgba(255, 193, 77, 0.35);
}
.dev .tval {
position: absolute;
left: 100%;
top: 50%;
transform: translateY(-50%);
margin-left: calc(var(--icon-size, 2.5cqw) * 0.1);
background: var(--card-background-color, var(--hp-bg));
border: 1px solid var(--hp-accent);
border-radius: calc(var(--icon-size, 2.5cqw) * 0.18);
padding: 0 calc(var(--icon-size, 2.5cqw) * 0.14);
font-size: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.45);
font-weight: 700;
line-height: calc(var(--icon-size, 2.5cqw) * 0.68);
color: var(--hp-txt);
white-space: nowrap;
pointer-events: none;
}
.dev .hval {
position: absolute;
left: 100%;
top: 50%;
transform: translateY(-50%);
margin-left: calc(var(--icon-size, 2.5cqw) * 0.1);
background: var(--card-background-color, var(--hp-bg));
border: 1px solid #4fc3f7;
border-radius: calc(var(--icon-size, 2.5cqw) * 0.18);
padding: 0 calc(var(--icon-size, 2.5cqw) * 0.14);
font-size: calc(var(--dev-size, var(--icon-size, 2.5cqw)) * 0.45);
font-weight: 700;
line-height: calc(var(--icon-size, 2.5cqw) * 0.68);
color: var(--hp-txt);
white-space: nowrap;
pointer-events: none;
}
.dev .lqi {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
margin-top: calc(var(--icon-size, 2.5cqw) * 0.05);
font-size: calc(var(--icon-size, 2.5cqw) * 0.38);
font-weight: 700;
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;
}
.editbar {
display: flex;
align-items: center;
gap: 10px;
padding: 9px 14px;
border-bottom: 1px solid var(--hp-line);
font-size: 13px;
flex-wrap: wrap;
}
.tab .tabedit {
--mdc-icon-size: 13px;
display: inline-flex;
align-items: center;
margin-left: 6px;
opacity: 0.4;
}
.tab:hover .tabedit {
opacity: 0.9;
}
.tab.tabadd {
padding: 6px 8px;
}
.tab.tabadd ha-icon {
--mdc-icon-size: 15px;
}
.srcrow {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
cursor: pointer;
padding: 2px 0;
}
.dispsection {
margin-top: 12px !important;
padding-top: 8px;
border-top: 1px solid var(--hp-line);
font-weight: 600;
color: var(--hp-txt) !important;
}
.colorrow {
display: flex;
align-items: center;
gap: 8px;
}
.colorrow input[type='color'] {
width: 42px;
height: 28px;
border: 1px solid var(--hp-line);
border-radius: 6px;
background: transparent;
padding: 1px;
cursor: pointer;
}
.colorrow input[type='range'] { flex: 1; }
.colorrow .tempin { width: 70px; flex: none; }
.temprange {
display: inline-flex;
align-items: center;
gap: 5px;
margin-left: auto;
color: var(--hp-muted);
font-size: 12px;
}
/* beat the generic .dialog .body .namein { width:100% } rule */
.dialog .body .temprange .tempin { width: 56px; flex: none; padding: 3px 6px; }
.dialog .body .colorrow .tempin { width: 72px; flex: none; }
.srcrow { flex-wrap: nowrap; }
.srcrow > span:first-of-type { white-space: nowrap; }
.colorrow .opl { color: var(--hp-muted); font-size: 12px; }
.colorrow .opv { font-size: 12px; min-width: 34px; text-align: right; }
.planrow {
display: flex;
align-items: center;
gap: 10px;
}
/* the "already uploaded" picker: a plan is never deleted for being
unreferenced, so it has to be findable again */
.savedplans {
display: flex;
flex-direction: column;
gap: 6px;
max-height: 240px;
overflow: auto;
margin: 6px 0 2px;
padding: 6px;
border: 1px solid var(--hp-line);
border-radius: 8px;
background: var(--hp-bg2, rgba(255, 255, 255, 0.03));
}
.savedplan {
display: flex;
align-items: center;
gap: 10px;
}
.savedplan.cur { outline: 1px solid var(--hp-accent); border-radius: 6px; }
.savedplan img {
width: 56px;
height: 40px;
object-fit: contain;
border: 1px solid var(--hp-line);
border-radius: 4px;
background: #fff;
flex: none;
}
.savedmeta { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.savedmeta b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.savedmeta .muted { font-size: 11px; }
.savedplan .btn.danger ha-icon { color: #f25a4a; }
.savedplan .btn[disabled] { opacity: 0.4; pointer-events: none; }
.planprev {
max-width: 120px;
max-height: 70px;
border: 1px solid var(--hp-line);
border-radius: 6px;
background: #fff;
}
.planname {
font-size: 12.5px;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.planname.muted {
color: var(--hp-muted);
}
.filebtn {
cursor: pointer;
}
.btn.danger {
border-color: #b3402a;
color: #ff7a5c;
}
.dialog .row .spacer {
flex: 1;
}
.dialog.wide {
width: min(500px, 94vw);
}
.dialog .body {
max-height: 66vh;
overflow-y: auto;
}
.descin {
width: 100%;
box-sizing: border-box;
background: var(--hp-bg);
border: 1px solid var(--hp-line);
color: var(--hp-txt);
border-radius: 6px;
padding: 6px 8px;
font-size: 13px;
font-family: inherit;
resize: vertical;
/* flex column of the dialog body squeezes textareas — keep a usable height */
min-height: 92px;
flex-shrink: 0;
line-height: 1.35;
}
.bindsel {
display: flex;
flex-direction: column;
gap: 6px;
border: 1px solid var(--hp-line);
border-radius: 8px;
padding: 8px;
}
.bindsel .opt {
display: flex;
align-items: center;
gap: 6px;
border: 1px solid var(--hp-line);
background: transparent;
color: var(--hp-txt);
border-radius: 6px;
padding: 6px 8px;
cursor: pointer;
font-size: 12.5px;
font-family: inherit;
}
.bindsel .opt.on {
background: var(--hp-accent);
color: var(--text-primary-color, #fff);
border-color: var(--hp-accent);
}
.curbind {
display: flex;
align-items: center;
gap: 6px;
font-size: 12.5px;
color: var(--hp-txt);
flex-wrap: wrap;
}
.curbind .ref {
color: var(--hp-muted);
font-size: 11px;
}
.candlist {
max-height: 160px;
overflow-y: auto;
border-top: 1px solid var(--hp-line);
}
.cand {
display: flex;
justify-content: space-between;
gap: 8px;
padding: 6px 8px;
cursor: pointer;
border-radius: 6px;
font-size: 12.5px;
}
.cand:hover {
background: rgba(127, 127, 127, 0.15);
}
.cand.sel {
background: var(--hp-accent);
color: var(--text-primary-color, #fff);
}
.cand .cs {
color: var(--hp-muted);
font-size: 11px;
white-space: nowrap;
}
.cand.sel .cs {
color: var(--text-primary-color, #fff);
opacity: 0.85;
}
.cand.muted {
color: var(--hp-muted);
cursor: default;
}
.pdfedit {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
}
.pdftag {
display: inline-flex;
align-items: center;
gap: 4px;
border: 1px solid var(--hp-line);
border-radius: 6px;
padding: 3px 6px;
font-size: 12px;
}
.pdftag a {
color: var(--hp-txt);
text-decoration: none;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pdftag .x {
--mdc-icon-size: 15px;
cursor: pointer;
color: var(--hp-muted);
}
.pdftag .x:hover {
color: #ff7a5c;
}
.entlist {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 10px;
}
.entrow {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 8px;
border-radius: 8px;
background: var(--secondary-background-color, rgba(128, 128, 128, 0.12));
}
.entrow ha-icon { --mdc-icon-size: 20px; color: var(--hp-muted); }
.entrow.on ha-icon { color: var(--hp-accent); }
.entrow .en { flex: 1; font-size: 13px; }
.entrow .ev { font-size: 13px; color: var(--hp-muted); }
.entbtn {
min-width: 74px;
min-height: 32px;
padding: 4px 12px;
border: 1px solid var(--hp-muted);
border-radius: 999px;
background: transparent;
color: var(--hp-txt);
font: inherit;
font-size: 13px;
cursor: pointer;
}
.entbtn.on {
background: var(--hp-accent);
border-color: var(--hp-accent);
color: var(--text-primary-color, #fff);
font-weight: 600;
}
.inforow {
display: flex;
gap: 10px;
font-size: 13px;
margin: 3px 0;
}
.inforow .k {
color: var(--hp-muted);
min-width: 84px;
}
.inforow a {
color: var(--hp-accent);
word-break: break-all;
}
.infodesc {
font-size: 13px;
white-space: pre-wrap;
margin-top: 6px;
}
.infodesc.muted {
color: var(--hp-muted);
}
.pdflist {
display: flex;
flex-direction: column;
gap: 4px;
}
.pdf {
display: inline-flex;
align-items: center;
gap: 4px;
color: var(--hp-accent);
text-decoration: none;
}
ha-icon-picker {
display: block;
}
.floorrow {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 4px;
font-size: 13.5px;
cursor: pointer;
}
.floorrow .floorlvl {
color: var(--hp-muted);
font-size: 11px;
border: 1px solid var(--hp-line);
border-radius: 5px;
padding: 0 5px;
}
.importprog {
margin-left: auto;
color: var(--hp-muted);
font-size: 12px;
font-weight: 400;
}
.rhint {
font-size: 12px;
color: var(--hp-muted);
margin-bottom: 6px;
}
.rtest {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.rtest .namein { flex: 1; }
.rtest ha-icon { color: var(--hp-accent); }
.rtesticon { font-size: 11px; color: var(--hp-muted); }
.rrow {
display: flex;
align-items: center;
gap: 6px;
margin: 2px 0;
}
.rrow .rpat { flex: 2; }
.rrow .ricon { flex: 1.4; }
.rrow .rpat.bad { border-color: #ff7a5c; }
.rrow .rprev { --mdc-icon-size: 18px; color: var(--hp-txt); min-width: 18px; }
.rrow .ract {
--mdc-icon-size: 16px;
color: var(--hp-muted);
cursor: pointer;
}
.rrow .ract:hover { color: var(--hp-txt); }
.rrow .ract.del:hover { color: #ff7a5c; }
.gsrow .gsl {
min-width: 150px;
font-size: 12.5px;
color: var(--hp-muted);
}
.dialogwrap {
background: rgba(0, 0, 0, 0.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 90;
}
.dialog {
background: var(--card-background-color, var(--hp-bg));
border: 1px solid var(--hp-accent);
border-radius: 14px;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
width: min(360px, 92vw);
overflow: hidden;
}
.dialog .hd {
padding: 12px 16px;
font-weight: 600;
border-bottom: 1px solid var(--hp-line);
display: flex;
align-items: center;
gap: 8px;
}
.dialog .hd ha-icon {
color: var(--hp-accent);
}
.dialog .body {
padding: 14px 16px;
display: flex;
flex-direction: column;
gap: 6px;
}
.dialog .body label {
font-size: 12px;
color: var(--hp-muted);
margin-top: 6px;
}
.dialog .body .namein,
.dialog .body .areasel {
width: 100%;
box-sizing: border-box;
}
.dialog .row {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 12px 16px;
border-top: 1px solid var(--hp-line);
}
.editbar .warn {
color: #ffc14d;
}
.editbar .sname {
font-weight: 600;
}
.editbar input {
width: 74px;
background: transparent;
border: 1px solid var(--hp-line);
color: var(--hp-txt);
border-radius: 6px;
padding: 5px 7px;
font-size: 13px;
}
.editbar label,
.editbar .hint {
color: var(--hp-muted);
font-size: 12px;
}
.menuwrap {
position: fixed;
inset: 0;
z-index: 80;
}
.menu {
position: fixed;
background: var(--hp-bg);
border: 1px solid var(--hp-accent);
border-radius: 10px;
box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
min-width: 210px;
max-width: 300px;
overflow: hidden;
transform: translate(0, 8px);
}
.menu .hd {
padding: 8px 12px;
font-weight: 600;
font-size: 12.5px;
border-bottom: 1px solid var(--hp-line);
display: flex;
align-items: center;
gap: 6px;
}
.menu .hd ha-icon,
.menu .it.all ha-icon {
color: var(--hp-accent);
--mdc-icon-size: 16px;
}
.menu .it {
padding: 8px 12px;
font-size: 12.5px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
}
.menu .it ha-icon {
--mdc-icon-size: 16px;
color: var(--hp-muted);
}
.menu .it:hover {
background: rgba(127, 127, 127, 0.15);
}
.menu .it.all {
color: var(--hp-accent);
font-weight: 600;
}
.tip {
position: fixed;
pointer-events: none;
background: var(--hp-bg);
border: 1px solid var(--hp-accent);
color: var(--hp-txt);
padding: 6px 10px;
border-radius: 8px;
font-size: 12.5px;
box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
z-index: 99;
max-width: 260px;
}
.tip .m {
color: var(--hp-muted);
font-size: 11px;
display: block;
}
.toast {
position: fixed;
left: 50%;
bottom: 22px;
transform: translateX(-50%);
background: var(--hp-bg);
border: 1px solid var(--hp-accent);
color: var(--hp-txt);
padding: 9px 16px;
border-radius: 10px;
font-size: 13px;
box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
z-index: 120;
max-width: 90vw;
}
`;