From 58f3acbbde48eecbdb9b165e5f2cb4652714a4e2 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 28 Aug 2026 04:39:46 +0300 Subject: [PATCH] fix: junction limits are honest at the boundaries (#331) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six normative cuts, both mirrors symmetric (spec revision 3): - §2.1 node keys quantise to 1e-7 with the repository's canonicalisation formula (sign·floor(|v|·1e7+0.5)/1e7, -0 normalised) — toFixed(6) keys split one node into two on floating debris and produced two false П4 refusals on a legitimate resize (reproduced: -1e-8 vs 0). Node pairs within 2e-7 of each other (raw coordinates) are ONE node, and the node-to-wall incidence uses the same quantum. - §2.2 a ~0° wedge IS a violation: two rays leaving a node the same way are a duplicated or overlaid wall (a butt joint yields 180°, never 0°) — the worst degenerate case was invisible while 0.5° was refused. - §2.3/§2.4 the wall run is an iterative edge walk over the collinear component: no recursion (10 000 atoms answered, not RangeError), no silently dropped fork (the old .find lost every branch but the first), O(E) by construction, and collinearity is measured against the BASE segment's axis so an arc of 0.9°-per-atom pieces cannot pose as one wall. - §2.5 an exception while judging the CANDIDATE refuses the write with the junction.limit_check_failed toast (fail-closed, as the #278 guard); the baseline branch stays fail-open by design and the smoke proves the asymmetry by breaking only the second call of the deterministic pair. - §2.6 the python mirror narrows its except on the candidate side only: a genuine migration bug (TypeError) surfaces as an honest WS error, while a previous-side bug keeps the wide "no baseline" fallback — the two AC6 cases pin the asymmetry so swapped sides turn a unit red. Parity fixtures gain the new boundary classes (debris node, duplicate wall, collinear fork); four new mutants pin the filter, the key precision, the dropped branch and the fail-open hole. Issue: #331 User-Visible: yes --- .../houseplan/frontend/houseplan-card.js | 152 +++++++++--------- .../houseplan/junction_limits.py | 97 +++++++---- demo/smoke_junction_limits.mjs | 25 +++ dist/houseplan-card.js | 152 +++++++++--------- docs/CHANGELOG.md | 6 + docs/CHANGELOG.ru.md | 6 + docs/USER-GUIDE.md | 4 + docs/USER-GUIDE.ru.md | 4 + docs/images/screenshots.json | 22 +-- docs/specs/329-junction-limits.md | 5 + scripts/mutation-gate.mjs | 54 +++++++ src/houseplan-card.ts | 8 +- src/i18n/en.json | 1 + src/i18n/ru.json | 1 + src/junction-limits.ts | 69 +++++--- test/junction-limits.test.mjs | 121 ++++++++++++++ tests_backend/test_junction_limits.py | 72 ++++++++- 17 files changed, 582 insertions(+), 217 deletions(-) diff --git a/custom_components/houseplan/frontend/houseplan-card.js b/custom_components/houseplan/frontend/houseplan-card.js index 3c9b8ec8..f667da6a 100755 --- a/custom_components/houseplan/frontend/houseplan-card.js +++ b/custom_components/houseplan/frontend/houseplan-card.js @@ -1,4 +1,4 @@ -globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5b79a0695b7e36440d9e2ee2cdb8a68b4e3b0defdac56646367868f262e842f9";const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),s=new WeakMap;let n=class{constructor(e,t,s){if(this._$cssResult$=!0,s!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o;const i=this.t;if(t&&void 0===e){const t=void 0!==i&&1===i.length;t&&(e=s.get(i)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),t&&s.set(i,e))}return e}toString(){return this.cssText}};const o=(e,...t)=>{const s=1===e.length?e[0]:t.reduce((t,i,s)=>t+(e=>{if(!0===e._$cssResult$)return e.cssText;if("number"==typeof e)return e;throw Error("Value passed to 'css' function must be a 'css' function result: "+e+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+e[s+1],e[0]);return new n(s,e,i)},a=t?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const i of e.cssRules)t+=i.cssText;return(e=>new n("string"==typeof e?e:e+"",void 0,i))(t)})(e):e,{is:r,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:d,getPrototypeOf:p}=Object,u=globalThis,_=u.trustedTypes,m=_?_.emptyScript:"",g=u.reactiveElementPolyfillSupport,f=(e,t)=>e,v={toAttribute(e,t){switch(t){case Boolean:e=e?m:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let i=e;switch(t){case Boolean:i=null!==e;break;case Number:i=null===e?null:Number(e);break;case Object:case Array:try{i=JSON.parse(e)}catch(e){i=null}}return i}},b=(e,t)=>!r(e,t),y={attribute:!0,type:String,converter:v,reflect:!1,useDefault:!1,hasChanged:b};Symbol.metadata??=Symbol("metadata"),u.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=y){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(e,i,t);void 0!==s&&l(this.prototype,e,s)}}static getPropertyDescriptor(e,t,i){const{get:s,set:n}=c(this.prototype,e)??{get(){return this[t]},set(e){this[t]=e}};return{get:s,set(t){const o=s?.call(this);n?.call(this,t),this.requestUpdate(e,o,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??y}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const e=p(this);e.finalize(),void 0!==e.l&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const e=this.properties,t=[...h(e),...d(e)];for(const i of t)this.createProperty(i,e[i])}const e=this[Symbol.metadata];if(null!==e){const t=litPropertyMetadata.get(e);if(void 0!==t)for(const[e,i]of t)this.elementProperties.set(e,i)}this._$Eh=new Map;for(const[e,t]of this.elementProperties){const i=this._$Eu(e,t);void 0!==i&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const i=new Set(e.flat(1/0).reverse());for(const e of i)t.unshift(a(e))}else void 0!==e&&t.push(a(e));return t}static _$Eu(e,t){const i=t.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof e?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),void 0!==this.renderRoot&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){const e=new Map,t=this.constructor.elementProperties;for(const i of t.keys())this.hasOwnProperty(i)&&(e.set(i,this[i]),delete this[i]);e.size>0&&(this._$Ep=e)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(t)i.adoptedStyleSheets=s.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const t of s){const s=document.createElement("style"),n=e.litNonce;void 0!==n&&s.setAttribute("nonce",n),s.textContent=t.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,i){this._$AK(e,i)}_$ET(e,t){const i=this.constructor.elementProperties.get(e),s=this.constructor._$Eu(e,i);if(void 0!==s&&!0===i.reflect){const n=(void 0!==i.converter?.toAttribute?i.converter:v).toAttribute(t,i.type);this._$Em=e,null==n?this.removeAttribute(s):this.setAttribute(s,n),this._$Em=null}}_$AK(e,t){const i=this.constructor,s=i._$Eh.get(e);if(void 0!==s&&this._$Em!==s){const e=i.getPropertyOptions(s),n="function"==typeof e.converter?{fromAttribute:e.converter}:void 0!==e.converter?.fromAttribute?e.converter:v;this._$Em=s;const o=n.fromAttribute(t,e.type);this[s]=o??this._$Ej?.get(s)??o,this._$Em=null}}requestUpdate(e,t,i,s=!1,n){if(void 0!==e){const o=this.constructor;if(!1===s&&(n=this[e]),i??=o.getPropertyOptions(e),!((i.hasChanged??b)(n,t)||i.useDefault&&i.reflect&&n===this._$Ej?.get(e)&&!this.hasAttribute(o._$Eu(e,i))))return;this.C(e,t,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(e,t,{useDefault:i,reflect:s,wrapped:n},o){i&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,o??t??this[e]),!0!==n||void 0!==o)||(this._$AL.has(e)||(this.hasUpdated||i||(t=void 0),this._$AL.set(e,t)),!0===s&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[e,t]of this._$Ep)this[e]=t;this._$Ep=void 0}const e=this.constructor.elementProperties;if(e.size>0)for(const[t,i]of e){const{wrapped:e}=i,s=this[t];!0!==e||this._$AL.has(t)||void 0===s||this.C(t,void 0,i,s)}}let e=!1;const t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM()}catch(t){throw e=!1,this._$EM(),t}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(e){}firstUpdated(e){}};w.elementStyles=[],w.shadowRootOptions={mode:"open"},w[f("elementProperties")]=new Map,w[f("finalized")]=new Map,g?.({ReactiveElement:w}),(u.reactiveElementVersions??=[]).push("2.1.2");const k=globalThis,x=e=>e,$=k.trustedTypes,S=$?$.createPolicy("lit-html",{createHTML:e=>e}):void 0,M="$lit$",C=`lit$${Math.random().toFixed(9).slice(2)}$`,D="?"+C,P=`<${D}>`,T=document,I=()=>T.createComment(""),R=e=>null===e||"object"!=typeof e&&"function"!=typeof e,A=Array.isArray,z="[ \t\n\f\r]",F=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,N=/-->/g,E=/>/g,O=RegExp(`>|${z}(?:([^\\s"'>=/]+)(${z}*=${z}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),H=/'/g,L=/"/g,B=/^(?:script|style|textarea|title)$/i,j=e=>(t,...i)=>({_$litType$:e,strings:t,values:i}),q=j(1),W=j(2),U=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),V=new WeakMap,K=T.createTreeWalker(T,129);function Y(e,t){if(!A(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(t):t}const J=(e,t)=>{const i=e.length-1,s=[];let n,o=2===t?"":3===t?"":"",a=F;for(let t=0;t"===l[0]?(a=n??F,c=-1):void 0===l[1]?c=-2:(c=a.lastIndex-l[2].length,r=l[1],a=void 0===l[3]?O:'"'===l[3]?L:H):a===L||a===H?a=O:a===N||a===E?a=F:(a=O,n=void 0);const d=a===O&&e[t+1].startsWith("/>")?" ":"";o+=a===F?i+P:c>=0?(s.push(r),i.slice(0,c)+M+i.slice(c)+C+d):i+C+(-2===c?t:d)}return[Y(e,o+(e[i]||"")+(2===t?"":3===t?"":"")),s]};class X{constructor({strings:e,_$litType$:t},i){let s;this.parts=[];let n=0,o=0;const a=e.length-1,r=this.parts,[l,c]=J(e,t);if(this.el=X.createElement(l,i),K.currentNode=this.el.content,2===t||3===t){const e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;null!==(s=K.nextNode())&&r.length0){s.textContent=$?$.emptyScript:"";for(let i=0;iA(e)||"function"==typeof e?.[Symbol.iterator])(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==G&&R(this._$AH)?this._$AA.nextSibling.data=e:this.T(T.createTextNode(e)),this._$AH=e}$(e){const{values:t,_$litType$:i}=e,s="number"==typeof i?this._$AC(e):(void 0===i.el&&(i.el=X.createElement(Y(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(t);else{const e=new Q(s,this),i=e.u(this.options);e.p(t),this.T(i),this._$AH=e}}_$AC(e){let t=V.get(e.strings);return void 0===t&&V.set(e.strings,t=new X(e)),t}k(e){A(this._$AH)||(this._$AH=[],this._$AR());const t=this._$AH;let i,s=0;for(const n of e)s===t.length?t.push(i=new ee(this.O(I()),this.O(I()),this,this.options)):i=t[s],i._$AI(n),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=G}_$AI(e,t=this,i,s){const n=this.strings;let o=!1;if(void 0===n)e=Z(this,e,t,0),o=!R(e)||e!==this._$AH&&e!==U,o&&(this._$AH=e);else{const s=e;let a,r;for(e=n[0],a=0;a{const s=i?.renderBefore??t;let n=s._$litPart$;if(void 0===n){const e=i?.renderBefore??null;s._$litPart$=n=new ee(t.insertBefore(I(),e),e,void 0,i??{})}return n._$AI(e),n},ce=globalThis;let he=class extends w{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const e=super.createRenderRoot();return this.renderOptions.renderBefore??=e.firstChild,e}update(e){const t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=le(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return U}};he._$litElement$=!0,he.finalized=!0,ce.litElementHydrateSupport?.({LitElement:he});const de=ce.litElementPolyfillSupport;de?.({LitElement:he}),(ce.litElementVersions??=[]).push("4.2.2");const pe=2,ue=e=>(...t)=>({_$litDirective$:e,values:t});let _e=class{constructor(e){}get _$AU(){return this._$AM._$AU}_$AT(e,t,i){this._$Ct=e,this._$AM=t,this._$Ci=i}_$AS(e,t){return this.update(e,t)}update(e,t){return this.render(...t)}};const me={},ge=ue(class extends _e{constructor(){super(...arguments),this.ot=me}render(e,t){return t()}update(e,[t,i]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((e,t)=>e===this.ot[t]))return U}else if(this.ot===t)return U;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,i)}}),{I:fe}=ae,ve=e=>e,be=()=>document.createComment(""),ye=(e,t,i)=>{const s=e._$AA.parentNode,n=void 0===t?e._$AB:t._$AA;if(void 0===i){const t=s.insertBefore(be(),n),o=s.insertBefore(be(),n);i=new fe(t,o,e,e.options)}else{const t=i._$AB.nextSibling,o=i._$AM,a=o!==e;if(a){let t;i._$AQ?.(e),i._$AM=e,void 0!==i._$AP&&(t=e._$AU)!==o._$AU&&i._$AP(t)}if(t!==n||a){let e=i._$AA;for(;e!==t;){const t=ve(e).nextSibling;ve(s).insertBefore(e,n),e=t}}}return i},we=(e,t,i=e)=>(e._$AI(t,i),e),ke={},xe=(e,t=ke)=>e._$AH=t,$e=e=>{e._$AR(),e._$AA.remove()},Se=(e,t,i)=>{const s=new Map;for(let n=t;n<=i;n++)s.set(e[n],n);return s},Me=ue(class extends _e{constructor(e){if(super(e),e.type!==pe)throw Error("repeat() can only be used in text expressions")}dt(e,t,i){let s;void 0===i?i=t:void 0!==t&&(s=t);const n=[],o=[];let a=0;for(const t of e)n[a]=s?s(t,a):a,o[a]=i(t,a),a++;return{values:o,keys:n}}render(e,t,i){return this.dt(e,t,i).values}update(e,[t,i,s]){const n=(e=>e._$AH)(e),{values:o,keys:a}=this.dt(t,i,s);if(!Array.isArray(n))return this.ut=a,o;const r=this.ut??=[],l=[];let c,h,d=0,p=n.length-1,u=0,_=o.length-1;for(;d<=p&&u<=_;)if(null===n[d])d++;else if(null===n[p])p--;else if(r[d]===a[u])l[u]=we(n[d],o[u]),d++,u++;else if(r[p]===a[_])l[_]=we(n[p],o[_]),p--,_--;else if(r[d]===a[_])l[_]=we(n[d],o[_]),ye(e,l[_+1],n[d]),d++,_--;else if(r[p]===a[u])l[u]=we(n[p],o[u]),ye(e,n[d],n[p]),p--,u++;else if(void 0===c&&(c=Se(a,u,_),h=Se(r,d,p)),c.has(r[d]))if(c.has(r[p])){const t=h.get(a[u]),i=void 0!==t?n[t]:null;if(null===i){const t=ye(e,n[d]);we(t,o[u]),l[u]=t}else l[u]=we(i,o[u]),ye(e,n[d],i),n[t]=null;u++}else $e(n[p]),p--;else $e(n[d]),d++;for(;u<=_;){const t=ye(e,l[_+1]);we(t,o[u]),l[u++]=t}for(;d<=p;){const e=n[d++];null!==e&&$e(e)}return this.ut=a,xe(e,l),U}}),Ce=new WeakMap;let De=0;class Pe extends he{constructor(){super(...arguments),this.title="",this.icon="",this.wide=!1,this.dismissOnScrim=!1,this.hass=null,this._opener=null,this._focusRoot=null,this._useHaDialog=!1,this._closing=!1,this._overlays=[],this._titleId="hp-dialog-title-"+ ++De,this._focusInitial=()=>{const e=this._focusableElements(),t=e.find(e=>e.hasAttribute("autofocus"))||e[0]||(this._useHaDialog?null:this.renderRoot.querySelector(".close"))||this.renderRoot.querySelector(".surface")||this.renderRoot.querySelector("ha-dialog");t?.focus({preventScroll:!0})},this._requestClose=()=>{this._closing||(this._closing=!0,this.dispatchEvent(new CustomEvent("hp-close",{bubbles:!0,composed:!0})))},this._onKeyDown=e=>{if("Escape"===e.key){e.preventDefault(),e.stopImmediatePropagation(),this._pruneOverlays();const t=this._overlays[this._overlays.length-1];return t?void this._closeOverlay(t,"escape"):void this._requestClose()}if("Tab"!==e.key||this._useHaDialog)return;const t=this.renderRoot.querySelector(".close"),i=t?[t,...this._focusableElements()]:this._focusableElements();if(!i.length)return e.preventDefault(),void this.renderRoot.querySelector(".surface")?.focus({preventScroll:!0});const s=this._deepActiveElement(),n=i[0],o=i[i.length-1];!e.shiftKey||s!==n&&i.includes(s)?e.shiftKey||s!==o||(e.preventDefault(),n.focus()):(e.preventDefault(),o.focus())},this._onFallbackCancel=e=>{e.preventDefault(),this._requestClose()},this._onFallbackClick=e=>{this.dismissOnScrim&&e.target===e.currentTarget&&this._requestClose()}}connectedCallback(){super.connectedCallback(),this._opener=this._deepActiveElement();const e=this.getRootNode();this._focusRoot=e;const t=Ce.get(e)||{dialogs:new Set,opener:this._opener};t.dialogs.add(this),Ce.set(e,t),this._useHaDialog=!!customElements.get("ha-dialog"),this.addEventListener("keydown",this._onKeyDown,!0)}disconnectedCallback(){this.removeEventListener("keydown",this._onKeyDown,!0);const e=[...this._overlays];this._overlays=[];for(const t of e.reverse())t.close("disconnect");const t=this._focusRoot,i=this._opener;this._opener=null,this._focusRoot=null;const s=t?Ce.get(t):void 0;s?.dialogs.delete(this),super.disconnectedCallback(),t&&s&&requestAnimationFrame(()=>{const e=Ce.get(t);if(!e)return;if(!e.dialogs.size)return e.opener?.isConnected&&e.opener.focus({preventScroll:!0}),void Ce.delete(t);const s=i?.closest("hp-dialog");i?.isConnected&&s&&e.dialogs.has(s)&&i.focus({preventScroll:!0})})}firstUpdated(e){if(super.firstUpdated(e),!this._useHaDialog){const e=this.renderRoot.querySelector("dialog");e&&!e.open&&e.showModal()}queueMicrotask(()=>this._focusInitial())}_deepActiveElement(){let e=document.activeElement;for(;e?.shadowRoot?.activeElement;)e=e.shadowRoot.activeElement;return e&&e!==document.body?e:null}_focusableElements(){const e=["[autofocus]","a[href]","button:not([disabled])",'input:not([disabled]):not([type="hidden"])',"select:not([disabled])","textarea:not([disabled])",'[contenteditable="true"]','[tabindex]:not([tabindex="-1"])'].join(","),t=[],i=new Set,s=n=>{if(!i.has(n)){if(i.add(n),n instanceof HTMLElement){if(n.matches(e)&&t.push(n),n instanceof HTMLSlotElement){for(const e of n.assignedNodes({flatten:!0}))s(e);return}if(n.shadowRoot){for(const e of n.shadowRoot.childNodes)s(e);return}}for(const e of n.childNodes)s(e)}};for(const e of this.childNodes)s(e);const n=this.overlayPortal();return n&&s(n),t.filter(e=>{let t=e;for(;t;){const e=getComputedStyle(t);if(t.hidden||t.inert||"true"===t.getAttribute("aria-hidden")||"none"===e.display||"hidden"===e.visibility)return!1;if(t=t.assignedSlot||t.parentElement||(t.getRootNode()instanceof ShadowRoot?t.getRootNode().host:null),t===this)break}return!0})}_pruneOverlays(){this._overlays=this._overlays.filter(e=>e.owner.isConnected)}_closeOverlay(e,t){const i=this._overlays.findIndex(t=>t.token===e.token);i>=0&&this._overlays.splice(i,1),e.close(t)}registerOverlay(e){this._pruneOverlays();const t=this._overlays.find(t=>t.owner===e.owner);t&&this._overlays.splice(this._overlays.indexOf(t),1);const i=e.group||"transient";for(const e of[...this._overlays].reverse())e.group===i&&this._closeOverlay(e,"exclusive");const s={...e,group:i,token:Symbol("hp-overlay")};this._overlays.push(s);let n=!1;return()=>{if(n)return;n=!0;const e=this._overlays.findIndex(e=>e.token===s.token);e>=0&&this._overlays.splice(e,1)}}closeTransientOverlays(e="outside"){this._pruneOverlays();const t=[...this._overlays].filter(e=>"transient"===(e.group||"transient"));for(const i of t.reverse())this._closeOverlay(i,e);return t.length>0}overlayPortal(){return this.renderRoot.querySelector(".overlay-portal")}render(){const e=q` +globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="34f7d06b869ff31edc9e70e29e33e2dd761bffd1b8941e7cff6777bbae089070";const e=globalThis,t=e.ShadowRoot&&(void 0===e.ShadyCSS||e.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),s=new WeakMap;let n=class{constructor(e,t,s){if(this._$cssResult$=!0,s!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o;const i=this.t;if(t&&void 0===e){const t=void 0!==i&&1===i.length;t&&(e=s.get(i)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),t&&s.set(i,e))}return e}toString(){return this.cssText}};const o=(e,...t)=>{const s=1===e.length?e[0]:t.reduce((t,i,s)=>t+(e=>{if(!0===e._$cssResult$)return e.cssText;if("number"==typeof e)return e;throw Error("Value passed to 'css' function must be a 'css' function result: "+e+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+e[s+1],e[0]);return new n(s,e,i)},a=t?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const i of e.cssRules)t+=i.cssText;return(e=>new n("string"==typeof e?e:e+"",void 0,i))(t)})(e):e,{is:r,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:d,getPrototypeOf:p}=Object,u=globalThis,_=u.trustedTypes,m=_?_.emptyScript:"",g=u.reactiveElementPolyfillSupport,f=(e,t)=>e,v={toAttribute(e,t){switch(t){case Boolean:e=e?m:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let i=e;switch(t){case Boolean:i=null!==e;break;case Number:i=null===e?null:Number(e);break;case Object:case Array:try{i=JSON.parse(e)}catch(e){i=null}}return i}},b=(e,t)=>!r(e,t),y={attribute:!0,type:String,converter:v,reflect:!1,useDefault:!1,hasChanged:b};Symbol.metadata??=Symbol("metadata"),u.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=y){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(e,i,t);void 0!==s&&l(this.prototype,e,s)}}static getPropertyDescriptor(e,t,i){const{get:s,set:n}=c(this.prototype,e)??{get(){return this[t]},set(e){this[t]=e}};return{get:s,set(t){const o=s?.call(this);n?.call(this,t),this.requestUpdate(e,o,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??y}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const e=p(this);e.finalize(),void 0!==e.l&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const e=this.properties,t=[...h(e),...d(e)];for(const i of t)this.createProperty(i,e[i])}const e=this[Symbol.metadata];if(null!==e){const t=litPropertyMetadata.get(e);if(void 0!==t)for(const[e,i]of t)this.elementProperties.set(e,i)}this._$Eh=new Map;for(const[e,t]of this.elementProperties){const i=this._$Eu(e,t);void 0!==i&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const i=new Set(e.flat(1/0).reverse());for(const e of i)t.unshift(a(e))}else void 0!==e&&t.push(a(e));return t}static _$Eu(e,t){const i=t.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof e?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),void 0!==this.renderRoot&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){const e=new Map,t=this.constructor.elementProperties;for(const i of t.keys())this.hasOwnProperty(i)&&(e.set(i,this[i]),delete this[i]);e.size>0&&(this._$Ep=e)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(t)i.adoptedStyleSheets=s.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const t of s){const s=document.createElement("style"),n=e.litNonce;void 0!==n&&s.setAttribute("nonce",n),s.textContent=t.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,i){this._$AK(e,i)}_$ET(e,t){const i=this.constructor.elementProperties.get(e),s=this.constructor._$Eu(e,i);if(void 0!==s&&!0===i.reflect){const n=(void 0!==i.converter?.toAttribute?i.converter:v).toAttribute(t,i.type);this._$Em=e,null==n?this.removeAttribute(s):this.setAttribute(s,n),this._$Em=null}}_$AK(e,t){const i=this.constructor,s=i._$Eh.get(e);if(void 0!==s&&this._$Em!==s){const e=i.getPropertyOptions(s),n="function"==typeof e.converter?{fromAttribute:e.converter}:void 0!==e.converter?.fromAttribute?e.converter:v;this._$Em=s;const o=n.fromAttribute(t,e.type);this[s]=o??this._$Ej?.get(s)??o,this._$Em=null}}requestUpdate(e,t,i,s=!1,n){if(void 0!==e){const o=this.constructor;if(!1===s&&(n=this[e]),i??=o.getPropertyOptions(e),!((i.hasChanged??b)(n,t)||i.useDefault&&i.reflect&&n===this._$Ej?.get(e)&&!this.hasAttribute(o._$Eu(e,i))))return;this.C(e,t,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(e,t,{useDefault:i,reflect:s,wrapped:n},o){i&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,o??t??this[e]),!0!==n||void 0!==o)||(this._$AL.has(e)||(this.hasUpdated||i||(t=void 0),this._$AL.set(e,t)),!0===s&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[e,t]of this._$Ep)this[e]=t;this._$Ep=void 0}const e=this.constructor.elementProperties;if(e.size>0)for(const[t,i]of e){const{wrapped:e}=i,s=this[t];!0!==e||this._$AL.has(t)||void 0===s||this.C(t,void 0,i,s)}}let e=!1;const t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM()}catch(t){throw e=!1,this._$EM(),t}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(e){}firstUpdated(e){}};w.elementStyles=[],w.shadowRootOptions={mode:"open"},w[f("elementProperties")]=new Map,w[f("finalized")]=new Map,g?.({ReactiveElement:w}),(u.reactiveElementVersions??=[]).push("2.1.2");const k=globalThis,x=e=>e,$=k.trustedTypes,S=$?$.createPolicy("lit-html",{createHTML:e=>e}):void 0,M="$lit$",C=`lit$${Math.random().toFixed(9).slice(2)}$`,D="?"+C,P=`<${D}>`,T=document,I=()=>T.createComment(""),R=e=>null===e||"object"!=typeof e&&"function"!=typeof e,A=Array.isArray,z="[ \t\n\f\r]",F=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,N=/-->/g,E=/>/g,O=RegExp(`>|${z}(?:([^\\s"'>=/]+)(${z}*=${z}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),H=/'/g,L=/"/g,B=/^(?:script|style|textarea|title)$/i,j=e=>(t,...i)=>({_$litType$:e,strings:t,values:i}),q=j(1),W=j(2),U=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),V=new WeakMap,K=T.createTreeWalker(T,129);function Y(e,t){if(!A(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(t):t}const J=(e,t)=>{const i=e.length-1,s=[];let n,o=2===t?"":3===t?"":"",a=F;for(let t=0;t"===l[0]?(a=n??F,c=-1):void 0===l[1]?c=-2:(c=a.lastIndex-l[2].length,r=l[1],a=void 0===l[3]?O:'"'===l[3]?L:H):a===L||a===H?a=O:a===N||a===E?a=F:(a=O,n=void 0);const d=a===O&&e[t+1].startsWith("/>")?" ":"";o+=a===F?i+P:c>=0?(s.push(r),i.slice(0,c)+M+i.slice(c)+C+d):i+C+(-2===c?t:d)}return[Y(e,o+(e[i]||"")+(2===t?"":3===t?"":"")),s]};class X{constructor({strings:e,_$litType$:t},i){let s;this.parts=[];let n=0,o=0;const a=e.length-1,r=this.parts,[l,c]=J(e,t);if(this.el=X.createElement(l,i),K.currentNode=this.el.content,2===t||3===t){const e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;null!==(s=K.nextNode())&&r.length0){s.textContent=$?$.emptyScript:"";for(let i=0;iA(e)||"function"==typeof e?.[Symbol.iterator])(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==G&&R(this._$AH)?this._$AA.nextSibling.data=e:this.T(T.createTextNode(e)),this._$AH=e}$(e){const{values:t,_$litType$:i}=e,s="number"==typeof i?this._$AC(e):(void 0===i.el&&(i.el=X.createElement(Y(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(t);else{const e=new Q(s,this),i=e.u(this.options);e.p(t),this.T(i),this._$AH=e}}_$AC(e){let t=V.get(e.strings);return void 0===t&&V.set(e.strings,t=new X(e)),t}k(e){A(this._$AH)||(this._$AH=[],this._$AR());const t=this._$AH;let i,s=0;for(const n of e)s===t.length?t.push(i=new ee(this.O(I()),this.O(I()),this,this.options)):i=t[s],i._$AI(n),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=G}_$AI(e,t=this,i,s){const n=this.strings;let o=!1;if(void 0===n)e=Z(this,e,t,0),o=!R(e)||e!==this._$AH&&e!==U,o&&(this._$AH=e);else{const s=e;let a,r;for(e=n[0],a=0;a{const s=i?.renderBefore??t;let n=s._$litPart$;if(void 0===n){const e=i?.renderBefore??null;s._$litPart$=n=new ee(t.insertBefore(I(),e),e,void 0,i??{})}return n._$AI(e),n},ce=globalThis;let he=class extends w{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const e=super.createRenderRoot();return this.renderOptions.renderBefore??=e.firstChild,e}update(e){const t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=le(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return U}};he._$litElement$=!0,he.finalized=!0,ce.litElementHydrateSupport?.({LitElement:he});const de=ce.litElementPolyfillSupport;de?.({LitElement:he}),(ce.litElementVersions??=[]).push("4.2.2");const pe=2,ue=e=>(...t)=>({_$litDirective$:e,values:t});let _e=class{constructor(e){}get _$AU(){return this._$AM._$AU}_$AT(e,t,i){this._$Ct=e,this._$AM=t,this._$Ci=i}_$AS(e,t){return this.update(e,t)}update(e,t){return this.render(...t)}};const me={},ge=ue(class extends _e{constructor(){super(...arguments),this.ot=me}render(e,t){return t()}update(e,[t,i]){if(Array.isArray(t)){if(Array.isArray(this.ot)&&this.ot.length===t.length&&t.every((e,t)=>e===this.ot[t]))return U}else if(this.ot===t)return U;return this.ot=Array.isArray(t)?Array.from(t):t,this.render(t,i)}}),{I:fe}=ae,ve=e=>e,be=()=>document.createComment(""),ye=(e,t,i)=>{const s=e._$AA.parentNode,n=void 0===t?e._$AB:t._$AA;if(void 0===i){const t=s.insertBefore(be(),n),o=s.insertBefore(be(),n);i=new fe(t,o,e,e.options)}else{const t=i._$AB.nextSibling,o=i._$AM,a=o!==e;if(a){let t;i._$AQ?.(e),i._$AM=e,void 0!==i._$AP&&(t=e._$AU)!==o._$AU&&i._$AP(t)}if(t!==n||a){let e=i._$AA;for(;e!==t;){const t=ve(e).nextSibling;ve(s).insertBefore(e,n),e=t}}}return i},we=(e,t,i=e)=>(e._$AI(t,i),e),ke={},xe=(e,t=ke)=>e._$AH=t,$e=e=>{e._$AR(),e._$AA.remove()},Se=(e,t,i)=>{const s=new Map;for(let n=t;n<=i;n++)s.set(e[n],n);return s},Me=ue(class extends _e{constructor(e){if(super(e),e.type!==pe)throw Error("repeat() can only be used in text expressions")}dt(e,t,i){let s;void 0===i?i=t:void 0!==t&&(s=t);const n=[],o=[];let a=0;for(const t of e)n[a]=s?s(t,a):a,o[a]=i(t,a),a++;return{values:o,keys:n}}render(e,t,i){return this.dt(e,t,i).values}update(e,[t,i,s]){const n=(e=>e._$AH)(e),{values:o,keys:a}=this.dt(t,i,s);if(!Array.isArray(n))return this.ut=a,o;const r=this.ut??=[],l=[];let c,h,d=0,p=n.length-1,u=0,_=o.length-1;for(;d<=p&&u<=_;)if(null===n[d])d++;else if(null===n[p])p--;else if(r[d]===a[u])l[u]=we(n[d],o[u]),d++,u++;else if(r[p]===a[_])l[_]=we(n[p],o[_]),p--,_--;else if(r[d]===a[_])l[_]=we(n[d],o[_]),ye(e,l[_+1],n[d]),d++,_--;else if(r[p]===a[u])l[u]=we(n[p],o[u]),ye(e,n[d],n[p]),p--,u++;else if(void 0===c&&(c=Se(a,u,_),h=Se(r,d,p)),c.has(r[d]))if(c.has(r[p])){const t=h.get(a[u]),i=void 0!==t?n[t]:null;if(null===i){const t=ye(e,n[d]);we(t,o[u]),l[u]=t}else l[u]=we(i,o[u]),ye(e,n[d],i),n[t]=null;u++}else $e(n[p]),p--;else $e(n[d]),d++;for(;u<=_;){const t=ye(e,l[_+1]);we(t,o[u]),l[u++]=t}for(;d<=p;){const e=n[d++];null!==e&&$e(e)}return this.ut=a,xe(e,l),U}}),Ce=new WeakMap;let De=0;class Pe extends he{constructor(){super(...arguments),this.title="",this.icon="",this.wide=!1,this.dismissOnScrim=!1,this.hass=null,this._opener=null,this._focusRoot=null,this._useHaDialog=!1,this._closing=!1,this._overlays=[],this._titleId="hp-dialog-title-"+ ++De,this._focusInitial=()=>{const e=this._focusableElements(),t=e.find(e=>e.hasAttribute("autofocus"))||e[0]||(this._useHaDialog?null:this.renderRoot.querySelector(".close"))||this.renderRoot.querySelector(".surface")||this.renderRoot.querySelector("ha-dialog");t?.focus({preventScroll:!0})},this._requestClose=()=>{this._closing||(this._closing=!0,this.dispatchEvent(new CustomEvent("hp-close",{bubbles:!0,composed:!0})))},this._onKeyDown=e=>{if("Escape"===e.key){e.preventDefault(),e.stopImmediatePropagation(),this._pruneOverlays();const t=this._overlays[this._overlays.length-1];return t?void this._closeOverlay(t,"escape"):void this._requestClose()}if("Tab"!==e.key||this._useHaDialog)return;const t=this.renderRoot.querySelector(".close"),i=t?[t,...this._focusableElements()]:this._focusableElements();if(!i.length)return e.preventDefault(),void this.renderRoot.querySelector(".surface")?.focus({preventScroll:!0});const s=this._deepActiveElement(),n=i[0],o=i[i.length-1];!e.shiftKey||s!==n&&i.includes(s)?e.shiftKey||s!==o||(e.preventDefault(),n.focus()):(e.preventDefault(),o.focus())},this._onFallbackCancel=e=>{e.preventDefault(),this._requestClose()},this._onFallbackClick=e=>{this.dismissOnScrim&&e.target===e.currentTarget&&this._requestClose()}}connectedCallback(){super.connectedCallback(),this._opener=this._deepActiveElement();const e=this.getRootNode();this._focusRoot=e;const t=Ce.get(e)||{dialogs:new Set,opener:this._opener};t.dialogs.add(this),Ce.set(e,t),this._useHaDialog=!!customElements.get("ha-dialog"),this.addEventListener("keydown",this._onKeyDown,!0)}disconnectedCallback(){this.removeEventListener("keydown",this._onKeyDown,!0);const e=[...this._overlays];this._overlays=[];for(const t of e.reverse())t.close("disconnect");const t=this._focusRoot,i=this._opener;this._opener=null,this._focusRoot=null;const s=t?Ce.get(t):void 0;s?.dialogs.delete(this),super.disconnectedCallback(),t&&s&&requestAnimationFrame(()=>{const e=Ce.get(t);if(!e)return;if(!e.dialogs.size)return e.opener?.isConnected&&e.opener.focus({preventScroll:!0}),void Ce.delete(t);const s=i?.closest("hp-dialog");i?.isConnected&&s&&e.dialogs.has(s)&&i.focus({preventScroll:!0})})}firstUpdated(e){if(super.firstUpdated(e),!this._useHaDialog){const e=this.renderRoot.querySelector("dialog");e&&!e.open&&e.showModal()}queueMicrotask(()=>this._focusInitial())}_deepActiveElement(){let e=document.activeElement;for(;e?.shadowRoot?.activeElement;)e=e.shadowRoot.activeElement;return e&&e!==document.body?e:null}_focusableElements(){const e=["[autofocus]","a[href]","button:not([disabled])",'input:not([disabled]):not([type="hidden"])',"select:not([disabled])","textarea:not([disabled])",'[contenteditable="true"]','[tabindex]:not([tabindex="-1"])'].join(","),t=[],i=new Set,s=n=>{if(!i.has(n)){if(i.add(n),n instanceof HTMLElement){if(n.matches(e)&&t.push(n),n instanceof HTMLSlotElement){for(const e of n.assignedNodes({flatten:!0}))s(e);return}if(n.shadowRoot){for(const e of n.shadowRoot.childNodes)s(e);return}}for(const e of n.childNodes)s(e)}};for(const e of this.childNodes)s(e);const n=this.overlayPortal();return n&&s(n),t.filter(e=>{let t=e;for(;t;){const e=getComputedStyle(t);if(t.hidden||t.inert||"true"===t.getAttribute("aria-hidden")||"none"===e.display||"hidden"===e.visibility)return!1;if(t=t.assignedSlot||t.parentElement||(t.getRootNode()instanceof ShadowRoot?t.getRootNode().host:null),t===this)break}return!0})}_pruneOverlays(){this._overlays=this._overlays.filter(e=>e.owner.isConnected)}_closeOverlay(e,t){const i=this._overlays.findIndex(t=>t.token===e.token);i>=0&&this._overlays.splice(i,1),e.close(t)}registerOverlay(e){this._pruneOverlays();const t=this._overlays.find(t=>t.owner===e.owner);t&&this._overlays.splice(this._overlays.indexOf(t),1);const i=e.group||"transient";for(const e of[...this._overlays].reverse())e.group===i&&this._closeOverlay(e,"exclusive");const s={...e,group:i,token:Symbol("hp-overlay")};this._overlays.push(s);let n=!1;return()=>{if(n)return;n=!0;const e=this._overlays.findIndex(e=>e.token===s.token);e>=0&&this._overlays.splice(e,1)}}closeTransientOverlays(e="outside"){this._pruneOverlays();const t=[...this._overlays].filter(e=>"transient"===(e.group||"transient"));for(const i of t.reverse())this._closeOverlay(i,e);return t.length>0}overlayPortal(){return this.renderRoot.querySelector(".overlay-portal")}render(){const e=q` ${this.icon?q``:G} ${this.title} `;return this._useHaDialog?q`${e.lqiText}`:G} - `}function Yo(e,t,i){return{kind:"none",reason:"none",generation:e,expiresAt:null,color:t,diameterScale:i,animated:!1,reducedMotionIndicator:"none"}}const Jo=new WeakMap;function Xo(e){const t=function(e){const t=e?.connection||e;return!t||"object"!=typeof t&&"function"!=typeof t?null:t}(e);if(!t)return null;let i=Jo.get(t);return i||(i={revision:0,loaded:!1,configEntries:{},manifests:{},listeners:new Set,refs:0},Jo.set(t,i)),i}function Zo(e){e.revision++;for(const t of[...e.listeners])try{t()}catch{}}function Qo(e,t){const i=Xo(e);if(!i)return()=>{};i.refs++,i.listeners.add(t),async function(e,t){t.loading||"function"!=typeof e?.callWS?t.loading:t.loaded&&t.loadedAt&&Date.now()-t.loadedAt<3e5||(t.loading=(async()=>{const[i,s]=await Promise.allSettled([e.callWS({type:"config_entries/get"}),e.callWS({type:"manifest/list"})]);"fulfilled"===i.status&&Array.isArray(i.value)&&(t.configEntries=Object.fromEntries(i.value.filter(e=>"string"==typeof e?.entry_id).map(e=>[e.entry_id,e]))),"fulfilled"===s.status&&Array.isArray(s.value)&&(t.manifests=Object.fromEntries(s.value.filter(e=>"string"==typeof e?.domain).map(e=>[e.domain,e]))),t.loaded=!0,t.loadedAt=Date.now(),t.loading=void 0,Zo(t)})().catch(()=>{t.loaded=!0,t.loadedAt=Date.now(),t.loading=void 0,Zo(t)}),t.loading)}(e,i),async function(e,t){if(t.unsubscribe||t.subscribing)return t.subscribing;const i=e?.connection?.subscribeMessage;"function"==typeof i&&(t.subscribing=(async()=>{try{t.unsubscribe=await i.call(e.connection,e=>{const i=Array.isArray(e)?e:Array.isArray(e?.entries)?e.entries:e?[e]:[];let s=!1;const n={...t.configEntries};for(const e of i){const t=e?.entry||e?.data?.entry||("string"==typeof e?.entry_id?e:null),i=t?.entry_id;if("string"!=typeof i)continue;const o=e?.type||e?.action||e?.data?.action;"removed"===o||"remove"===o?delete n[i]:n[i]=t,s=!0}s&&(t.configEntries=n,Zo(t))},{type:"config_entries/subscribe"})}catch{}finally{0===t.refs&&(t.unsubscribe?.(),t.unsubscribe=void 0),t.subscribing=void 0}})(),t.subscribing)}(e,i);let s=!1;return()=>{s||(s=!0,i.listeners.delete(t),i.refs=Math.max(0,i.refs-1),i.refs||(i.unsubscribe?.(),i.unsubscribe=void 0))}}function ea(e){const t=Xo(e);return t?{revision:t.revision,loaded:t.loaded,configEntries:t.configEntries,manifests:t.manifests}:{revision:0,loaded:!1,configEntries:{},manifests:{}}}function ta(e,t,i=ea(e),s){const n=i.manifests[t];return function(e,t){if("function"!=typeof e?.localize)return"";try{const i=`component.${t}.title`,s=e.localize(i);return"string"==typeof s&&s!==i?s:""}catch{return""}}(e,t)||String(n?.name||"")||String(s||"")||t.replace(/_/g," ")}function ia(e,t,i,s,n){const o=String(t||"").trim();if(!o)return null;const a=n?s.configEntries[n]:null;return{domain:o,label:ta(e,o,s,a?.title),configEntryId:n,configEntryTitle:a?.title,confidence:i}}function sa(e){const t={"registry-owner":0,"entity-platform":1,"identifier-fallback":2},i=new Set;return e.filter(e=>!!e).sort((e,i)=>t[e.confidence]-t[i.confidence]||e.label.localeCompare(i.label)||e.domain.localeCompare(i.domain)).filter(e=>{const t=`${e.domain}\n${e.configEntryId||""}`;return!i.has(t)&&(i.add(t),!0)})}function na(e,t,i,s=ea(e)){const n=i.entities?.[t]||e?.entities?.[t];if(!n)return null;const o=n.config_entry_id||void 0,a=o?s.configEntries[o]?.domain:"";return ia(e,n.platform||a,"entity-platform",s,o)}var oa={"editor.context_actions":"Actions: {object}","editor.tool_options":"Tool options: {tool}","editor.palette":"Palette: {tool}","editor.open_group":"Tool group: {group}","editor.group_active":"{group} — active: {item}","editor.disabled_action":"{action} is unavailable: {reason}","color_picker.title":"Color picker","color_picker.hue":"Hue","color_picker.saturation":"Saturation","color_picker.value":"Brightness","color_picker.hex":"Hex color","color_picker.invalid_hex":"Enter a 3- or 6-digit hex color","btn.properties":"Properties","btn.keep_as_walls":"Keep as walls","btn.delete_room_keep_walls":"Delete room, keep walls","btn.delete_room_with_walls":"Delete room and walls","title.markup_select":"Select and edit walls, columns and saved outlines","title.markup_column":"Column: click a grid point to place a square column","markup.partition":"Partition","markup.column":"Column","markup.hint_column":"click a grid point to place a square column","history.draft_segment":"Add room-draft segment","history.draft_merge":"Join unfinished room outlines","history.draft_segment_delete":"Remove room-draft segment","history.partition_add":"Add partition","history.column_add":"Add column","history.physical_edit":"Change physical object","history.physical_delete":"Delete physical object","history.physical_move":"Move physical object","history.contour_to_partitions":"Convert contour to closed walls","history.wall_chain_finish":"Finish wall chain","history.wall_face_batch":"Create rooms from walls","toast.column_duplicate":"A column with the same centre and outer size already exists","confirm.delete_draft":"Delete the whole unfinished room contour?","confirm.delete_draft_segment":"Delete this draft segment? The remaining contour may split in two.","physical.partition_properties":"Partition properties","physical.column_properties":"Column properties","physical.draft_properties":"Draft segment properties","physical.shape":"Shape","physical.square":"Square","physical.circle":"Circle","physical.diameter":"Diameter","physical.side":"Side","physical.rotation":"Rotation angle","physical.length":"Length","physical.allowed_range":"Allowed: {min}–{max} {unit}","physical.delete_segment":"Delete segment","physical.delete_draft":"Delete entire outline","physical.column_size_title":"Outer side of the square column placed by the click (1–150 cm).","card.title":"House plan","count.devices":"{n} dev.","empty.no_spaces":"No spaces yet.","empty.add_first":"Add the first space and upload a floor plan.","empty.install":'Install the House Plan integration and add it in "Devices & services".',"btn.add_space":"Add space","btn.cancel":"Cancel","btn.save":"Save","btn.close":"Close","btn.delete":"Delete","btn.edit":"Edit","btn.open_in_ha":"Open in HA","btn.reset":"Reset","btn.attach":"Attach…","btn.upload":"Upload…","btn.replace":"Replace…","title.zoom_in":"Zoom in","title.zoom_out":"Zoom out","title.zoom_fit":"Fit all","title.add_device":"Add a device to the plan","title.show_all":"Show hidden and disabled devices as service markers (this tab only)","title.markup":"Room markup: grid, lines, outlines","title.configure_space":"Configure space","title.add_space":"Add space","title.markup_add":"Walls: draw a continuous chain; Shift locks to 45°. Changing tool or leaving the editor finishes it as independent walls","title.markup_merge":"Merge: click one room, then the neighbour it shares a wall with","title.markup_split":"Split a room: click the room, then two points on its walls","title.markup_delroom":"Delete room: click inside a room and confirm","title.need_plan":"Upload a floor-plan image","markup.add":"Walls","markup.merge":"Merge","markup.split":"Split","markup.resize":"Resize","title.markup_resize":"Resize rooms: drag an available horizontal or vertical wall handle","markup.hint_resize":"drag an available wall handle · the wall stops at the first unsafe position · Esc cancels · Ctrl+Z — undo","resize.disabled.diagonal":"This wall is angled; Resize supports only horizontal and vertical walls","resize.disabled.side-angle":"An adjoining wall is angled; Resize requires both adjoining walls to meet this wall at right angles","resize.disabled.duplicate-physical-wall":"A separate partition, unfinished outline, or column overlaps this wall; remove or move it before resizing","resize.disabled.partial-shared":"A neighbouring room uses only part of this wall, so it cannot be moved safely as one shared wall","resize.disabled.unequal-shared":"The neighbouring room uses different endpoints or wall length, so the walls cannot move together safely","resize.disabled.multiple-rooms":"Moving this wall would affect more than two rooms","resize.disabled.thickness-conflict":"The wall thickness cannot be preserved safely","resize.disabled.opening-conflict":"An opening on this wall cannot be moved safely","resize.disabled.invalid-geometry":"This wall cannot be resized without changing the plan structure","resize.limit_stopped":"Wall stopped: the next step would break a junction limit","resize.commit_failed":"Resize was cancelled because the final plan did not pass the geometry check","resize.preview_failed":"Resize stopped at the last safe position because the plan geometry could not be preserved","markup.opening":"Opening","markup.delete_room":"Delete room","history.undo":"Undo","history.redo":"Redo","history.undo_named":"Undo: {name} (Ctrl+Z)","history.redo_named":"Redo: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Nothing to undo","history.redo_empty":"Nothing to redo","history.undone":"Undone: {name}","history.redone":"Redone: {name}","history.add_room":"Create room","history.split_room":"Split room","history.merge_rooms":"Merge rooms","history.resize_room":"Resize room","history.wall_thickness":"Change wall thickness","history.add_opening":"Add door or window opening","history.edit_opening":"Edit opening","history.move_opening":"Move opening","history.delete_opening":"Delete opening","history.delete_room":"Delete room","history.delete_room_keep_walls":"Delete room, keep walls","history.delete_room_with_walls":"Delete room and walls","history.decor_add":"Add decor object","history.decor_edit":"Edit decor object","history.decor_move":"Move decor object","history.decor_transform":"Transform decor object","history.decor_delete":"Delete decor object","history.backdrop_transform":"Transform plan backdrop","title.markup_opening":"Openings: click a wall to place, click an opening to edit","opening.new":"New opening","opening.edit":"Opening","opening.door":"Door","opening.window":"Window","opening.gate":"Gate","opening.passage":"Open passage","opening.passage_binding_warning":"Saving will remove the open/close sensor and lock.","opening.invalid_passage_fields":"The open passage on plan “{room}” has unsupported settings: {fields}.","opening.type_label":"Type","opening.length_label":"Length, cm","opening.contact_label":"Open/close sensor","opening.lock_label":"Lock","opening.none":"— none —","opening.search_ph":"Search: name or entity id…","opening.invert":"Invert open/closed","opening.flip_h":"Hinge on the other jamb","opening.flip_v":"Opens to the other side","opening.open":"Open","opening.closed":"Closed","opening.locked":"Locked","opening.unlocked":"Unlocked","opening.state_unknown":"unavailable","opening.no_entities":"No sensors bound — a static symbol on the plan.","toast.opening_no_wall":"Click next to a room wall or independent wall","opening.host_partition":"Independent wall","opening.partition_orphan":"The independent wall for this opening no longer exists","opening.partition_jamb_margin":"Leave at least {distance} between the opening and the end of the wall","opening.rebind_partition":"Attach to another independent wall","confirm.delete_partition_openings_title":"Delete wall and openings?","confirm.delete_partition_openings_body":"This wall contains {count} opening(s). They will be deleted together.","confirm.delete_partition_openings_item":"• {type}, {length}","markup.delete":"Delete","markup.hint_points":"points: {n} · Shift — 45° steps · Esc — finish chain · Ctrl+Z — undo a point · closing an area offers a room","markup.hint_start":"click a grid dot to start a wall chain","tip.lqi":"average zigbee signal:","tip.area":"area: {value}","info.device_header":"Device on the plan","info.model":"Model","info.state":"State","info.link":"Link","info.manuals":"Manuals","info.none":"No additional information","marker.new_device":"New device","marker.name_label":"Name (shown on the plan)","marker.name_ph":"Name","marker.binding_label":"Bind to an HA device","marker.binding_disabled":"disabled in Home Assistant","marker.virtual_option":"Virtual device (no binding)","marker.search_ph":"Search device / group…","marker.nothing_found":"nothing found","marker.room_label":"Room","marker.room_override":" (override placement)","marker.room_choose":"— select a room —","marker.room_auto":"— by device area (auto) —","marker.icon_label":"Icon","marker.icon_ph":"mdi:… (empty = auto)","marker.display_label":"Display","display.badge":"Icon + state","display.icon_ripple":"Icon + state and activity","display.static_icon":"Always static icon","marker.display_hint":"Icon + state changes the plate for work, open, alarm and unavailable states. Icon + state and activity additionally shows a short pulse for events and a continuous pulse for ongoing work, movement or presence. Value + state replaces the icon with one unambiguous HA value. A static icon never reacts to device states.","marker.display_hint_badge":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon_ripple":"The icon, dynamic plate and pulse show a short pulse for events and a continuous pulse for work, motion or presence. Red alarms have separate priority.","marker.display_hint_value":"One unambiguous Home Assistant value replaces the icon while the plate continues to show state. Red alarms remain visible.","marker.display_hint_static_icon":"The theme-aware shell and icon always stay the same. State, activity, unavailability and alarms do not change the face.","marker.static_alarm_warning":"This device can report alarms. Static display hides the marker's visual alarm indication.","marker.preview.title":"Display preview","marker.preview.actual":"Now","marker.preview.example":"Example","marker.preview.integration":"Provided by","marker.preview.source":"Display source","marker.preview.current_state":"Current state","marker.preview.result":"On the plan","marker.preview.details":"Source details","marker.preview.select_source":"Choose a Home Assistant device or entity to see its actual display.","marker.preview.unknown_provider":"Unknown integration","marker.preview.virtual_provider":"House Plan · virtual device","marker.preview.no_source":"No active source","marker.preview.no_state":"No current state","marker.preview.mixed_states":"Several different states","marker.preview.multiple_sources":"{n} sources","marker.preview.more_sources":"+{n} more","marker.preview.scaled":"The preview is scaled to {n}% to fit. The saved size is unchanged.","marker.preview.demo_activity":"Show activity example","marker.preview.demo_notice":"Temporary activity example; the actual device state is unchanged.","marker.preview.demo_short":"Show short activity","marker.preview.demo_continuous":"Show continuous activity","marker.preview.stop_continuous":"Stop continuous activity","marker.preview.demo_short_notice":"Short activity example; the actual device state is unchanged.","marker.preview.demo_continuous_notice":"Continuous activity example; the actual device state is unchanged.","marker.preview.demo_already_visible":"The current state already shows a real activity or alarm effect.","marker.preview.reduced_motion":"System reduced-motion is enabled, so ordinary activity is shown as a dot.","marker.preview.reason.neutral":"Neutral dark plate","marker.preview.reason.working":"Yellow plate: the device is working now","marker.preview.reason.working_activity":"Yellow plate and activity effect: the device is working now","marker.preview.reason.open":"Orange plate: physically open or unlocked","marker.preview.reason.cover_icon_state":"Neutral plate; the cover entity controls the displayed state and icon","marker.preview.reason.presence":"Activity effect while presence is detected","marker.preview.reason.event":"Short activity effect after a detected event","marker.preview.reason.transition":"Activity effect while the device is moving or changing state","marker.preview.reason.media_neutral":"Media devices stay on a neutral dark plate while available","marker.preview.reason.unavailable":"Subdued neutral plate: unavailable or off","marker.preview.reason.alarm":"Red alarm plate; alarm indication is always shown","marker.preview.reason.live_states_disabled":"Live-state styling is disabled for this card","marker.preview.reason.value_no_state":"No usable state value; the icon is shown instead","marker.preview.reason.value_ambiguous_sources":"Several possible value sources; the icon is shown instead","marker.preview.reason.value_non_scalar":"The source did not return a simple value; the icon is shown instead","marker.preview.reason.value_virtual":"A virtual device has no Home Assistant value; the icon is shown instead","marker.preview.reason.vacuum_live_plan_only":"Live vacuum position and trail are available only on the full plan","marker.preview.reason.hidden_design_preview":"The device is hidden on the plan; this design preview remains visible","marker.preview.reason.composite_power_source":"State comes from the device Power entity; auxiliary switches are ignored","marker.preview.reason.activity_display_disabled":"The selected display mode does not show ordinary activity effects","marker.preview.reason.ha_disabled":"The Home Assistant binding is disabled and will be hidden on the plan","marker.preview.reason.orphaned":"The saved Home Assistant binding can no longer be found","marker.preview.reason.static_icon":"Static mode: device state does not change the icon","marker.activity_color":"Activity pulse color","marker.ripple_size":"Activity pulse size","marker.activity_alarm_note":"Color and size affect ordinary activity only and do not change red alarms.","marker.pulse_a11y_alarm":"Alarm","marker.pulse_a11y_event":"Recent event","marker.pulse_a11y_presence":"Presence detected","marker.pulse_a11y_transition":"Changing state","marker.pulse_a11y_running":"Working","marker.state_a11y_neutral":"Default state","marker.state_a11y_working":"Working","marker.state_a11y_open":"Open","marker.state_a11y_locked":"Locked","marker.state_a11y_unlocked":"Unlocked","marker.state_a11y_alarm":"Alarm","marker.state_a11y_unavailable":"Unavailable","marker.lqi_a11y_low":"LQI {value}, low signal","marker.lqi_a11y_mid":"LQI {value}, medium signal","marker.lqi_a11y_high":"LQI {value}, high signal","marker.size_label":"Icon size / rotation","marker.angle_label":"Rotate","marker.model_label":"Model","marker.model_ph":"e.g. Aqara T&H","marker.link_label":"Link","marker.desc_label":"Description","marker.desc_ph":"Notes, specs…","marker.manuals_label":"Manuals (PDF etc.)","marker.sub_device":"device","marker.sub_z2m_group":" · Z2M group","marker.sub_group":"group","marker.sub_helper":"helper","space.new":"New space","space.header":"Space","space.title_label":"Title","space.title_ph":"e.g. Garage","space.plan_label":"Floor plan (background)","space.no_plan":"no plan image","space.plan_alt":"plan","room.new":"New room","room.name_label":"Display name","room.name_ph":"e.g. Terrace","room.area_label":"Home Assistant area (unassigned)","room.no_area_option":"— no area —","room.default_name":"Room","device.unnamed":"unnamed","device.light_group":"light group","device.fallback":"device","device.virtual":"virtual device","confirm.delete_room":'Delete room "{name}"?',"confirm.delete_room_title":'Delete room "{name}"?',"confirm.delete_room_body":"Choose whether the room's exclusive physical walls should remain. Walls shared with another room are always kept.","confirm.merge_rooms":'Merge rooms "{a}" and "{b}"?',"confirm.remove_marker":'Delete "{name}" from the plan? The device will disappear completely and stop contributing to plan data. You can add it again later.',"confirm.erase_decor":"Erase the {kind} object? You can undo this action from the editor history.","confirm.delete_space":'Delete space "{title}" with all its rooms and markup?',"space.delete_blocked":"This space is still used by {n} device(s). Move them to another space or delete them first.","toast.pos_save_failed":"Failed to save position: {err}","toast.no_entity":"The device has no suitable entity","toast.ha_disabled_action":"A Home Assistant object that is disabled cannot be used on the plan.","toast.ha_disabled_show_device":"A device disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_show_entity":"An entity disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_add":"A disabled Home Assistant object cannot be added to the plan. Enable it in Home Assistant first.","toast.ha_binding_unverified":"The object status could not be verified through the Home Assistant registry. Display and actions are temporarily unavailable.","toast.markup_needs_server":"Markup is available after the config is moved to the server","toast.space_order_changed":"Order changed. If any card pins its floor by number, check those panels.","toast.conflict":"Config was changed in another window — data refreshed, repeat your last action","toast.cfg_save_failed":"Failed to save config: {err}","toast.room_overlap":"The outline overlaps room “{name}” — rooms must not overlap","toast.contour_min_edges":"Draw at least two edges before closing the outline","toast.contour_cannot_close":"The outline cannot close because it is degenerate or intersects itself","toast.merge_not_adjacent":"Only rooms that share a wall can be merged","toast.rooms_merged":"Rooms merged into “{name}”","toast.split_pick_wall":"Start the cut on the room’s wall","toast.split_bad_cut":"The cut must run wall to wall inside the room, without crossing walls or itself","merge.header":"Merge rooms","merge.hint":"The merged room keeps one name and one area. The other area is released — its devices leave the plan until another room claims it.","merge.keep":"Keep","merge.no_area":"no area","room.split_header":"New room from the split","toast.room_saved":"Room saved ({n}). Devices added: {added}. Outline the next one or exit markup.","toast.room_saved_no_area":"Room saved ({n}, no area). Outline the next one or exit markup.","toast.marker_needs_server":"Device editing is available after the config is moved to the server","toast.virtual_name_required":"Enter a name for the virtual device","toast.marker_saved":"Device saved","toast.marker_removed":"Device deleted from the plan","toast.integration_missing":"The House Plan integration is not installed — management unavailable","toast.plan_formats":"Supported formats: SVG, PNG, JPG, WebP","toast.plan_required":"Upload a floor plan — it is required","toast.space_added_onboard":"Space added. Outline the rooms: click grid dots and close the contour.","toast.space_added":"Space added","toast.space_saved":"Space saved","toast.space_deleted":"Space deleted","toast.delete_failed":"Delete failed: {err}","toast.error":"Error: {err}","toast.toggle_target_unavailable":"Target “{name}” is unavailable — no action was performed","toast.toggle_targets_unavailable":"Targets are unavailable: {names}. No action was performed","toast.file_failed":'File "{name}" was not uploaded: {err}',"toast.files_attached":"Files attached: {n}","err.unknown":"unknown error","err.code":"code {code}","err.too_large":"file larger than {mb} MB","err.bad_ext":"unsupported type (PDF/image expected)","err.unauthorized":"administrator rights required","editor.title":"Title","editor.default_floor":"Initial space","editor.default_floor_missing":"Initial space “{id}” no longer exists. Choose another space.","editor.floor":"Fixed space","editor.floor_none":"— not fixed —","editor.floor_index":"YAML index {index} (preserved)","fixed_floor.loading":"Loading the fixed space…","fixed_floor.invalid_title":"The fixed space is unavailable","fixed_floor.invalid_body":"Check floor in the card configuration. The configured value is: {value}","editor.icon_size":"Icon size, % of plan width","editor.show_temperature":"Show temperature","editor.live_states":"Live states (on/off, open…)","editor.show_signal":"Show zigbee signal (LQI)","editor.language":"Interface language","editor.lang_auto":"Auto (HA profile)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Icon rules: which MDI icon devices get by name","rules.title":"Icon rules","rules.hint":"Rules are checked top-down against “device name + model” (case-insensitive regex); the first match wins. When nothing matches, the entity device class decides, then the generic chip icon.","rules.pattern_ph":"regex, e.g. plug|socket","rules.icon_ph":"mdi:power-socket-de","rules.add":"Add rule","rules.reset":"Reset to defaults","rules.test_ph":"Try a device name…","rules.invalid":"invalid regex","rules.saved":"Icon rules saved","btn.up":"Up","btn.down":"Down","tap.info":"Device card","tap.more_info":"HA more-info dialog","tap.toggle":"Toggle state","marker.tap_label":"Tap action for this device","tap.toggle_note":"The resolved state is shown below. Secure devices are never toggled from the plan.","import.title":"Create spaces from HA floors","import.hint":"Your Home Assistant already knows these floors. Pick the ones to turn into plan spaces — you will upload a floor-plan image for each one next. Rooms are then outlined by hand on the plan.","import.start":"Create {n} space(s)","import.manual":"Start from scratch","import.progress":"Floor {i} of {n}","import.done":"Spaces created. Outline the rooms: click grid dots and close the contour.","btn.skip":"Skip","space.scale_label":"Scale (grid cell size)","space.scale_unit":"cm per cell","space.scale_unit_imperial":"in per cell","space.display_section":"Display","space.show_borders":"Always show room borders","space.zero_wall_style":"Zero-thickness walls","space.zero_wall_dashed":"Dashed","space.zero_wall_solid":"Solid","space.zero_wall_help":"Dashed walls let light through. Solid walls block light even at zero thickness.","space.show_names":"Show room names (drag to move)","space.room_color":"Border & name color","space.opacity":"Opacity","space.fill_label":"Room fill","fill.none":"None","fill.lqi":"Zigbee signal","fill.light":"Lights","fill.custom":"Custom color","space.custom_fill":"Fill color","space.source_file":"I have a floor-plan image","space.source_draw":"No image — I'll outline rooms by hand","space.orientation":"Canvas","orient.landscape":"Landscape","orient.portrait":"Portrait","orient.square":"Square","fill.temp":"Temperature","space.temp_min":"Comfort from","space.temp_max":"to","tip.temp_avg":"average temperature:","tip.hum_avg":"average humidity:","space_card.button":"Open the space plan","space_card.not_found":"Space “{id}” not found","space_card.loading":"Loading…","continuity.restore_plan":"Restoring floor plan…","continuity.restore_connection":"Restoring device connection…","continuity.retry":"Try again","editor.space":"Space","editor.show_button":"Show button","editor.button_label":"Button label","editor.button_target":"Target dashboard path","marker.sub_entity":"entity","title.general_settings":"General settings","gs.title":"General settings","gs.hint":"Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.","gs.light_group":"Fill: lights","gs.light_on":"Lights on","gs.light_off":"All lights off","gs.temp_group":"Fill: temperature","gs.temp_cold":"Cold","gs.temp_ok":"Comfortable","gs.temp_hot":"Hot","gs.lqi_group":"Fill: zigbee signal","gs.lqi_low":"Weak signal","gs.lqi_high":"Strong signal","gs.reset":"Reset to defaults","gs.saved":"General settings saved","space.show_lqi":"Show zigbee signal (LQI) next to devices","space.hide_decor":"Hide the decorative layer","space.hide_decor_tip":"Lines, shapes, labels and furniture stay where they are — visible in the backdrop editor, not on the plan.","space.hide_openings":"Hide openings","space.hide_openings_tip":"Door, window and gate symbols are not drawn, but the openings keep working: light passes through, the sun comes in at a window, contact sensors still open them. The plan editor always shows them.","gs.light_none":"No light sources","mode.plan":"Plan editor","mode.devices":"Device editor","display.value":"Value + state","marker.subarea":"no area, manual","device.new":"New device — open its editor to dismiss","opening.unlock_action":"Unlock","opening.lock_action":"Lock","opening.lock_pending":"Working…","title.close_editor":"Close editor (back to view)","devbar.add":"Add","devbar.show_all":"Hidden and disabled","devbar.rules":"Icon rules","device_inbox.button":"Devices","device_inbox.title":"Devices on the plan","device_inbox.search":"Search devices, entities and integrations…","device_inbox.add_virtual":"Add virtual device","device_inbox.tab_on_plan":"On plan","device_inbox.tab_available":"Available","device_inbox.tab_hidden":"Hidden","device_inbox.tab_readd":"Available again","device_inbox.only_new":"New only","device_inbox.show_entities":"Show entities","device_inbox.show_hidden":"Show hidden on plan","device_inbox.new":"New","device_inbox.find":"Find on plan","device_inbox.edit":"Edit","device_inbox.hide":"Hide","device_inbox.show":"Show","device_inbox.add":"Add","device_inbox.readd":"Add again","device_inbox.hide_available":"Hide from list","device_inbox.show_more":"Show more","device_inbox.more_actions":"More actions","device_inbox.find_hidden_hint":"Enable “Show hidden on plan” first","device_inbox.show_disabled":"Activate this binding in Home Assistant before showing it","device_inbox.empty_on_plan":"No matching devices are on the plan yet.","device_inbox.empty_available":"No devices are available to add.","device_inbox.empty_hidden":"There are no hidden devices.","device_inbox.empty_readd":"No removed devices are available to add again.","device_inbox.reason_visible_auto":"Discovered automatically","device_inbox.reason_visible_explicit":"Added explicitly","device_inbox.reason_manual_hidden":"Hidden by user","device_inbox.reason_automatic_hidden":"Hidden automatically","device_inbox.reason_service_entry":"Service-only registry entry","device_inbox.reason_excluded_integration":"Integration excluded by device filters","device_inbox.reason_excluded_domain":"Non-spatial entity type","device_inbox.reason_grouped_light":"Represented by a room light group","device_inbox.reason_represented_by_parent":"Represented by its parent device","device_inbox.reason_removed":"Previously removed from the plan","device_inbox.reason_available":"Available to add to the plan","device_inbox.reason_no_bound_room":"HA area is not linked to a plan room","device_inbox.status_ha_disabled":"Disabled in Home Assistant","device_inbox.status_orphaned":"Binding is no longer present in Home Assistant","device_inbox.status_unverified":"Home Assistant registry is temporarily unavailable","device_inbox.saved":"Device list updated","space.roomcard_section":"Room card shows:","space.label_temp":"Temperature","space.label_hum":"Humidity","space.label_lqi":"Average Zigbee signal","space.label_light":"Lights on/off","roomcard.light_on":"On","roomcard.light_off":"Off","roomcard.light_partial":"{on} of {total}","toast.split_pick_inside":"Intermediate cut points must be inside the room","mode.decor":"Background editor","decor.select":"Select","decor.line":"Line","decor.rect":"Rectangle","decor.ellipse":"Oval","decor.text":"Text","decor.erase":"Erase","decor.erase_confirm_title":"Erase object?","decor.color":"Color","decor.width":"Line width","decor.line_style":"Line style","decor.line_style_solid":"Solid","decor.line_style_dashed":"Dashed","decor.fill":"Fill","decor.fill_color":"Fill color","decor.length":"Length","decor.size":"Size","decor.angle":"Rotation angle, °","decor.text_size":"Text size","decor.backdrop_properties":"Plan backdrop properties","decor.text_title":"Text label","decor.object_title":"Edit {kind}","decor.text_label":"Text","decor.live_group":"Insert HA variable","decor.live_entity":"Entity","decor.live_entity_ph":"choose an entity","decor.live_attr":"Value","decor.live_attr_ph":"choose state or attribute","decor.live_state":"State","decor.backdrop":"Backdrop image","decor.backdrop_hint":"Drag to move; pull a corner to resize; use the top handle to rotate. Shift changes proportions or frees the angle.","decor.backdrop_reset":"Reset the picture","decor.backdrop_reset_done":"The picture is back at its original place and size","marker.icon_auto":"Auto: {icon} (by icon rules; pick one to override)","marker.icon_pin_auto":"Pin","mode.plan_tip":"Plan editor — the geometry of the home: draw and split/merge rooms, bind them to HA areas, place doors, windows and gates, move room cards, set the scale","mode.devices_tip":"Device editor — everything about icons: drag to position, click to edit binding/icon/display, add virtual devices, icon rules","mode.decor_tip":"Background editor — purely visual decor under the plan: lines, rectangles, ovals and text labels that never react to clicks","space.glow_enabled":"Light-source glow","gs.glow_group":"Light-source glow","gs.glow_base":"House darkness","gs.glow_light":"Default light color / intensity","gs.wall_group":"Walls","gs.wall_fill":"Wall fill","gs.glow_radius":"Glow radius","gs.unit_m":"m","gs.unit_ft":"ft","marker.controls_label":"Controls other light sources","marker.controls_hint":"With tap action “Toggle”, a click flips every source added here. The marker’s own bound entity is controlled directly and cannot be added; use the switch below to classify it as a light.","marker.controls_filter":"Search lights and switches…","info.controls":"Controls","marker.glow_radius_label":"Glow radius","marker.glow_radius.help":"Sets the glow radius in metres or feet; an empty value uses the radius from general settings.","marker.glow_radius.help.aria":"Help: glow radius","markup.wallthick":"Thickness","markup.select":"Select","title.markup_wallthick":"Thickness — click a wall to set its thickness from 0 to 100 cm.","markup.hint_wallthick":"click a wall · Esc closes without applying","wallthick.field":"Thickness","wallthick.unit_cm":"cm","wallthick.unit_in":"in","wallthick.apply_room":"Apply to all walls of this room","markup.draw_wall_title":"Thickness of each new wall segment (0–100 cm). Shared walls keep the existing value.","room.queue_progress":"Room {current} of {total}","toast.wall_rooms_saved":"Created rooms: {n}","toast.wall_chain_saved":"Wall chain saved","toast.wallthick_pick":"Click a wall","toast.wallthick_set":"Wall thickness set","toast.wallthick_cleared":"Wall thickness removed","toast.physical_range":"Enter a value from {min} to {max} {unit}","toast.zero_wall_opening_conflict":"Remove the opening on this wall segment first.","toast.zero_wall_ambiguous":"The wall segment is ambiguous. Simplify or adjust the junction.","toast.zero_wall_migration_blocked":"The space was not converted: {reason}. No data was changed.","toast.physical_angle":"Enter a rotation angle from 0° up to, but not including, 90°","toast.physical_limit":"The space has reached the limit for this type of geometry","toast.geometry_unsafe":"Change canceled: wall geometry could not be built safely.","junction.limit_angle":"The angle between walls is too sharp: {actual}°, minimum {limit}°.","junction.limit_valence":"Too many walls meet in one node: {actual}, maximum {limit}.","junction.limit_length":"The wall segment is too short: {actual} cm, minimum {limit} cm.","junction.limit_distance":"Walls and nodes are too close: {actual} cm, minimum {limit} cm.","junction.limit_clearance":"No room interior is left: {actual} cm², minimum {limit} cm².","toast.wall_model_migration_blocked":"The wall model could not be updated: {reason}. The plan was not changed. Run “Optimize plans”; if the error repeats, fix the conflicting wall geometry.","toast.wall_model_client_outdated":"Update the card and reload the page before editing the plan.","wall_model.reason.invalid-room":"invalid room contour","wall_model.reason.zero-length":"zero-length wall segment","wall_model.reason.third-owner":"wall shared by more than two rooms","wall_model.reason.duplicate-id":"conflicting wall identifiers","wall_model.reason.thickness-conflict":"conflicting wall thickness values","wall_model.reason.opening-host":"opening cannot be assigned to one wall","toast.delete_room_pick":"Click inside the room you want to delete","toast.plan_snap_ambiguous":"Zoom in to choose the wall node","toast.wall_repair_ambiguous":"The room outline has more than one possible connection. Zoom in and connect it explicitly.","toast.wall_repair_too_large":"The gap is larger than 2 cm. Connect the walls before creating the room.","toast.wall_repair_changed":"The wall geometry changed. Try creating the room again.","toast.opening_on_zero_wall":"Openings cannot sit on a zero-thickness wall","marker.from_ha_option":"Pick from the HA list","marker.show_entities":"Show entities","marker.show_entities_tip":"Adds not only devices to the list, but all their entities too","marker.pick_ph":"Choose a device…","room.open_area":"Open the HA area","view.volumetric":"Volumetric view","view.flat":"Flat view","kiosk.title":"This screen's sizes","kiosk.hint":"Stored on this device only — every wall tablet or TV can have its own comfortable sizes.","kiosk.icon_scale":"Device icon size","kiosk.font_scale":"Room card text size","editor.kiosk":"Wall device (kiosk) mode","editor.cycle":"Auto-switch spaces every N seconds (kiosk, 0 = off)","room.settings_title":"Room settings","room.settings_section":"Room settings (override the space)","room.fill_label":"Fill in THIS room","fill.inherit":"As the space","room.custom_fill_space":"Space color","room.custom_fill_own":"Room color","room.temp_src_label":"Temperature source","room.hum_src_label":"Humidity source","room.src_average":"Average over the room's sensors (default)","room.src_pick":"A specific HA device or entity","room.src_ph":"Choose a source…","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","toast.cfg_reload_failed":"Could not reload the plan from the server: {err}","room.settings_short":"Room settings","room.unnamed":"Unnamed room","marker.use_climate_temp":"Include the device temperature in the room","marker.use_climate_temp_tip":"Adds an air conditioner or thermostat current_temperature to the room average. Configure the external value badge separately below.","marker.value_badge_title":"Value badge","marker.value_badge.help":"Shows one selected value next to the icon. It does not affect room metrics, light, Glow or the tap action.","marker.value_badge.help.aria":"Help: device value badge","marker.value_badge_enabled":"Show a value badge","marker.value_badge_source":"Value","marker.value_badge_source.help":"Choose a specific state, supported attribute, or derived value of this device.","marker.value_badge_source.help.aria":"Help: value badge source","marker.value_badge_position":"Position","marker.value_badge_position.help":"The chosen side stays fixed and does not flip automatically at a plan edge.","marker.value_badge_position.help.aria":"Help: value badge position","marker.value_badge_right":"Right","marker.value_badge_bottom":"Below","marker.value_badge_left":"Left","marker.value_badge_top":"Above","marker.value_badge_empty":"This device has no available values","marker.value_badge_static":"A static icon does not show live values. The setting is preserved.","marker.value_badge_missing":"Source unavailable","marker.value_badge_missing_hint":"The saved source is currently unavailable. Keep it, disable the badge, or choose another value.","marker.value_badge_duplicate":"The same value is already shown inside the icon.","marker.value_badge_state":"State · {name}","marker.value_badge_marker_state":"Light-source state · {name}","marker.value_badge_lqi":"Average Zigbee signal quality","marker.value_badge_attr_current_temperature":"Current temperature · {name}","marker.value_badge_attr_temperature":"Target temperature · {name}","marker.value_badge_attr_current_humidity":"Current humidity · {name}","marker.value_badge_attr_humidity":"Humidity · {name}","marker.value_badge_attr_current_position":"Position · {name}","marker.value_badge_attr_percentage":"Speed · {name}","marker.value_badge_attr_brightness":"Brightness · {name}","marker.value_badge_attr_volume_level":"Volume · {name}","marker.value_badge_attr_battery_level":"Battery · {name}","marker.value_badge_attr_fan_speed":"Fan speed · {name}","marker.light_role_label":"Is this device a light source?","marker.light_role.help":"Controls whether this marker itself is a spatial light source. Linked lights above remain independent room-light controls.","marker.light_role.help.aria":"Help: whether this device is a light source","marker.light_role_auto_yes":"Auto (light source)","marker.light_role_auto_no":"Auto (not a light source)","marker.light_role_always":"Always a light source","marker.light_role_never":"Never a light source","marker.light_entity_label":"Leading light entity","marker.light_entity.help":"For a composite device, choose the entity whose state, colour and brightness represent this plan source. The automatic fallback remains compatible with older plans.","marker.light_entity.help.aria":"Help: leading light entity","marker.light_entity_auto":"Automatic ({entity})","marker.light_entity_none":"no controllable entity","marker.light_entity_missing":"The saved entity {entity} is unavailable. House Plan temporarily uses {fallback}; the saved choice will be restored if it returns.","marker.toggle_entity_label":"Entity to toggle","marker.toggle_entity.help":"For a composite device, choose the exact own light or switch operated by a tap. Automatic keeps the previous target rules and is independent from the leading light entity.","marker.toggle_entity.help.aria":"Help: entity to toggle","marker.toggle_entity_auto":"Automatic ({entity})","marker.toggle_entity_none":"no own controllable entity","marker.toggle_entity_missing":"The saved entity {entity} is no longer among this marker’s selectable channels. House Plan temporarily uses {fallback}; the saved choice will be restored if it returns.","marker.glow_color_label":"Glow colour and brightness","marker.glow_mode.help":"Use live source values, override only its colour, or fix both colour and brightness. The minimum is 1%; to disable the source, choose “Never”.","marker.glow_mode.help.aria":"Help: glow colour and brightness","marker.glow_mode_auto":"From source","marker.glow_mode_color":"Set colour","marker.glow_mode_fixed":"Set colour and brightness","marker.glow_color":"Glow colour","marker.glow_brightness":"Brightness","marker.glow_disabled_never":"Glow settings are unavailable because this marker is explicitly not a light source.","marker.glow_disabled_auto":"Auto mode found no spatial light source for this marker.","marker.glow_disabled_no_entity":"No active controllable Home Assistant entity is available for this spatial source.","marker.glow_passive_hint":"This source has no own Home Assistant data. Set its colour and brightness manually; its radius remains available.","marker.control_broken":"Saved source is missing or no longer marked as a light","marker.control_missing_label":"Missing plan light","marker.control_passive":"passive source","toast.marker_control_cycle":"This link would create a circular chain of light controls.","toast.marker_binding_required":"Choose a Home Assistant device before linking another light source.","confirm.unlock":"Unlock “{name}”?","toast.files_migrate_failed":"Attachments could not be moved to the new binding, links keep pointing at the old files: {err}","space.pick_saved":"Already uploaded","space.pick_saved_hint":"Plans stored on the server, including ones you detached earlier","space.no_saved":"No plans stored on the server yet.","space.loading":"Loading…","space.used_by":"in use: {list}","space.in_use":"A space still uses this plan — detach it first","btn.use":"Use","confirm.delete_plan":'Delete the plan file "{name}" from the server? This cannot be undone.',"toast.plans_list_failed":"Could not list the stored plans: {err}","toast.plan_delete_failed":"Could not delete the plan: {err}","marker.hide":"Hide","marker.hide_tip":'The device will disappear from the plan after saving but will still count toward the room signal. Restore it through the "Devices" catalog in the device editor.',"marker.show":"Show","marker.show_tip":"The device will appear on the plan again after saving.","marker.hidden_ghost":"Device hidden by the user","marker.ha_disabled_device":"The device is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_entity":"The entity is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_all_entities":"The device has no active Home Assistant entities, so it is hidden from the plan.","marker.ha_registry_limited":"The full Home Assistant registry is unavailable to this user. The unverified object cannot be shown or used for now.","marker.delete_tip":"Completely delete the device from the plan and every aggregate. You can add it again later.","tap.run":"Run automation/script/scene","tap.cover":"Open/close (curtains/blinds)","marker.run_target_label":"What to run","marker.run_search_ph":"Search: automation, script or scene…","marker.run_target_gone":"Target {id} not found — pick again","marker.tap_confirm":"Ask for confirmation","marker.tap_confirm_tip":"Show a confirmation dialog before acting — a guard against accidental taps.","marker.toggle_hint_single":"Target: {name} ({id}).","marker.virtual_light_target":"Virtual light: {name}.","marker.virtual_light_current":"Manual state: {state} → {effect}.","marker.virtual_light_state_on":"on","marker.virtual_light_state_off":"off","marker.toggle_hint_group":"Will toggle {count} source(s): {names}.","marker.toggle_hint_current":"Now: {state} → {effect}.","marker.toggle_hint_group_current":"Currently on: {on} of {count} → {effect}.","marker.toggle_hint_skipped":"Will be skipped ({count}): {targets}.","marker.toggle_effect_turn_on":"will turn on","marker.toggle_effect_turn_off":"will turn off","marker.toggle_effect_open":"will open","marker.toggle_effect_close":"will close","marker.toggle_effect_stop":"will stop","marker.toggle_effect_toggle":"state will toggle","marker.toggle_skip_missing":"not found","marker.toggle_skip_ha_disabled":"disabled in HA","marker.toggle_skip_unavailable":"unavailable","marker.toggle_skip_unsupported":"toggle is unsupported","marker.toggle_skip_secure":"blocked for security","marker.toggle_none_no_binding":"This device has no binding, so there is no state to toggle. Tapping will do nothing.","marker.toggle_none_no_actionable_entity":"This device has no state that can be toggled. Tapping will do nothing.","marker.toggle_none_configured_targets_missing":"The configured targets are unavailable. The device's own entity will not be substituted for them.","marker.toggle_none_ha_disabled":"The target is disabled in Home Assistant and cannot be used on the plan.","marker.toggle_none_unavailable":"The target is currently unavailable. Tapping will do nothing.","marker.toggle_none_unsupported":"Home Assistant exposes no safe toggle operation for this entity. Tapping will do nothing.","marker.toggle_none_secure":"Toggling locks, alarms and secure gates from the plan is blocked for security.","run.automation":"automation","run.script":"script","run.scene":"scene","confirm.tap_run":'Run "{name}"?',"confirm.tap_toggle":'Toggle "{name}"?',"confirm.tap_cover":'Open/close "{name}"?',"confirm.current_state":"Current state: {state}","confirm.expected_state":"After switching: {state}","confirm.group_current":"on {on} of {total}","confirm.group_all_on":"all are on","confirm.group_all_off":"all are off","confirm.unavailable_targets":"Unavailable: {count}","confirm.expected_by_ha":"Home Assistant will determine the state","confirm.state_on":"On","confirm.state_off":"Off","confirm.state_open":"Open","confirm.state_closed":"Closed","confirm.state_opening":"Opening","confirm.state_closing":"Closing","confirm.state_stopped":"Stopped","confirm.state_unknown":"Unknown","toast.run_started":"Started: {name}","toast.run_target_missing":"Run target not found — check the device settings","toast.run_target_required":"Pick an automation, script or scene","toast.tap_target_changed":"The action target changed. Try again.","toast.virtual_light_toggle_failed":"Could not toggle the virtual light: {err}","toast.value_badge_source_required":"Choose a value for the badge","btn.run":"Run","vac.section":"Robot vacuum: live position","vac.autocal":"Set up automatically","vac.live":"Live position on the plan","vac.trail":"Show the robot's path","vac.cal_maps":"Calibrated maps: {maps}","vac.autocal_no_rooms":"The integration reports no room list — open “Fit manually”","vac.autocal_no_match":"Room names did not match (need ≥3 in common) — open “Fit manually”","vac.autocal_done":"Done: bound via {rooms} rooms. Start a cleanup and check","vac.cal_need_pos":"The robot is not reporting coordinates — start a cleanup and pause it","vac.cal_done":"Calibration saved. Start a cleanup and check","vac.cal_cancelled":"Calibration cancelled","vac.fit":"Fit manually","vac.fit_hint":"Drag the robot map into place, stretch by the corners","vac.fit_rotate":"Rotate 90°","vac.fit_mirror":"Mirror","vac.trail_never":"Never","vac.trail_cleaning":"While cleaning","vac.trail_always":"Always","gs.bg_group":"Stage background","gs.bg_color":"Background around the plan","gs.bg_default":"Theme default","gs.bg_theme":"theme default","gs.bg_mode":"Plan background","gs.bg_static":"Static color","gs.bg_daynight":"Follows the Sun","gs.bg_daynight_hint":"The background moves through dawn, day, dusk and night using sun.sun, or your local clock when sun data is unavailable. No compass is needed for the background.","gs.sun_group":"Sun","gs.sun_missing":"The sun.sun entity was not found — the background follows your local clock; window rays are unavailable.","gs.north":"North on the plan","gs.north_ph":"not set","gs.north_hint":"Set north only for window rays (1° steps, 15° with Shift); the Sun background works without it.","gs.north_clear":"Clear","gs.north_letter":"N","gs.sun_rays":"Sunlight through windows","gs.about_group":"About","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · docs & issues","gs.about_telegram":"Telegram chat","space.bg_color":"Background around the plan","space.bg_inherit":"Inherit general","space.bg_inherited":"inherits general settings","space.bg_mode":"Plan background","space.north":"North on the plan (override)","space.north_inherited":"inherited: {v}","space.sun_rays":"Sunlight through windows","space.sun_inherit":"Inherit general","space.sun_on":"On","space.sun_off":"Off","canvas.far_objects":"{n} object(s) far from the plan","canvas.show_far":"Show","canvas.home_tip":"The plan is over there — click to fit it","gs.grid_group":"Plan maintenance","gs.grid_hint":"Updates data models, aligns plan elements to the grid and merges redundant wall fragments. An exact report is shown before anything is stored.","gs.align_all":"Optimize plans","gs.align_title":"Optimize plans","gs.align_none":"All plans already use the current optimized data model.","gs.optimize_no_automatic_changes":"There are no automatic changes to apply. Review the items below.","gs.align_count":"{n} of {total} elements will move, by at most {cm} cm.","gs.align_where":"The largest shift is in “{s}”.","gs.align_turned":"Openings whose angle is corrected: {n}.","gs.align_removed_drafts":"Invalid outlines collapsed by the grid and removed: {n}.","gs.optimize_redundant_drafts":"Saved wall chains hidden by solid room walls and removed: {n}.","gs.align_preflight_failed":"Could not safely verify the geometry of the following spaces: {spaces}{more}.","gs.align_preflight_hint":"Plans were not changed. Copy the diagnostics with the button below and attach them to the bug report together with a space export.","gs.preflight_reason_prepare-exception":"Could not prepare the space geometry (exception while building the model)","gs.preflight_reason_wall-null":"The wall body did not build (the union came back empty)","gs.preflight_reason_wall-degraded-extra":"The wall body degraded with extra geometry","gs.preflight_reason_wall-failed-core":"The wall body core failed to assemble","gs.preflight_reason_wall-exception":"Wall construction threw an exception","gs.preflight_reason_floor-null":"The floor outline did not build","gs.preflight_reason_floor-exception":"Floor construction threw an exception","gs.preflight_copy":"Copy diagnostics","gs.preflight_copied":"Diagnostics copied","gs.preflight_update_hint":"The card and integration versions differ — update House Plan and retry.","gs.align_preflight_space":"Space {n}","gs.align_preflight_more":", and {n} more","gs.optimize_changes":"Model migrations: {m}; spaces updated: {c}; noisy coordinate values removed: {p}; merged real-wall fragments: {w}; merged zero-thickness wall fragments: {s}; independent walls: {i}.","gs.zero_walls_migrated":"Virtual wall spans converted: {n}.","gs.wall_segments_migrated":"Wall segments stabilised: {n}.","gs.optimize_lattice_summary":"Noisy coordinate values canonicalized: {n}; maximum movement: {cm} cm.","gs.optimize_lattice_space":"{space}: coordinate values canonicalized: {n}; off-grid values left unchanged: {far}.","gs.optimize_coincident_partitions":"Hidden independent wall sections absorbed into room walls: {n}.","gs.optimize_openings_rehosted":"Openings reattached to room walls: {n}.","gs.optimize_walls_straightened":"Walls straightened: {n}; maximum movement: {cm} cm.","gs.optimize_walls_straightened_where":"Largest wall correction: {s}.","gs.optimize_walls_straighten_skipped":"Near-axis walls left unchanged because they could not be repaired safely: {n}.","gs.optimize_glow_migration":"Legacy Glow: {spaces} spaces → no data fill + independent Glow; {rooms} rooms → inherited data fill + independent Glow.","gs.optimize_references":"References repaired: spaces — {spaces}; rooms — {rooms}; positions — {positions}; devices detached from missing spaces — {detached}.","gs.optimize_reference_more":", and {n} more","gs.optimize_orphans_removed":"Forgotten records removed: {total} — room labels: {rooms}; devices: {devices}; group markers: {groups}. They belonged to spaces deleted earlier.","gs.optimize_live_positions":"Old positions in deleted spaces belong to existing objects: {n}{names}. They will be kept.","gs.optimize_live_positions_remove":"Old positions in deleted spaces belong to existing objects: {n}{names}. They are selected for removal.","gs.optimize_live_names":": {names}{more}","gs.optimize_live_remove":"Remove old positions","gs.optimize_live_keep":"Keep old positions","gs.optimize_live_selected":"Old positions will be removed after Optimize is applied.","gs.optimize_unverified":"Could not safely verify positions: {n}. They were left unchanged.","gs.optimize_registry_limited":"Full administrator access to the Home Assistant registries is required for a safe check.","gs.optimize_vacuum_warning":"Vacuum room mappings that still need review: {n}.","gs.optimize_details":"Details","gs.optimize_details_more":"And {n} more records.","gs.optimize_detail_removed":"will be removed","gs.optimize_detail_live":"will be kept","gs.optimize_detail_unverified":"not verified","gs.optimize_detail_room_label":"room label","gs.optimize_detail_device":"device","gs.optimize_detail_group":"group marker","gs.optimize_detail_unknown":"unknown owner","gs.optimize_detail_item":"{status}: {kind} {id}; old space {space}","gs.align_warn":"Elements deliberately placed between grid nodes will move. One undo is available after the operation, only until the next plan edit.","gs.align_run":"Optimize","gs.align_done":"Plans optimized: {n} elements moved, {m} records maintained, {r} references repaired","gs.optimize_undo":"Undo last optimization","gs.optimize_undone":"The last optimization was undone","decor.furniture":"Furniture","furn.title":"Furniture library","furn.symbol":"Symbol","furn.group_furniture":"Furniture","furn.group_appliance":"Appliances","furn.group_sanitary":"Plumbing","furn.group_other":"Other","furn.width":"Width","furn.depth":"Depth","furn.pick_hint":"Pick a symbol, then click on the plan.","furn.place_hint":"Click on the plan — the piece lands against the nearest wall. Shift places it free.","furn.sym_sofa":"Sofa","furn.sym_armchair":"Armchair","furn.sym_coffee_table":"Coffee table","furn.sym_table_dining":"Dining table","furn.sym_table_round":"Round table","furn.sym_chair":"Chair","furn.sym_desk":"Desk","furn.sym_bed_double":"Double bed","furn.sym_bed_single":"Single bed","furn.sym_nightstand":"Nightstand","furn.sym_wardrobe":"Wardrobe","furn.sym_bookshelf":"Bookshelf","furn.sym_fridge":"Fridge","furn.sym_stove":"Cooker","furn.sym_dishwasher":"Dishwasher","furn.sym_washer":"Washing machine","furn.sym_dryer":"Tumble dryer","furn.sym_tv":"TV","furn.sym_ac":"Air conditioner","furn.sym_water_heater":"Water heater","furn.sym_toilet":"Toilet","furn.sym_bathtub":"Bathtub","furn.sym_shower":"Shower","furn.sym_sink":"Washbasin","furn.sym_kitchen_sink":"Kitchen sink","furn.sym_bidet":"Bidet","furn.sym_stairs":"Stairs","furn.sym_fireplace":"Fireplace","furn.sym_plant":"Plant","furn.sym_rug":"Rug","common.yes":"Yes","common.no":"No","vac.diag_source":"Source","vac.diag_platform":"Integration","vac.diag_status":"Status","vac.diag_position":"Position","vac.diag_rooms":"Rooms","vac.diag_rooms_value":"{total} · {matched} names match · {readiness}","vac.autocal_ready":"auto-calibration available","vac.autocal_not_ready":"need 3 matching names","vac.diag_path":"Integration path","vac.diag_map":"Map ID","vac.source_none":"not selected","vac.source_status_ok":"Ready","vac.source_status_missing":"Missing","vac.source_status_disabled":"Disabled in Home Assistant","vac.source_status_unavailable":"Unavailable","vac.source_status_unverified":"Cannot verify with current permissions","vac.source_status_unsupported":"No position data","vac.source_status_none":"No source","vac.source_banner_missing":"The saved source no longer exists. It was not replaced automatically; choose another source or restore it in Home Assistant.","vac.source_banner_disabled":"The saved source is disabled in Home Assistant. Enable it there or choose another source.","vac.source_banner_unverified":"Current Home Assistant permissions cannot verify this saved source. It remains pinned and will not be replaced automatically.","vac.choose_source":"Choose source","vac.source_auto":"Automatic","vac.source_auto_hint":"Use a compatible entity from this device","vac.all_cameras":"All cameras","vac.all_cameras_warn":"A camera may not provide robot data. Choose one only if it is your vacuum map.","vac.all_cameras_empty":"No other camera entities found.","vac.platform_unknown":"unknown integration","vac.cap_position":"position","vac.cap_rooms_short":"rooms","vac.cap_path":"path","vac.cap_map":"map ID","vac.cap_none":"no robot data detected","vac.xcme_hint":"Enable these Xiaomi Cloud Map Extractor attributes:","vac.documentation":"Documentation","vac.residual_title":"Check automatic calibration","vac.residual_message":"The matched rooms disagree by up to {error}. Apply this approximate calibration, refine it manually, or cancel without changing the saved setup.","vac.apply_proposal":"Apply","gs.backup_group":"Backup and transfer","gs.backup_hint":"Download a portable JSON backup or preview and import a backup made by House Plan.","backup.export_open":"Export","backup.import_open":"Import","backup.export_title":"Export House Plan","backup.import_title":"Import House Plan","backup.export_hint":"Choose whether the backup should contain the whole House Plan configuration or only the current space.","backup.full":"Full backup","backup.current_space":"Current space","backup.current_space_title":"Current space: {title}","backup.no_current_space":"No current space","backup.plan_only":"Plan only","backup.plan_only_hint":"Keep rooms, walls, openings, decor and room-label positions without devices or Home Assistant bindings.","backup.plan_only_preview":"This file contains the plan only","backup.privacy_warning":"The archive keeps names, Home Assistant identifiers and exact coordinates. Internal plans and attachments are referenced, not embedded; runtime states and vacuum trails are not included.","backup.download":"Download JSON","backup.export_done":"Backup downloaded","backup.reading":"Checking the backup…","backup.revalidated":"The plan changed after this preview. The summary was refreshed; review it and confirm again.","backup.error.unauthorized":"You do not have permission to export or import this plan.","backup.error.not_ready":"House Plan is not ready yet. Try again in a moment.","backup.error.too_large":"The backup exceeds the 8 MiB limit.","backup.error.invalid_json":"The selected file is not valid JSON.","backup.error.invalid_format":"The selected file is not a House Plan backup.","backup.error.unsupported_export_version":"This backup format is not supported by the installed version.","backup.error.future_model":"The backup was created by a newer House Plan data model.","backup.error.invalid_config":"The backup contains an invalid House Plan configuration.","backup.error.wall_model_migration_blocked":"The backup wall model could not be upgraded safely. Optimize the source plan first, then export it again.","backup.error.invalid_layout":"The backup contains invalid object positions.","backup.error.invalid_content":"The backup contains invalid or inconsistent content references.","backup.error.space_not_found":"The selected space no longer exists.","backup.error.capacity_exceeded":"Adding this backup would exceed the plan limits.","backup.error.preview_expired":"The preview expired. Select the backup file again.","backup.error.preview_owner_mismatch":"This preview belongs to another Home Assistant user.","backup.error.conflict":"The plan changed after the preview. Review the refreshed summary.","backup.error.content_confirmation_required":"Confirm that unavailable local content may be detached.","backup.error.commit_failed":"The backup could not be applied safely. The previous plan was restored or is pending recovery.","backup.error.missing_plan":"A referenced plan file disappeared after the preview. Review the backup again.","backup.error.missing_content":"A referenced local attachment disappeared after the preview. Review the backup again.","backup.error.marker_control_missing":"A linked plan light is missing from the backup.","backup.error.marker_control_not_light":"A linked plan target is no longer marked as a light source.","backup.error.marker_control_self":"A light source cannot control itself.","backup.error.marker_control_cycle":"The backup contains a circular chain of light controls.","backup.error.duplicate_marker_control":"The backup contains a duplicate plan-light link.","backup.error.no_backup":"There is no import or optimization snapshot to restore.","backup.same_source":"Created on this Home Assistant instance","backup.foreign_source":"Created on another Home Assistant instance","backup.created":"Created: {value}","backup.versions":"Card {card}; integration {integration}; data model {model}","backup.count_spaces":"Spaces: {n}","backup.count_rooms":"Rooms: {n}","backup.count_walls":"Walls: {n}","backup.count_openings":"Openings: {n}","backup.count_decor":"Decor objects: {n}","backup.count_markers":"Devices: {n}","backup.count_layout":"Positions: {n}","backup.bindings":"Bindings — devices: {device}, entities: {entity}, virtual: {virtual}; positions without a space: {legacy}","backup.binding_status":"Target status — active: {active}, disabled: {disabled}, missing: {missing}","backup.missing_areas":"Areas missing on the target: {areas}","backup.dropped_marker_links":"Plan-light links outside this transferred space were omitted: {n}.","backup.repaired_target_refs":"Existing references restored by this space import: {n}.","backup.preserved_unresolved_refs":"References that could not be restored unambiguously were preserved: {n}.","backup.preserved_unresolved_hint":"No data was guessed or deleted. After the import, run Optimize plans to inspect the remaining references.","backup.import_details":"Import reference details","backup.import_detail.incoming_remapped":"References updated inside the imported copy: {n}","backup.import_detail.target_repaired":"Existing references restored: {n}","backup.import_detail.preserved_unresolved":"Unresolved references preserved: {n}","backup.import_detail.collisions":"Destination conflicts preserved safely: {n}","backup.import_detail.dropped_links":"Incoming links omitted by transfer rules: {n}","backup.import_detail.bounded_lineages":"Overly nested identifiers left bounded: {n}","backup.replace_warning":"This replaces the current configuration and layout. Uploaded files are never deleted. One undo remains available until the next plan edit.","backup.foreign_bookkeeping":"Instance-specific known/new-device bookkeeping will not be imported.","backup.final_name":"New space name","backup.target_settings":"The target instance's global settings remain unchanged.","backup.duplicates":"Bindings already present: {n}","backup.skip":"Skip duplicate devices","backup.virtual_copy":"Add safe static virtual copies","backup.content":"Referenced content","backup.content_available":"available locally","backup.content_external":"external link","backup.content_detach_required":"will be detached","backup.confirm_detach":"I understand that unavailable internal plans and attachments will be detached from the imported configuration.","backup.replace":"Replace","backup.add":"Add space","backup.space_done":"Space imported: {rooms} rooms, {markers} devices, {refs} existing references restored","backup.full_done":"Backup restored: {spaces} spaces, {rooms} rooms, {markers} devices","backup.undo_import":"Undo last full import","backup.import_undone":"Full import undone"};const aa=[{code:"en",nativeLabel:"English",dictionary:oa},{code:"ru",nativeLabel:"Русский",dictionary:{"editor.context_actions":"Действия: {object}","editor.tool_options":"Параметры инструмента: {tool}","editor.palette":"Палитра: {tool}","editor.open_group":"Группа инструментов: {group}","editor.group_active":"{group} — активно: {item}","editor.disabled_action":"{action} недоступно: {reason}","color_picker.title":"Выбор цвета","color_picker.hue":"Оттенок","color_picker.saturation":"Насыщенность","color_picker.value":"Яркость","color_picker.hex":"Цвет HEX","color_picker.invalid_hex":"Введите цвет HEX из 3 или 6 цифр","btn.properties":"Свойства","btn.keep_as_walls":"Оставить стенами","btn.delete_room_keep_walls":"Удалить комнату, оставить стены","btn.delete_room_with_walls":"Удалить комнату и стены","title.markup_select":"Выбор и редактирование стен, колонн и сохранённых контуров","title.markup_column":"Колонна: кликните точку сетки для квадратной колонны","markup.partition":"Перегородка","markup.column":"Колонна","markup.hint_column":"кликните точку сетки, чтобы поставить квадратную колонну","history.draft_segment":"Добавление сегмента черновика комнаты","history.draft_merge":"Соединение незавершённых контуров комнаты","history.draft_segment_delete":"Удаление сегмента черновика комнаты","history.partition_add":"Добавление перегородки","history.column_add":"Добавление колонны","history.physical_edit":"Изменение физического объекта","history.physical_delete":"Удаление физического объекта","history.physical_move":"Перемещение физического объекта","history.contour_to_partitions":"Преобразование контура в замкнутые стены","history.wall_chain_finish":"Завершение цепочки стен","history.wall_face_batch":"Создание комнат из стен","toast.column_duplicate":"Колонна с тем же центром и внешним размером уже существует","confirm.delete_draft":"Удалить весь незавершённый контур комнаты?","confirm.delete_draft_segment":"Удалить этот сегмент черновика? Оставшийся контур может разделиться на два.","physical.partition_properties":"Свойства перегородки","physical.column_properties":"Свойства колонны","physical.draft_properties":"Свойства сегмента черновика","physical.shape":"Форма","physical.square":"Квадрат","physical.circle":"Круг","physical.diameter":"Диаметр","physical.side":"Сторона","physical.rotation":"Угол поворота","physical.length":"Длина","physical.allowed_range":"Допустимо: {min}–{max} {unit}","physical.delete_segment":"Удалить сегмент","physical.delete_draft":"Удалить весь контур","physical.column_size_title":"Внешняя сторона квадратной колонны, создаваемой кликом (1–150 см).","card.title":"План дома","count.devices":"{n} устр.","empty.no_spaces":"Пространств пока нет.","empty.add_first":"Добавьте первое пространство и загрузите план этажа.","empty.install":"Установите интеграцию House Plan и добавьте запись в «Устройства и службы».","btn.add_space":"Добавить пространство","btn.cancel":"Отмена","btn.save":"Сохранить","btn.close":"Закрыть","btn.delete":"Удалить","btn.edit":"Редактировать","btn.open_in_ha":"Открыть в HA","btn.reset":"Сброс","btn.attach":"Прикрепить…","btn.upload":"Загрузить…","btn.replace":"Заменить…","title.zoom_in":"Приблизить","title.zoom_out":"Отдалить","title.zoom_fit":"Вписать всё","title.add_device":"Добавить устройство на план","title.show_all":"Показать скрытые и деактивированные устройства служебными маркерами (только в этой вкладке)","title.markup":"Разметка комнат: сетка, линии, контуры","title.configure_space":"Настроить пространство","title.add_space":"Добавить пространство","title.markup_add":"Стены: рисуйте непрерывную цепочку; Shift фиксирует 45°. Смена инструмента или выход завершает её как независимые стены","title.markup_merge":"Объединить: клик по одной комнате, затем по соседней с общей стеной","title.markup_split":"Разделить комнату: клик по комнате, затем две точки на её стенах","title.markup_delroom":"Удалить комнату: кликните внутри комнаты и подтвердите удаление","title.need_plan":"Загрузите подложку (план этажа)","markup.add":"Стены","markup.merge":"Объединить","markup.split":"Разделить","markup.resize":"Размер","title.markup_resize":"Изменение размера комнат: тяните доступную ручку горизонтальной или вертикальной стены","markup.hint_resize":"тяните доступную ручку стены · стена упирается в первую небезопасную позицию · Esc — отмена · Ctrl+Z — отмена шага","resize.disabled.diagonal":"Стена расположена под углом: Resize поддерживает только горизонтальные и вертикальные стены","resize.disabled.side-angle":"Примыкающая стена расположена под углом: для Resize обе примыкающие стены должны образовывать прямой угол","resize.disabled.duplicate-physical-wall":"Стену перекрывает отдельная перегородка, незавершённый контур или колонна; удалите или переместите её перед изменением размера","resize.disabled.partial-shared":"Соседняя комната использует только часть этой стены, поэтому её нельзя безопасно двигать как одну общую стену","resize.disabled.unequal-shared":"У соседней комнаты другие конечные точки или длина стены, поэтому стены нельзя безопасно двигать вместе","resize.disabled.multiple-rooms":"Перемещение затронуло бы больше двух комнат","resize.disabled.thickness-conflict":"Толщину стены нельзя безопасно сохранить","resize.disabled.opening-conflict":"Проём на этой стене нельзя безопасно переместить","resize.disabled.invalid-geometry":"Эту стену нельзя переместить без изменения структуры плана","resize.limit_stopped":"Стена остановлена: дальше нарушается ограничение стыков","resize.commit_failed":"Изменение размера отменено: итоговый план не прошёл проверку геометрии","resize.preview_failed":"Стена остановлена в последней безопасной позиции: геометрию плана нельзя сохранить без потерь","markup.opening":"Проём","markup.delete_room":"Удалить комнату","history.undo":"Отменить","history.redo":"Повторить","history.undo_named":"Отменить: {name} (Ctrl+Z)","history.redo_named":"Повторить: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Нет операций для отмены","history.redo_empty":"Нет операций для повтора","history.undone":"Отменено: {name}","history.redone":"Повторено: {name}","history.add_room":"Создание комнаты","history.split_room":"Разделение комнаты","history.merge_rooms":"Объединение комнат","history.resize_room":"Изменение размера комнаты","history.wall_thickness":"Изменение толщины стены","history.add_opening":"Добавление дверного или оконного проёма","history.edit_opening":"Изменение проёма","history.move_opening":"Перемещение проёма","history.delete_opening":"Удаление проёма","history.delete_room":"Удаление комнаты","history.delete_room_keep_walls":"Удаление комнаты с сохранением стен","history.delete_room_with_walls":"Удаление комнаты и её стен","history.decor_add":"Добавление объекта декора","history.decor_edit":"Изменение объекта декора","history.decor_move":"Перемещение объекта декора","history.decor_transform":"Трансформация объекта декора","history.decor_delete":"Удаление объекта декора","history.backdrop_transform":"Трансформация подложки плана","title.markup_opening":"Проёмы: клик по стене — добавить, клик по проёму — редактировать","opening.new":"Новый проём","opening.edit":"Проём","opening.door":"Дверь","opening.window":"Окно","opening.gate":"Ворота","opening.passage":"Открытый проём","opening.passage_binding_warning":"При сохранении датчик открытия и замок будут удалены.","opening.invalid_passage_fields":"У открытого проёма на плане «{room}» есть недопустимые параметры: {fields}.","opening.type_label":"Тип","opening.length_label":"Длина, см","opening.contact_label":"Датчик открытия","opening.lock_label":"Замок","opening.none":"— нет —","opening.search_ph":"Поиск: имя или entity_id…","opening.invert":"Инвертировать открыто/закрыто","opening.flip_h":"Петли с другой стороны","opening.flip_v":"Открывается в другую сторону","opening.open":"Открыто","opening.closed":"Закрыто","opening.locked":"Заперто","opening.unlocked":"Не заперто","opening.state_unknown":"недоступно","opening.no_entities":"Датчики не привязаны — статичный символ на плане.","toast.opening_no_wall":"Кликните рядом со стеной комнаты или независимой стеной","opening.host_partition":"Независимая стена","opening.partition_orphan":"Независимая стена этого проёма больше не существует","opening.partition_jamb_margin":"Оставьте от края проёма до торца стены минимум {distance}","opening.rebind_partition":"Привязать к другой независимой стене","confirm.delete_partition_openings_title":"Удалить стену и проёмы?","confirm.delete_partition_openings_body":"В стене есть проёмы: {count}. Они будут удалены вместе со стеной.","confirm.delete_partition_openings_item":"• {type}, {length}","markup.delete":"Удалить","markup.hint_points":"точек: {n} · Shift — шаг 45° · Esc — завершить цепочку · Ctrl+Z — убрать точку · при замыкании будет предложена комната","markup.hint_start":"кликните точку сетки, чтобы начать цепочку стен","tip.lqi":"средний сигнал zigbee:","tip.area":"площадь: {value}","info.device_header":"Устройство на плане","info.model":"Модель","info.state":"Состояние","info.link":"Ссылка","info.manuals":"Инструкции","info.none":"Нет дополнительной информации","marker.new_device":"Новое устройство","marker.name_label":"Имя (отображается на плане)","marker.name_ph":"Название","marker.binding_label":"Привязка к устройству HA","marker.binding_disabled":"деактивировано в Home Assistant","marker.virtual_option":"Виртуальное устройство (без привязки)","marker.search_ph":"Поиск устройства / группы…","marker.nothing_found":"ничего не найдено","marker.room_label":"Комната","marker.room_override":" (переопределить размещение)","marker.room_choose":"— выберите комнату —","marker.room_auto":"— по зоне устройства (авто) —","marker.icon_label":"Иконка","marker.icon_ph":"mdi:… (пусто = авто)","marker.display_label":"Отображение","display.badge":"Значок + состояние","display.icon_ripple":"Значок + состояние и активность","display.static_icon":"Всегда статичный значок","marker.display_hint":"Значок + состояние меняет подложку при работе, открытии, тревоге и недоступности. Значок + состояние и активность дополнительно показывает короткую пульсацию для событий и постоянную — для работы, движения или присутствия. Значение + состояние заменяет значок одним однозначным значением HA. Статичный значок не реагирует на состояния устройства.","marker.display_hint_badge":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon_ripple":"Значок, динамическая подложка и пульсация: короткая — для событий, постоянная — для работы, движения или присутствия. Красная тревога имеет отдельный приоритет.","marker.display_hint_value":"Значок заменяется одним однозначным значением Home Assistant; подложка продолжает показывать состояние. Красная тревога сохраняется.","marker.display_hint_static_icon":"Подложка в цветах темы и значок всегда остаются одинаковыми. Состояния, активность, недоступность и тревоги не меняют отображение.","marker.static_alarm_warning":"Это устройство может сообщать о тревогах. Статичный режим скрывает визуальную тревогу маркера.","marker.preview.title":"Предпросмотр отображения","marker.preview.actual":"Сейчас","marker.preview.example":"Пример","marker.preview.integration":"Интеграция","marker.preview.source":"Источник отображения","marker.preview.current_state":"Текущее состояние","marker.preview.result":"На плане","marker.preview.details":"Подробнее об источниках","marker.preview.select_source":"Выберите устройство или сущность Home Assistant, чтобы увидеть фактическое отображение.","marker.preview.unknown_provider":"Интеграция неизвестна","marker.preview.virtual_provider":"House Plan · виртуальное устройство","marker.preview.no_source":"Нет активного источника","marker.preview.no_state":"Нет текущего состояния","marker.preview.mixed_states":"Несколько разных состояний","marker.preview.multiple_sources":"Источников: {n}","marker.preview.more_sources":"ещё {n}","marker.preview.scaled":"Предпросмотр уменьшен до {n}%, чтобы поместиться. Сохранённый размер не изменится.","marker.preview.demo_activity":"Показать пример активности","marker.preview.demo_notice":"Временный пример активности; фактическое состояние устройства не меняется.","marker.preview.demo_short":"Показать краткую активность","marker.preview.demo_continuous":"Показать постоянную активность","marker.preview.stop_continuous":"Остановить постоянную активность","marker.preview.demo_short_notice":"Пример краткой активности; фактическое состояние устройства не меняется.","marker.preview.demo_continuous_notice":"Пример постоянной активности; фактическое состояние устройства не меняется.","marker.preview.demo_already_visible":"Текущее состояние уже показывает реальную активность или тревогу.","marker.preview.reduced_motion":"В системе включено уменьшение движения, поэтому обычная активность показана точкой.","marker.preview.reason.neutral":"Нейтральная тёмная подложка","marker.preview.reason.working":"Жёлтая подложка: устройство сейчас работает","marker.preview.reason.working_activity":"Жёлтая подложка и эффект активности: устройство сейчас работает","marker.preview.reason.open":"Оранжевая подложка: физически открыто или разблокировано","marker.preview.reason.cover_icon_state":"Нейтральная подложка; состояние и значок определяет сущность шторы или заслонки","marker.preview.reason.presence":"Эффект активности, пока обнаружено присутствие","marker.preview.reason.event":"Короткий эффект активности после события","marker.preview.reason.transition":"Эффект активности во время движения или смены состояния","marker.preview.reason.media_neutral":"Медиаустройство остаётся на нейтральной тёмной подложке, пока доступно","marker.preview.reason.unavailable":"Приглушённая нейтральная подложка: отключено или недоступно","marker.preview.reason.alarm":"Красная тревожная подложка; тревога отображается всегда","marker.preview.reason.live_states_disabled":"Оформление по живым состояниям отключено в настройках карточки","marker.preview.reason.value_no_state":"Нет подходящего значения состояния — вместо него показан значок","marker.preview.reason.value_ambiguous_sources":"Подходит несколько источников значения — вместо него показан значок","marker.preview.reason.value_non_scalar":"Источник вернул не простое значение — вместо него показан значок","marker.preview.reason.value_virtual":"У виртуального устройства нет значения HA — вместо него показан значок","marker.preview.reason.vacuum_live_plan_only":"Живая позиция и след пылесоса доступны только на полном плане","marker.preview.reason.hidden_design_preview":"Устройство скрыто на плане, но его оформление видно в предпросмотре","marker.preview.reason.composite_power_source":"Состояние берётся из сущности питания устройства; вспомогательные переключатели не учитываются","marker.preview.reason.activity_display_disabled":"Выбранный режим отображения не показывает обычные эффекты активности","marker.preview.reason.ha_disabled":"Привязка отключена в Home Assistant, поэтому устройство будет скрыто на плане","marker.preview.reason.orphaned":"Сохранённая привязка Home Assistant больше не найдена","marker.preview.reason.static_icon":"Статичный режим: состояние устройства не меняет значок","marker.activity_color":"Цвет пульсации активности","marker.ripple_size":"Размер пульсации активности","marker.activity_alarm_note":"Цвет и размер относятся только к обычной активности и не влияют на красную тревогу.","marker.pulse_a11y_alarm":"Тревога","marker.pulse_a11y_event":"Недавнее событие","marker.pulse_a11y_presence":"Обнаружено присутствие","marker.pulse_a11y_transition":"Изменение состояния","marker.pulse_a11y_running":"Работает","marker.state_a11y_neutral":"Обычное состояние","marker.state_a11y_working":"Работает","marker.state_a11y_open":"Открыто","marker.state_a11y_locked":"Заблокировано","marker.state_a11y_unlocked":"Разблокировано","marker.state_a11y_alarm":"Тревога","marker.state_a11y_unavailable":"Недоступно","marker.lqi_a11y_low":"LQI {value}, слабый сигнал","marker.lqi_a11y_mid":"LQI {value}, средний сигнал","marker.lqi_a11y_high":"LQI {value}, сильный сигнал","marker.size_label":"Размер / поворот значка","marker.angle_label":"Поворот","marker.model_label":"Модель","marker.model_ph":"напр. Aqara T&H","marker.link_label":"Ссылка","marker.desc_label":"Описание","marker.desc_ph":"Заметки, характеристики…","marker.manuals_label":"Инструкции (PDF и т.п.)","marker.sub_device":"устройство","marker.sub_z2m_group":" · Z2M-группа","marker.sub_group":"группа","marker.sub_helper":"хелпер","space.new":"Новое пространство","space.header":"Пространство","space.title_label":"Название","space.title_ph":"Например: Гараж","space.plan_label":"Подложка (план)","space.no_plan":"нет подложки","space.plan_alt":"план","room.new":"Новая комната","room.name_label":"Отображаемое имя","room.name_ph":"Например: Терраса","room.area_label":"Зона Home Assistant (свободные)","room.no_area_option":"— без зоны —","room.default_name":"Комната","device.unnamed":"без имени","device.light_group":"группа света","device.fallback":"устройство","device.virtual":"виртуальное устройство","confirm.delete_room":"Удалить комнату «{name}»?","confirm.delete_room_title":"Удалить комнату «{name}»?","confirm.delete_room_body":"Выберите, нужно ли оставить физические стены, принадлежащие только этой комнате. Общие с другой комнатой стены сохраняются всегда.","confirm.merge_rooms":"Слить комнаты «{a}» и «{b}»?","confirm.remove_marker":"Удалить «{name}» с плана? Устройство исчезнет полностью и перестанет участвовать в данных плана. Позже его можно будет добавить заново.","confirm.erase_decor":"Стереть объект «{kind}»? Действие можно отменить из истории редактора.","confirm.delete_space":"Удалить пространство «{title}» со всеми комнатами и разметкой?","space.delete_blocked":"Это пространство всё ещё используется устройствами: {n}. Сначала перенесите их в другое пространство или удалите.","toast.pos_save_failed":"Не удалось сохранить позицию: {err}","toast.no_entity":"У устройства нет подходящей сущности","toast.ha_disabled_action":"Деактивированный объект Home Assistant нельзя использовать на плане.","toast.ha_disabled_show_device":"Деактивированное в Home Assistant устройство нельзя показать на плане. Сначала активируйте его в Home Assistant.","toast.ha_disabled_show_entity":"Деактивированную в Home Assistant сущность нельзя показать на плане. Сначала активируйте её в Home Assistant.","toast.ha_disabled_add":"Деактивированный объект Home Assistant нельзя добавить на план. Сначала активируйте его в Home Assistant.","toast.ha_binding_unverified":"Статус объекта не удалось подтвердить по реестру Home Assistant. Отображение и действия временно недоступны.","toast.markup_needs_server":"Разметка доступна после переноса конфига на сервер","toast.space_order_changed":"Порядок изменён. Если где-то этаж карточки задан номером, проверьте такие панели.","toast.conflict":"Конфиг изменён в другом окне — данные обновлены, повторите последнее действие","toast.cfg_save_failed":"Не удалось сохранить конфиг: {err}","toast.room_overlap":"Контур накладывается на комнату «{name}» — комнаты не должны накладываться","toast.contour_min_edges":"Чтобы замкнуть контур, сначала нарисуйте минимум две грани","toast.contour_cannot_close":"Контур нельзя замкнуть: он вырожден или пересекает сам себя","toast.merge_not_adjacent":"Объединять можно только комнаты с общей стеной","toast.rooms_merged":"Комнаты объединены в «{name}»","toast.split_pick_wall":"Начните разрез на стене комнаты","toast.split_bad_cut":"Разрез — от стены до стены внутри комнаты, без пересечения стен и самого себя","merge.header":"Объединение комнат","merge.hint":"У объединённой комнаты одно имя и одна зона. Вторая зона освобождается — её устройства уйдут с плана, пока их не заберёт другая комната.","merge.keep":"Оставить","merge.no_area":"без зоны","room.split_header":"Новая комната после разделения","toast.room_saved":"Комната сохранена ({n}). Устройств добавлено: {added}. Обведите следующую или выйдите из разметки.","toast.room_saved_no_area":"Комната сохранена ({n}, без зоны). Обведите следующую или выйдите из разметки.","toast.marker_needs_server":"Редактирование устройств доступно после переноса конфига на сервер","toast.virtual_name_required":"Укажите имя виртуального устройства","toast.marker_saved":"Устройство сохранено","toast.marker_removed":"Устройство удалено с плана","toast.integration_missing":"Интеграция House Plan не установлена — управление недоступно","toast.plan_formats":"Поддерживаются SVG, PNG, JPG, WebP","toast.plan_required":"Загрузите подложку — план этажа обязателен","toast.space_added_onboard":"Пространство добавлено. Обведите комнаты: кликайте по точкам сетки и замкните контур.","toast.space_added":"Пространство добавлено","toast.space_saved":"Пространство сохранено","toast.space_deleted":"Пространство удалено","toast.delete_failed":"Ошибка удаления: {err}","toast.error":"Ошибка: {err}","toast.toggle_target_unavailable":"Цель «{name}» недоступна — действие не выполнено","toast.toggle_targets_unavailable":"Цели недоступны: {names}. Действие не выполнено","toast.file_failed":"Файл «{name}» не загружен: {err}","toast.files_attached":"Прикреплено файлов: {n}","err.unknown":"неизвестная ошибка","err.code":"код {code}","err.too_large":"файл больше {mb} МБ","err.bad_ext":"недопустимый тип (нужен PDF/изображение)","err.unauthorized":"нужны права администратора","editor.title":"Заголовок","editor.default_floor":"Стартовое пространство","editor.default_floor_missing":"Стартовое пространство «{id}» больше не существует. Выберите другое.","editor.floor":"Закреплённое пространство","editor.floor_none":"— не закреплять —","editor.floor_index":"YAML-индекс {index} (сохранён)","fixed_floor.loading":"Загружаем закреплённое пространство…","fixed_floor.invalid_title":"Закреплённое пространство недоступно","fixed_floor.invalid_body":"Проверьте floor в конфигурации карточки. Настроенное значение: {value}","editor.icon_size":"Размер иконок, % ширины плана","editor.show_temperature":"Показывать температуру","editor.live_states":"Живые состояния (вкл/выкл, открыто…)","editor.show_signal":"Показывать сигнал zigbee (LQI)","editor.language":"Язык интерфейса","editor.lang_auto":"Авто (профиль HA)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Правила иконок: какая MDI-иконка достаётся устройству по имени","rules.title":"Правила иконок","rules.hint":"Правила проверяются сверху вниз по строке «имя устройства + модель» (regex без учёта регистра); срабатывает первое совпадение. Если ничего не подошло — решает device class сущности, затем — иконка-заглушка.","rules.pattern_ph":"regex, напр. розетк|plug","rules.icon_ph":"mdi:power-socket-de","rules.add":"Добавить правило","rules.reset":"Сбросить к умолчаниям","rules.test_ph":"Проверьте имя устройства…","rules.invalid":"некорректный regex","rules.saved":"Правила иконок сохранены","btn.up":"Вверх","btn.down":"Вниз","tap.info":"Карточка устройства","tap.more_info":"Диалог HA (more-info)","tap.toggle":"Переключить состояние","marker.tap_label":"Действие по нажатию для этого устройства","tap.toggle_note":"Под селектором показано, какое состояние изменится. Защищённые устройства не переключаются с плана.","import.title":"Создать пространства из этажей HA","import.hint":"Home Assistant уже знает эти этажи. Отметьте, какие превратить в пространства плана — далее для каждого попросим картинку плана. Комнаты затем обводятся вручную по плану.","import.start":"Создать: {n}","import.manual":"Начать с нуля","import.progress":"Этаж {i} из {n}","import.done":"Пространства созданы. Обведите комнаты: кликайте по точкам сетки и замкните контур.","btn.skip":"Пропустить","space.scale_label":"Масштаб (размер клетки сетки)","space.scale_unit":"см на клетку","space.scale_unit_imperial":"дюйм на клетку","space.display_section":"Отображение","space.show_borders":"Всегда отображать границы комнат","space.zero_wall_style":"Стены нулевой толщины","space.zero_wall_dashed":"Пунктирные","space.zero_wall_solid":"Сплошные","space.zero_wall_help":"Пунктирные стены пропускают свет. Сплошные стены блокируют свет, даже при нулевой толщине.","space.show_names":"Отображать названия комнат (перетаскиваются)","space.room_color":"Цвет границ и названий","space.opacity":"Прозрачность","space.fill_label":"Заливка комнат","fill.none":"Нет","fill.lqi":"По силе зигби-сигнала","fill.light":"По освещению","fill.custom":"Свой цвет","space.custom_fill":"Цвет заливки","space.source_file":"У меня есть картинка плана","space.source_draw":"Нет подложки — нарисую комнаты вручную","space.orientation":"Холст","orient.landscape":"Альбомный","orient.portrait":"Портретный","orient.square":"Квадрат","fill.temp":"По температуре","space.temp_min":"Комфорт от","space.temp_max":"до","tip.temp_avg":"средняя температура:","tip.hum_avg":"средняя влажность:","space_card.button":"Перейти к пространству","space_card.not_found":"Пространство «{id}» не найдено","space_card.loading":"Загрузка…","continuity.restore_plan":"Восстанавливаем план…","continuity.restore_connection":"Восстанавливаем подключение к устройствам…","continuity.retry":"Повторить","editor.space":"Пространство","editor.show_button":"Показывать кнопку","editor.button_label":"Текст кнопки","editor.button_target":"Путь дашборда (куда вести)","marker.sub_entity":"сущность","title.general_settings":"Общие настройки","gs.title":"Общие настройки","gs.hint":"Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.","gs.light_group":"Заливка: освещение","gs.light_on":"Свет включён","gs.light_off":"Весь свет выключен","gs.temp_group":"Заливка: температура","gs.temp_cold":"Холодно","gs.temp_ok":"Комфорт","gs.temp_hot":"Жарко","gs.lqi_group":"Заливка: зигби-сигнал","gs.lqi_low":"Слабый сигнал","gs.lqi_high":"Сильный сигнал","gs.reset":"Сбросить к умолчаниям","gs.saved":"Общие настройки сохранены","space.show_lqi":"Показывать зигби-сигнал (LQI) у устройств","space.hide_decor":"Скрыть декоративный слой","space.hide_decor_tip":"Линии, фигуры, надписи и мебель остаются на месте — их видно в редакторе подложки, но не на плане.","space.hide_openings":"Скрыть проёмы","space.hide_openings_tip":"Двери, окна и ворота не рисуются, но продолжают работать: свет проходит, солнце светит в окна, датчики открытия срабатывают. В редакторе плана проёмы видно всегда.","gs.light_none":"Нет источников света","mode.plan":"Редактор плана","mode.devices":"Редактор устройств","display.value":"Значение + состояние","marker.subarea":"без зоны, вручную","device.new":"Новое устройство — откройте его редактор, чтобы снять отметку","opening.unlock_action":"Открыть замок","opening.lock_action":"Закрыть замок","opening.lock_pending":"Выполняется…","title.close_editor":"Закрыть редактор (вернуться к просмотру)","devbar.add":"Добавить","devbar.show_all":"Скрытые и деактивированные","devbar.rules":"Правила иконок","device_inbox.button":"Устройства","device_inbox.title":"Устройства на плане","device_inbox.search":"Поиск по устройствам, сущностям и интеграциям…","device_inbox.add_virtual":"Добавить виртуальное устройство","device_inbox.tab_on_plan":"На плане","device_inbox.tab_available":"Доступны","device_inbox.tab_hidden":"Скрытые","device_inbox.tab_readd":"Доступны снова","device_inbox.only_new":"Только новые","device_inbox.show_entities":"Показывать сущности","device_inbox.show_hidden":"Показывать скрытые на плане","device_inbox.new":"Новое","device_inbox.find":"Найти на плане","device_inbox.edit":"Настроить","device_inbox.hide":"Скрыть","device_inbox.show":"Показать","device_inbox.add":"Добавить","device_inbox.readd":"Добавить заново","device_inbox.hide_available":"Скрыть из списка","device_inbox.show_more":"Показать ещё","device_inbox.more_actions":"Другие действия","device_inbox.find_hidden_hint":"Сначала включите «Показывать скрытые на плане»","device_inbox.show_disabled":"Перед показом активируйте эту привязку в Home Assistant","device_inbox.empty_on_plan":"На плане пока нет подходящих устройств.","device_inbox.empty_available":"Нет доступных устройств для добавления.","device_inbox.empty_hidden":"Скрытых устройств нет.","device_inbox.empty_readd":"Нет удалённых устройств, доступных для повторного добавления.","device_inbox.reason_visible_auto":"Найдено автоматически","device_inbox.reason_visible_explicit":"Добавлено явно","device_inbox.reason_manual_hidden":"Скрыто пользователем","device_inbox.reason_automatic_hidden":"Скрыто автоматически","device_inbox.reason_service_entry":"Служебная запись реестра","device_inbox.reason_excluded_integration":"Интеграция исключена фильтрами устройств","device_inbox.reason_excluded_domain":"Непространственный тип сущности","device_inbox.reason_grouped_light":"Представлено группой света комнаты","device_inbox.reason_represented_by_parent":"Представлено родительским устройством","device_inbox.reason_removed":"Ранее удалено с плана","device_inbox.reason_available":"Можно добавить на план","device_inbox.reason_no_bound_room":"Зона HA не связана с комнатой плана","device_inbox.status_ha_disabled":"Отключено в Home Assistant","device_inbox.status_orphaned":"Привязка больше не найдена в Home Assistant","device_inbox.status_unverified":"Реестр Home Assistant временно недоступен","device_inbox.saved":"Список устройств обновлён","space.roomcard_section":"В карточке комнаты:","space.label_temp":"Температура","space.label_hum":"Влажность","space.label_lqi":"Средний Zigbee-сигнал","space.label_light":"Свет вкл/выкл","roomcard.light_on":"Вкл","roomcard.light_off":"Выкл","roomcard.light_partial":"{on} из {total}","toast.split_pick_inside":"Промежуточные точки разреза — внутри комнаты","mode.decor":"Редактор подложки","decor.select":"Выбрать","decor.line":"Линия","decor.rect":"Прямоугольник","decor.ellipse":"Овал","decor.text":"Надпись","decor.erase":"Стереть","decor.erase_confirm_title":"Стереть объект?","decor.color":"Цвет","decor.width":"Толщина линии","decor.line_style":"Стиль линии","decor.line_style_solid":"Сплошная","decor.line_style_dashed":"Пунктирная","decor.fill":"Заливка","decor.fill_color":"Цвет заливки","decor.length":"Длина","decor.size":"Размер","decor.angle":"Угол поворота, °","decor.text_size":"Размер текста","decor.backdrop_properties":"Свойства картинки-подложки","decor.text_title":"Надпись","decor.object_title":"Редактирование: {kind}","decor.text_label":"Текст","decor.live_group":"Вставить переменную HA","decor.live_entity":"Сущность","decor.live_entity_ph":"выберите сущность","decor.live_attr":"Значение","decor.live_attr_ph":"выберите состояние или атрибут","decor.live_state":"Состояние","decor.backdrop":"Картинка-подложка","decor.backdrop_hint":"Тяните картинку — перемещение; угол — размер; верхнюю ручку — поворот. Shift меняет пропорции или снимает шаг угла.","decor.backdrop_reset":"Вернуть картинку","decor.backdrop_reset_done":"Картинка вернулась на своё место и в свой размер","marker.icon_auto":"Авто: {icon} (по правилам иконок; выберите свою, чтобы заменить)","marker.icon_pin_auto":"Закрепить","mode.plan_tip":"Редактор плана — геометрия дома: рисование и объединение/разделение комнат, привязка к зонам HA, двери, окна и ворота, карточки комнат, масштаб","mode.devices_tip":"Редактор устройств — всё про значки: перетаскивание, клик — настройка привязки/иконки/отображения, виртуальные устройства, правила иконок","mode.decor_tip":"Редактор подложки — чисто визуальный декор под планом: линии, прямоугольники, овалы и надписи, не реагирующие на клики","space.glow_enabled":"Свечение источников света","gs.glow_group":"Свечение источников света","gs.glow_base":"Темнота дома","gs.glow_light":"Цвет света по умолчанию / интенсивность","gs.wall_group":"Стены","gs.wall_fill":"Цвет заливки стен","gs.glow_radius":"Радиус свечения","gs.unit_m":"м","gs.unit_ft":"фут","marker.controls_label":"Управляет другими источниками света","marker.controls_hint":"При действии «Переключить» клик разом переключает все добавленные здесь источники. Собственная сущность маркера управляется напрямую и сюда не добавляется; чтобы считать её светом, включите флаг ниже.","marker.controls_filter":"Поиск ламп и выключателей…","info.controls":"Управляет","marker.glow_radius_label":"Радиус свечения","marker.glow_radius.help":"Задаёт радиус свечения в метрах или футах; пустое значение использует радиус из общих настроек.","marker.glow_radius.help.aria":"Подсказка: радиус свечения","markup.wallthick":"Толщина","markup.select":"Выбрать","title.markup_wallthick":"Толщина — клик по стене задаёт толщину от 0 до 100 см.","markup.hint_wallthick":"клик по стене · Esc закрывает без применения","wallthick.field":"Толщина","wallthick.unit_cm":"см","wallthick.unit_in":"дюйм","wallthick.apply_room":"Применить ко всем стенам комнаты","markup.draw_wall_title":"Толщина каждого нового сегмента стены (0–100 см). Общие стены сохраняют существующее значение.","room.queue_progress":"Комната {current} из {total}","toast.wall_rooms_saved":"Создано комнат: {n}","toast.wall_chain_saved":"Цепочка стен сохранена","toast.wallthick_pick":"Кликните по стене","toast.wallthick_set":"Толщина стены задана","toast.wallthick_cleared":"Толщина стены убрана","toast.physical_range":"Введите значение от {min} до {max} {unit}","toast.zero_wall_opening_conflict":"Сначала удалите проём на этом участке стены.","toast.zero_wall_ambiguous":"Не удалось однозначно выбрать участок стены. Уточните геометрию узла.","toast.zero_wall_migration_blocked":"Пространство не преобразовано: {reason}. Данные не изменены.","toast.physical_angle":"Введите угол поворота от 0° до 90°, не включая 90°","toast.physical_limit":"В пространстве достигнут лимит объектов этого типа","toast.geometry_unsafe":"Изменение отменено: геометрию стен нельзя безопасно построить.","junction.limit_angle":"Слишком острый угол между стенами: {actual}°, минимум {limit}°.","junction.limit_valence":"Слишком много стен в одном узле: {actual}, максимум {limit}.","junction.limit_length":"Слишком короткий участок стены: {actual} см, минимум {limit} см.","junction.limit_distance":"Стены и узлы слишком близко: {actual} см, минимум {limit} см.","junction.limit_clearance":"Внутри комнаты не остаётся места: {actual} см², минимум {limit} см².","toast.wall_model_migration_blocked":"Не удалось обновить модель стен: {reason}. План не изменён. Запустите «Оптимизировать планы»; если ошибка повторится, исправьте конфликтующую геометрию стен.","toast.wall_model_client_outdated":"Обновите карточку и перезагрузите страницу перед редактированием плана.","wall_model.reason.invalid-room":"некорректный контур комнаты","wall_model.reason.zero-length":"отрезок стены нулевой длины","wall_model.reason.third-owner":"стена принадлежит более чем двум комнатам","wall_model.reason.duplicate-id":"конфликт идентификаторов стен","wall_model.reason.thickness-conflict":"конфликт значений толщины стены","wall_model.reason.opening-host":"проём нельзя однозначно привязать к стене","toast.delete_room_pick":"Кликните внутри комнаты, которую нужно удалить","toast.plan_snap_ambiguous":"Увеличьте масштаб, чтобы выбрать узел стены","toast.wall_repair_ambiguous":"У контура несколько возможных соединений. Увеличьте масштаб и соедините стены явно.","toast.wall_repair_too_large":"Разрыв больше 2 см. Соедините стены перед созданием комнаты.","toast.wall_repair_changed":"Геометрия стен изменилась. Попробуйте создать комнату ещё раз.","toast.opening_on_zero_wall":"Проёмы на стене нулевой толщины запрещены","marker.from_ha_option":"Выбрать из списка HA","marker.show_entities":"Показывать сущности","marker.show_entities_tip":"Добавляет в список не только устройства, но и все их сущности","marker.pick_ph":"Выберите устройство…","room.open_area":"Открыть зону в HA","view.volumetric":"Объёмный вид","view.flat":"Плоский вид","kiosk.title":"Размеры на этом экране","kiosk.hint":"Хранится только на этом устройстве — у каждого настенного планшета или ТВ свои удобные размеры.","kiosk.icon_scale":"Размер значков устройств","kiosk.font_scale":"Размер текста карточек комнат","editor.kiosk":"Режим настенного устройства (киоск)","editor.cycle":"Автосмена пространств каждые N секунд (киоск, 0 = выкл)","room.settings_title":"Настройки комнаты","room.settings_section":"Настройки комнаты (переопределяют пространство)","room.fill_label":"Заливка в ЭТОЙ комнате","fill.inherit":"Как у пространства","room.custom_fill_space":"Цвет пространства","room.custom_fill_own":"Цвет комнаты","room.temp_src_label":"Источник температуры","room.hum_src_label":"Источник влажности","room.src_average":"Средняя по датчикам комнаты (по умолчанию)","room.src_pick":"Конкретное устройство или сущность HA","room.src_ph":"Выберите источник…","toast.room_updated":"Комната обновлена","space.card_font":"Размер шрифта карточек комнат (всё пространство)","room.sizes_section":"Размеры шрифтов","room.name_scale":"Размер названия","room.label_scale":"Размер подписей","preview.room_name":"Гостиная","toast.cfg_reload_failed":"Не удалось перечитать план с сервера: {err}","room.settings_short":"Настройки комнаты","room.unnamed":"Комната без имени","marker.use_climate_temp":"Учитывать температуру устройства в комнате","marker.use_climate_temp_tip":"Добавляет current_temperature кондиционера или термостата в среднюю температуру комнаты. Внешний бейдж со значением настраивается отдельно ниже.","marker.value_badge_title":"Бейдж со значением","marker.value_badge.help":"Показывает одно выбранное значение рядом со значком. Не влияет на комнатные показатели, свет, Glow и действие по нажатию.","marker.value_badge.help.aria":"Подсказка: бейдж со значением устройства","marker.value_badge_enabled":"Отображать бейдж со значением","marker.value_badge_source":"Значение","marker.value_badge_source.help":"Выберите конкретное состояние, поддерживаемый атрибут или производное значение этого устройства.","marker.value_badge_source.help.aria":"Подсказка: источник значения бейджа","marker.value_badge_position":"Расположение","marker.value_badge_position.help":"Выбранная сторона сохраняется постоянно и не переворачивается автоматически у края плана.","marker.value_badge_position.help.aria":"Подсказка: расположение бейджа","marker.value_badge_right":"Справа","marker.value_badge_bottom":"Снизу","marker.value_badge_left":"Слева","marker.value_badge_top":"Сверху","marker.value_badge_empty":"У этого устройства нет доступных значений","marker.value_badge_static":"Статичный значок не показывает живые значения. Настройка сохранится.","marker.value_badge_missing":"Источник недоступен","marker.value_badge_missing_hint":"Сохранённый источник сейчас недоступен. Можно сохранить его, отключить бейдж или выбрать другой.","marker.value_badge_duplicate":"То же значение уже показано внутри значка.","marker.value_badge_state":"Состояние · {name}","marker.value_badge_marker_state":"Состояние источника света · {name}","marker.value_badge_lqi":"Качество Zigbee-сигнала (среднее)","marker.value_badge_attr_current_temperature":"Текущая температура · {name}","marker.value_badge_attr_temperature":"Целевая температура · {name}","marker.value_badge_attr_current_humidity":"Текущая влажность · {name}","marker.value_badge_attr_humidity":"Влажность · {name}","marker.value_badge_attr_current_position":"Положение · {name}","marker.value_badge_attr_percentage":"Скорость · {name}","marker.value_badge_attr_brightness":"Яркость · {name}","marker.value_badge_attr_volume_level":"Громкость · {name}","marker.value_badge_attr_battery_level":"Заряд батареи · {name}","marker.value_badge_attr_fan_speed":"Скорость вентилятора · {name}","marker.light_role_label":"Является источником света","marker.light_role.help":"Определяет, является ли сам маркер пространственным источником света. Привязанные выше лампы остаются независимыми источниками для комнаты.","marker.light_role.help.aria":"Подсказка: является ли устройство источником света","marker.light_role_auto_yes":"Автоматически (источник света)","marker.light_role_auto_no":"Автоматически (не источник света)","marker.light_role_always":"Всегда источник света","marker.light_role_never":"Никогда не источник света","marker.light_entity_label":"Ведущая сущность света","marker.light_entity.help":"Для составного устройства выберите сущность, состояние, цвет и яркость которой представляют этот источник на плане. Автоматический вариант сохраняет совместимость со старыми планами.","marker.light_entity.help.aria":"Подсказка: ведущая сущность света","marker.light_entity_auto":"Автоматически ({entity})","marker.light_entity_none":"нет управляемой сущности","marker.light_entity_missing":"Сохранённая сущность {entity} недоступна. Временно используется {fallback}; выбор восстановится, если сущность вернётся.","marker.toggle_entity_label":"Переключаемая сущность","marker.toggle_entity.help":"Для составного устройства выберите собственную лампу или переключатель, которой управляет нажатие. Автоматический вариант сохраняет прежние правила цели и не зависит от ведущей сущности света.","marker.toggle_entity.help.aria":"Подсказка: переключаемая сущность","marker.toggle_entity_auto":"Автоматически ({entity})","marker.toggle_entity_none":"нет собственной управляемой сущности","marker.toggle_entity_missing":"Сохранённой сущности {entity} больше нет среди выбираемых каналов этого маркера. Временно используется {fallback}; выбор восстановится, если сущность вернётся.","marker.glow_color_label":"Цвет и яркость свечения","marker.glow_mode.help":"Используйте данные источника, задайте только цвет или зафиксируйте цвет и яркость. Минимум — 1%; чтобы отключить источник, выберите «Никогда».","marker.glow_mode.help.aria":"Подсказка: цвет и яркость свечения","marker.glow_mode_auto":"Из источника","marker.glow_mode_color":"Задать цвет","marker.glow_mode_fixed":"Задать цвет и яркость","marker.glow_color":"Цвет свечения","marker.glow_brightness":"Яркость","marker.glow_disabled_never":"Настройки свечения недоступны: маркер явно исключён из источников света.","marker.glow_disabled_auto":"В автоматическом режиме у этого маркера не найден пространственный источник света.","marker.glow_disabled_no_entity":"Для пространственного источника нет активной управляемой сущности Home Assistant.","marker.glow_passive_hint":"У источника нет собственных данных Home Assistant. Задайте цвет и яркость вручную; радиус остаётся доступным.","marker.control_broken":"Сохранённый источник отсутствует или больше не отмечен как источник света","marker.control_missing_label":"Отсутствующий источник на плане","marker.control_passive":"пассивный источник","toast.marker_control_cycle":"Такая связь создаст циклическую цепочку управления светом.","toast.marker_binding_required":"Сначала выберите устройство Home Assistant, затем добавьте управляемый источник света.","confirm.unlock":"Открыть замок «{name}»?","toast.files_migrate_failed":"Не удалось перенести вложения к новой привязке, ссылки остались на старые файлы: {err}","space.pick_saved":"Уже загруженные","space.pick_saved_hint":"Планы, сохранённые на сервере, включая отцеплённые ранее","space.no_saved":"На сервере пока нет сохранённых планов.","space.loading":"Загрузка…","space.used_by":"используется: {list}","space.in_use":"План используется пространством — сначала отцепите его","btn.use":"Выбрать","confirm.delete_plan":"Удалить файл плана «{name}» с сервера? Действие необратимо.","toast.plans_list_failed":"Не удалось получить список планов: {err}","toast.plan_delete_failed":"Не удалось удалить план: {err}","marker.hide":"Скрыть","marker.hide_tip":"Устройство исчезнет с плана после сохранения, но продолжит участвовать в расчёте сигнала комнаты. Вернуть его можно через каталог «Устройства» в редакторе устройств.","marker.show":"Показать","marker.show_tip":"Устройство снова появится на плане после сохранения.","marker.hidden_ghost":"Устройство скрыто пользователем","marker.ha_disabled_device":"Устройство деактивировано в Home Assistant и скрыто с плана.","marker.ha_disabled_entity":"Сущность деактивирована в Home Assistant и скрыта с плана.","marker.ha_disabled_all_entities":"У устройства нет активных сущностей Home Assistant, поэтому оно скрыто с плана.","marker.ha_registry_limited":"Полный реестр Home Assistant недоступен этому пользователю. Неподтверждённый объект временно нельзя показывать или использовать.","marker.delete_tip":"Полностью удалить устройство с плана и из всех расчётов. Позже его можно добавить заново.","tap.run":"Запустить автоматизацию/скрипт/сцену","tap.cover":"Открыть/закрыть (шторы/жалюзи)","marker.run_target_label":"Что запускать","marker.run_search_ph":"Поиск: автоматизация, скрипт или сцена…","marker.run_target_gone":"Цель {id} не найдена — выберите заново","marker.tap_confirm":"Спрашивать подтверждение","marker.tap_confirm_tip":"Перед выполнением показать диалог подтверждения — защита от случайных нажатий.","marker.toggle_hint_single":"Цель: {name} ({id}).","marker.virtual_light_target":"Виртуальный свет: {name}.","marker.virtual_light_current":"Ручное состояние: {state} → {effect}.","marker.virtual_light_state_on":"включён","marker.virtual_light_state_off":"выключен","marker.toggle_hint_group":"Переключатся источники ({count}): {names}.","marker.toggle_hint_current":"Сейчас: {state} → {effect}.","marker.toggle_hint_group_current":"Сейчас включено {on} из {count} → {effect}.","marker.toggle_hint_skipped":"Не будут затронуты ({count}): {targets}.","marker.toggle_effect_turn_on":"включится","marker.toggle_effect_turn_off":"выключится","marker.toggle_effect_open":"откроется","marker.toggle_effect_close":"закроется","marker.toggle_effect_stop":"остановится","marker.toggle_effect_toggle":"состояние переключится","marker.toggle_skip_missing":"не найдено","marker.toggle_skip_ha_disabled":"деактивировано в HA","marker.toggle_skip_unavailable":"недоступно","marker.toggle_skip_unsupported":"переключение не поддерживается","marker.toggle_skip_secure":"заблокировано из соображений безопасности","marker.toggle_none_no_binding":"У устройства нет привязки, состояние переключать нечем. По нажатию ничего не произойдёт.","marker.toggle_none_no_actionable_entity":"У устройства нет состояния, которое можно переключить. По нажатию ничего не произойдёт.","marker.toggle_none_configured_targets_missing":"Настроенные цели сейчас недоступны. Собственная сущность устройства не будет подставлена вместо них.","marker.toggle_none_ha_disabled":"Цель деактивирована в Home Assistant и не может использоваться на плане.","marker.toggle_none_unavailable":"Цель сейчас недоступна. По нажатию ничего не произойдёт.","marker.toggle_none_unsupported":"Для этой сущности Home Assistant не предоставляет безопасного переключения. По нажатию ничего не произойдёт.","marker.toggle_none_secure":"Переключение замков, сигнализации и защитных ворот с плана заблокировано из соображений безопасности.","run.automation":"автоматизация","run.script":"скрипт","run.scene":"сцена","confirm.tap_run":"Запустить «{name}»?","confirm.tap_toggle":"Переключить «{name}»?","confirm.tap_cover":"Открыть/закрыть «{name}»?","confirm.current_state":"Текущее состояние: {state}","confirm.expected_state":"После переключения: {state}","confirm.group_current":"включено {on} из {total}","confirm.group_all_on":"все включены","confirm.group_all_off":"все выключены","confirm.unavailable_targets":"Недоступно: {count}","confirm.expected_by_ha":"Состояние определит Home Assistant","confirm.state_on":"Включено","confirm.state_off":"Выключено","confirm.state_open":"Открыто","confirm.state_closed":"Закрыто","confirm.state_opening":"Открывается","confirm.state_closing":"Закрывается","confirm.state_stopped":"Остановлено","confirm.state_unknown":"Неизвестно","toast.run_started":"Запущено: {name}","toast.run_target_missing":"Цель запуска не найдена — проверьте настройки устройства","toast.run_target_required":"Выберите автоматизацию, скрипт или сцену","toast.tap_target_changed":"Цель действия изменилась. Повторите попытку.","toast.virtual_light_toggle_failed":"Не удалось переключить виртуальный свет: {err}","toast.value_badge_source_required":"Выберите значение для бейджа","btn.run":"Выполнить","vac.section":"Робот-пылесос: живая позиция","vac.autocal":"Настроить автоматически","vac.live":"Живая позиция на плане","vac.trail":"Показывать путь робота","vac.cal_maps":"Откалиброваны карты: {maps}","vac.autocal_no_rooms":"Интеграция не отдаёт список комнат — откройте «Подогнать вручную»","vac.autocal_no_match":"Не совпали имена комнат (нужно ≥3 общих) — откройте «Подогнать вручную»","vac.autocal_done":"Готово: привязка по {rooms} комнатам. Запустите уборку и проверьте","vac.cal_need_pos":"Робот сейчас не отдаёт координаты — запустите уборку и поставьте на паузу","vac.cal_done":"Калибровка сохранена. Запустите уборку и проверьте","vac.cal_cancelled":"Калибровка отменена","vac.fit":"Подогнать вручную","vac.fit_hint":"Перетащите карту робота на место, растяните за уголки","vac.fit_rotate":"Повернуть 90°","vac.fit_mirror":"Отразить","vac.trail_never":"Не показывать никогда","vac.trail_cleaning":"Во время уборки","vac.trail_always":"Показывать всегда","gs.bg_group":"Фон сцены","gs.bg_color":"Цвет фона вокруг плана","gs.bg_default":"Как в теме","gs.bg_theme":"по умолчанию — из темы","gs.bg_mode":"Фон плана","gs.bg_static":"Статичный цвет","gs.bg_daynight":"Следует за Солнцем","gs.bg_daynight_hint":"Фон проходит рассвет, день, закат и ночь по данным sun.sun, а при их недоступности — по локальным часам. Компас для фона не нужен.","gs.sun_group":"Солнце","gs.sun_missing":"Сущность sun.sun не найдена — фон следует локальным часам, а оконные лучи недоступны.","gs.north":"Север на плане","gs.north_ph":"не задан","gs.north_hint":"Север нужен только для оконных лучей (шаг 1°, с Shift 15°); солнечный фон работает без него.","gs.north_clear":"Сбросить","gs.north_letter":"С","gs.sun_rays":"Солнце в окнах","gs.about_group":"О карточке","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · документация и issues","gs.about_telegram":"Чат в Telegram","space.bg_color":"Цвет фона вокруг плана","space.bg_inherit":"Наследовать общий","space.bg_inherited":"наследуется из общих настроек","space.bg_mode":"Фон плана","space.north":"Север на плане (переопределение)","space.north_inherited":"наследуется: {v}","space.sun_rays":"Солнце в окнах","space.sun_inherit":"Наследовать общий","space.sun_on":"Вкл","space.sun_off":"Выкл","canvas.far_objects":"Объектов далеко от плана: {n}","canvas.show_far":"Показать","canvas.home_tip":"План там — нажмите, чтобы вписать","gs.grid_group":"Обслуживание планов","gs.grid_hint":"Обновляет модели данных, выравнивает элементы по сетке и объединяет лишние фрагменты стен. Перед записью будет показан точный отчёт.","gs.align_all":"Оптимизировать планы","gs.align_title":"Оптимизировать планы","gs.align_none":"Все планы уже используют актуальную и оптимальную модель данных.","gs.optimize_no_automatic_changes":"Автоматических изменений нет. Проверьте пункты ниже.","gs.align_count":"Сдвинется элементов: {n} из {total}, максимум на {cm} см.","gs.align_where":"Наибольший сдвиг — в пространстве «{s}».","gs.align_turned":"Проёмов с исправлением угла: {n}.","gs.align_removed_drafts":"Схлопнувшиеся на сетке некорректные контуры удалены: {n}.","gs.optimize_redundant_drafts":"Сохранённые цепочки стен, полностью скрытые стенами комнат, удалены: {n}.","gs.align_preflight_failed":"Не удалось безопасно проверить геометрию следующих пространств: {spaces}{more}.","gs.align_preflight_hint":"Планы не изменены. Скопируйте диагностику кнопкой ниже и приложите её к отчёту об ошибке вместе с экспортом пространства.","gs.preflight_reason_prepare-exception":"Не удалось подготовить геометрию пространства (исключение при сборке модели)","gs.preflight_reason_wall-null":"Тело стен не построилось (объединение вернуло пустоту)","gs.preflight_reason_wall-degraded-extra":"Тело стен деградировало с лишней геометрией","gs.preflight_reason_wall-failed-core":"Ядро тела стен не собралось","gs.preflight_reason_wall-exception":"Построение стен упало с исключением","gs.preflight_reason_floor-null":"Контур пола не построился","gs.preflight_reason_floor-exception":"Построение пола упало с исключением","gs.preflight_copy":"Скопировать диагностику","gs.preflight_copied":"Диагностика скопирована","gs.preflight_update_hint":"Версии карточки и интеграции различаются — обновите House Plan и повторите.","gs.align_preflight_space":"Пространство {n}","gs.align_preflight_more":" и ещё {n}","gs.optimize_changes":"Миграций модели: {m}; обновлено пространств: {c}; устранён шум координат: {p}; объединено отрезков реальных стен: {w}; объединено отрезков стен нулевой толщины: {s}; независимых: {i}.","gs.zero_walls_migrated":"Преобразовано виртуальных участков: {n}.","gs.wall_segments_migrated":"Стабилизировано сегментов стен: {n}.","gs.optimize_lattice_summary":"Канонизировано шумовых значений координат: {n}; максимальный сдвиг: {cm} см.","gs.optimize_lattice_space":"{space}: канонизировано значений координат: {n}; оставлено значений вне сетки: {far}.","gs.optimize_coincident_partitions":"Скрытые участки независимых стен поглощены стенами комнат: {n}.","gs.optimize_openings_rehosted":"Проёмы перепривязаны к стенам комнат: {n}.","gs.optimize_walls_straightened":"Выпрямлено стен: {n}; максимальное перемещение: {cm} см.","gs.optimize_walls_straightened_where":"Максимальная правка стены: {s}.","gs.optimize_walls_straighten_skipped":"Оставлено почти осевых стен, которые нельзя безопасно выпрямить: {n}.","gs.optimize_glow_migration":"Старый Glow: пространств — {spaces} → без заливки данных + независимый Glow; комнат — {rooms} → наследуемая заливка + независимый Glow.","gs.optimize_references":"Исправлено ссылок: пространства — {spaces}; комнаты — {rooms}; позиции — {positions}; устройств отвязано от отсутствующих пространств — {detached}.","gs.optimize_reference_more":", и ещё {n}","gs.optimize_orphans_removed":"Убрано забытых записей: {total} — подписи комнат: {rooms}; устройства: {devices}; групповые метки: {groups}. Все они принадлежали пространствам, удалённым ранее.","gs.optimize_live_positions":"Старые позиции в удалённых пространствах принадлежат существующим объектам: {n}{names}. Они будут сохранены.","gs.optimize_live_positions_remove":"Старые позиции в удалённых пространствах принадлежат существующим объектам: {n}{names}. Они выбраны для удаления.","gs.optimize_live_names":": {names}{more}","gs.optimize_live_remove":"Убрать старые позиции","gs.optimize_live_keep":"Сохранить старые позиции","gs.optimize_live_selected":"Старые позиции будут убраны после применения оптимизации.","gs.optimize_unverified":"Не удалось безопасно проверить позиций: {n}. Они оставлены без изменений.","gs.optimize_registry_limited":"Для безопасной проверки нужен полный доступ администратора к реестрам Home Assistant.","gs.optimize_vacuum_warning":"Сопоставления комнат пылесоса требуют проверки: {n}.","gs.optimize_details":"Подробности","gs.optimize_details_more":"И ещё записей: {n}.","gs.optimize_detail_removed":"будет удалено","gs.optimize_detail_live":"будет сохранено","gs.optimize_detail_unverified":"не проверено","gs.optimize_detail_room_label":"подпись комнаты","gs.optimize_detail_device":"устройство","gs.optimize_detail_group":"групповая метка","gs.optimize_detail_unknown":"неизвестный владелец","gs.optimize_detail_item":"{status}: {kind} {id}; прежнее пространство {space}","gs.align_warn":"Элементы, намеренно поставленные между узлами, будут сдвинуты. После операции доступна одна отмена — только до следующего изменения плана.","gs.align_run":"Оптимизировать","gs.align_done":"Планы оптимизированы: сдвинуто элементов — {n}, обслужено записей — {m}, исправлено ссылок — {r}","gs.optimize_undo":"Отменить последнюю оптимизацию","gs.optimize_undone":"Последняя оптимизация отменена","decor.furniture":"Мебель","furn.title":"Библиотека мебели","furn.symbol":"Символ","furn.group_furniture":"Мебель","furn.group_appliance":"Техника","furn.group_sanitary":"Сантехника","furn.group_other":"Прочее","furn.width":"Ширина","furn.depth":"Глубина","furn.pick_hint":"Выберите символ и кликните по плану.","furn.place_hint":"Кликните по плану — предмет встанет к ближайшей стене. Shift — свободно.","furn.sym_sofa":"Диван","furn.sym_armchair":"Кресло","furn.sym_coffee_table":"Журнальный столик","furn.sym_table_dining":"Обеденный стол","furn.sym_table_round":"Круглый стол","furn.sym_chair":"Стул","furn.sym_desk":"Письменный стол","furn.sym_bed_double":"Двуспальная кровать","furn.sym_bed_single":"Односпальная кровать","furn.sym_nightstand":"Тумбочка","furn.sym_wardrobe":"Шкаф","furn.sym_bookshelf":"Стеллаж","furn.sym_fridge":"Холодильник","furn.sym_stove":"Плита","furn.sym_dishwasher":"Посудомоечная машина","furn.sym_washer":"Стиральная машина","furn.sym_dryer":"Сушильная машина","furn.sym_tv":"Телевизор","furn.sym_ac":"Кондиционер","furn.sym_water_heater":"Бойлер","furn.sym_toilet":"Унитаз","furn.sym_bathtub":"Ванна","furn.sym_shower":"Душ","furn.sym_sink":"Раковина","furn.sym_kitchen_sink":"Кухонная мойка","furn.sym_bidet":"Биде","furn.sym_stairs":"Лестница","furn.sym_fireplace":"Камин","furn.sym_plant":"Растение","furn.sym_rug":"Ковёр","common.yes":"Да","common.no":"Нет","vac.diag_source":"Источник","vac.diag_platform":"Интеграция","vac.diag_status":"Статус","vac.diag_position":"Позиция","vac.diag_rooms":"Комнаты","vac.diag_rooms_value":"{total} · совпало имён: {matched} · {readiness}","vac.autocal_ready":"автокалибровка доступна","vac.autocal_not_ready":"нужно 3 совпавших имени","vac.diag_path":"Путь интеграции","vac.diag_map":"ID карты","vac.source_none":"не выбран","vac.source_status_ok":"Готов","vac.source_status_missing":"Не найден","vac.source_status_disabled":"Деактивирован в Home Assistant","vac.source_status_unavailable":"Недоступен","vac.source_status_unverified":"Нельзя проверить с текущими правами","vac.source_status_unsupported":"Нет данных позиции","vac.source_status_none":"Нет источника","vac.source_banner_missing":"Сохранённый источник больше не существует. Он не заменён автоматически: выберите другой или восстановите его в Home Assistant.","vac.source_banner_disabled":"Сохранённый источник деактивирован в Home Assistant. Активируйте его там или выберите другой.","vac.source_banner_unverified":"Текущих прав Home Assistant недостаточно для проверки источника. Привязка сохранена и не будет заменена автоматически.","vac.choose_source":"Выбрать источник","vac.source_auto":"Автоматически","vac.source_auto_hint":"Использовать совместимую сущность этого устройства","vac.all_cameras":"Все камеры","vac.all_cameras_warn":"Камера может не отдавать данные робота. Выбирайте её только если это карта вашего пылесоса.","vac.all_cameras_empty":"Других сущностей камеры не найдено.","vac.platform_unknown":"интеграция неизвестна","vac.cap_position":"позиция","vac.cap_rooms_short":"комнаты","vac.cap_path":"путь","vac.cap_map":"ID карты","vac.cap_none":"данные робота не обнаружены","vac.xcme_hint":"Включите атрибуты Xiaomi Cloud Map Extractor:","vac.documentation":"Документация","vac.residual_title":"Проверьте автокалибровку","vac.residual_message":"Совпавшие комнаты расходятся максимум на {error}. Примените приблизительную калибровку, подгоните её вручную либо отмените без изменения сохранённых настроек.","vac.apply_proposal":"Применить","gs.backup_group":"Резервная копия и перенос","gs.backup_hint":"Скачайте переносимую JSON-копию или проверьте и импортируйте копию, созданную House Plan.","backup.export_open":"Экспорт","backup.import_open":"Импорт","backup.export_title":"Экспорт House Plan","backup.import_title":"Импорт House Plan","backup.export_hint":"Выберите, должна ли копия содержать всю конфигурацию House Plan или только текущее пространство.","backup.full":"Полная копия","backup.current_space":"Текущее пространство","backup.current_space_title":"Текущее пространство: {title}","backup.no_current_space":"Нет текущего пространства","backup.plan_only":"Только планировка","backup.plan_only_hint":"Сохранить комнаты, стены, проёмы, декор и позиции подписей комнат без устройств и привязок Home Assistant.","backup.plan_only_preview":"Файл содержит только планировку","backup.privacy_warning":"Архив сохраняет названия, идентификаторы Home Assistant и точные координаты. Внутренние планы и вложения указываются ссылками, но не вкладываются; текущие состояния и маршруты пылесосов не включаются.","backup.download":"Скачать JSON","backup.export_done":"Резервная копия скачана","backup.reading":"Проверяем резервную копию…","backup.revalidated":"После предпросмотра план изменился. Сводка обновлена — проверьте её и подтвердите ещё раз.","backup.error.unauthorized":"У вас нет прав на экспорт или импорт этого плана.","backup.error.not_ready":"House Plan ещё не готов. Повторите попытку через несколько секунд.","backup.error.too_large":"Размер резервной копии превышает 8 МиБ.","backup.error.invalid_json":"Выбранный файл не является корректным JSON.","backup.error.invalid_format":"Выбранный файл не является резервной копией House Plan.","backup.error.unsupported_export_version":"Эта версия формата резервной копии не поддерживается установленной версией.","backup.error.future_model":"Резервная копия создана в более новой версии модели данных House Plan.","backup.error.invalid_config":"Резервная копия содержит некорректную конфигурацию House Plan.","backup.error.wall_model_migration_blocked":"Не удалось безопасно обновить модель стен из резервной копии. Сначала оптимизируйте исходный план и повторите экспорт.","backup.error.invalid_layout":"Резервная копия содержит некорректные позиции объектов.","backup.error.invalid_content":"Резервная копия содержит некорректные или несогласованные ссылки на файлы.","backup.error.space_not_found":"Выбранное пространство больше не существует.","backup.error.capacity_exceeded":"Добавление этой копии превысит ограничения размера плана.","backup.error.preview_expired":"Время предпросмотра истекло. Выберите файл резервной копии заново.","backup.error.preview_owner_mismatch":"Этот предпросмотр принадлежит другому пользователю Home Assistant.","backup.error.conflict":"После предпросмотра план изменился. Проверьте обновлённую сводку.","backup.error.content_confirmation_required":"Подтвердите отсоединение недоступных локальных файлов.","backup.error.commit_failed":"Не удалось безопасно применить копию. Предыдущий план восстановлен или ожидает восстановления.","backup.error.missing_plan":"После предпросмотра исчез связанный файл плана. Проверьте копию ещё раз.","backup.error.missing_content":"После предпросмотра исчез связанный локальный файл. Проверьте копию ещё раз.","backup.error.marker_control_missing":"В резервной копии отсутствует связанный источник света на плане.","backup.error.marker_control_not_light":"Связанная цель больше не отмечена как источник света.","backup.error.marker_control_self":"Источник света не может управлять самим собой.","backup.error.marker_control_cycle":"Резервная копия содержит циклическую цепочку управления светом.","backup.error.duplicate_marker_control":"Резервная копия содержит повторяющуюся связь с источником света.","backup.error.no_backup":"Нет снимка импорта или оптимизации, который можно восстановить.","backup.same_source":"Создано на этом экземпляре Home Assistant","backup.foreign_source":"Создано на другом экземпляре Home Assistant","backup.created":"Создано: {value}","backup.versions":"Карточка {card}; интеграция {integration}; модель данных {model}","backup.count_spaces":"Пространств: {n}","backup.count_rooms":"Комнат: {n}","backup.count_walls":"Стен: {n}","backup.count_openings":"Проёмов: {n}","backup.count_decor":"Объектов декора: {n}","backup.count_markers":"Устройств: {n}","backup.count_layout":"Позиций: {n}","backup.bindings":"Привязки — устройства: {device}, сущности: {entity}, виртуальные: {virtual}; позиций без пространства: {legacy}","backup.binding_status":"Статус на целевом экземпляре — активных: {active}, деактивированных: {disabled}, отсутствующих: {missing}","backup.missing_areas":"На целевом экземпляре отсутствуют зоны: {areas}","backup.dropped_marker_links":"Связи с источниками света вне переносимого пространства пропущены: {n}.","backup.repaired_target_refs":"Существующих ссылок восстановлено этим импортом: {n}.","backup.preserved_unresolved_refs":"Неоднозначные ссылки сохранены без изменений: {n}.","backup.preserved_unresolved_hint":"House Plan ничего не угадывал и не удалял. После импорта запустите «Оптимизировать планы», чтобы проверить оставшиеся ссылки.","backup.import_details":"Подробности восстановления ссылок","backup.import_detail.incoming_remapped":"Ссылок обновлено внутри импортируемой копии: {n}","backup.import_detail.target_repaired":"Существующих ссылок восстановлено: {n}","backup.import_detail.preserved_unresolved":"Неразрешимых ссылок сохранено: {n}","backup.import_detail.collisions":"Конфликтов назначения безопасно сохранено: {n}","backup.import_detail.dropped_links":"Входящих связей пропущено по правилам переноса: {n}","backup.import_detail.bounded_lineages":"Чрезмерно вложенных идентификаторов ограничено: {n}","backup.replace_warning":"Текущая конфигурация и расположение будут заменены. Загруженные файлы не удаляются. Одну отмену можно выполнить до следующего изменения плана.","backup.foreign_bookkeeping":"Служебные списки известных и новых устройств другого экземпляра импортированы не будут.","backup.final_name":"Название нового пространства","backup.target_settings":"Глобальные настройки целевого экземпляра не изменятся.","backup.duplicates":"Уже существующих привязок: {n}","backup.skip":"Пропустить повторяющиеся устройства","backup.virtual_copy":"Добавить безопасные статичные виртуальные копии","backup.content":"Связанное содержимое","backup.content_available":"доступно локально","backup.content_external":"внешняя ссылка","backup.content_detach_required":"будет отвязано","backup.confirm_detach":"Я понимаю, что недоступные внутренние планы и вложения будут отвязаны от импортируемой конфигурации.","backup.replace":"Заменить","backup.add":"Добавить пространство","backup.space_done":"Пространство импортировано: комнат — {rooms}, устройств — {markers}, существующих ссылок восстановлено — {refs}","backup.full_done":"Копия восстановлена: пространств — {spaces}, комнат — {rooms}, устройств — {markers}","backup.undo_import":"Отменить последний полный импорт","backup.import_undone":"Полный импорт отменён"}}],ra=oa;function la(e){return"string"==typeof e?e.trim().replaceAll("_","-").toLowerCase():""}const ca=new Map(aa.map(e=>[la(e.code),e]));function ha(e){return ca.get(la(e))}function da(e,t){const i=[{value:"",label:e},...aa.map(({code:e,nativeLabel:t})=>({value:e,label:t}))],s="string"==typeof t?t:"";return s&&!i.some(e=>e.value===s)&&i.push({value:s,label:ha(s)?.nativeLabel??s}),i}function pa(e,t){return function(e,t,i,s){const n=new Map(i.map(e=>[la(e),e])),o=n.get(la(e));if(o)return o;const a=la(t),r=n.get(a);return r||(n.get(a.split("-")[0]||"")??s)}(t,e?.locale?.language||e?.language,aa.map(({code:e})=>e),"en")}function ua(e,t,i){const s=ha(e)?.dictionary;return ss(s?.[t]??ra[t]??t,i)}function _a(e,t){const i=ha(e)?.dictionary[t]??ra[t];return"string"==typeof i&&i.trim().length>0}class ma extends he{constructor(){super(...arguments),this.deviceName="",this._metadataConnection=null,this._demoUntil=0,this._demoTimer=0,this._demoGeneration=1,this._demoKind=null,this._reducedMotion=!1,this._onMotionChange=e=>{this._reducedMotion=e.matches,this.requestUpdate()},this._startShortDemo=()=>{const e=this.presentation;e&&"icon_ripple"===e.display&&"none"===e.pulse.kind&&(this._demoGeneration++,this._demoKind="short",this._demoUntil=Date.now()+3300,window.clearTimeout(this._demoTimer),this._demoTimer=window.setTimeout(()=>{this._demoUntil=0,this._demoTimer=0,this._demoKind=null,this.requestUpdate()},3330),this.requestUpdate())},this._toggleContinuousDemo=()=>{const e=this.presentation;e&&"icon_ripple"===e.display&&"none"===e.pulse.kind&&("continuous"===this._demoKind?this._clearDemo():(this._clearDemo(),this._demoGeneration++,this._demoKind="continuous",this._demoUntil=Number.POSITIVE_INFINITY),this.requestUpdate())}}connectedCallback(){super.connectedCallback(),this._motionMedia=window.matchMedia?.("(prefers-reduced-motion: reduce)"),this._reducedMotion=!!this._motionMedia?.matches,this._motionMedia?.addEventListener?.("change",this._onMotionChange),this._ensureMetadata()}disconnectedCallback(){this._metadataRelease?.(),this._metadataRelease=void 0,this._metadataConnection=null,this._clearDemo(),this._motionMedia?.removeEventListener?.("change",this._onMotionChange),this._motionMedia=void 0,super.disconnectedCallback()}willUpdate(e){e.has("hass")&&this._ensureMetadata();const t=this.presentation,i=e.get("presentation");e.has("presentation")&&i?.binding!==t?.binding&&this._clearDemo(),t&&this._demoKind&&("icon_ripple"!==t.display||"none"!==t.pulse.kind)&&this._clearDemo()}_ensureMetadata(){const e=this.hass?.connection||null;e&&e!==this._metadataConnection&&(this._metadataRelease?.(),this._metadataConnection=e,this._metadataRelease=Qo(this.hass,()=>this.requestUpdate()))}_clearDemo(){window.clearTimeout(this._demoTimer),this._demoTimer=0,this._demoUntil=0,this._demoKind=null}get _lang(){return pa(this.hass)}_t(e,t){return ua(this._lang,e,t)}_providerText(e){return"houseplan"===e.domain?this._t("marker.preview.virtual_provider"):e.label.toLowerCase()===e.domain.toLowerCase()?e.domain:`${e.label} (${e.domain})`}_providers(){return this.presentation&&this.registry?function(e,t,i,s=ea(e)){if("virtual"===t)return[{domain:"houseplan",label:"House Plan",confidence:"registry-owner"}];const n=t.indexOf(":");if(n<1)return[];const o=t.slice(0,n),a=t.slice(n+1);if("entity"===o){const t=na(e,a,i,s);return t?[t]:[]}if("device"!==o)return[];const r=i.devices?.[a]||e?.devices?.[a];if(!r)return[];const l=[r.config_entry_id,...Array.isArray(r.config_entries)?r.config_entries:[]].filter(e=>"string"==typeof e&&!!e).map(t=>{const i=s.configEntries[t];return ia(e,i?.domain,"registry-owner",s,t)});if(l.some(Boolean))return sa(l);const c=[];for(const[t,n]of Object.entries(i.entities||{}))n?.device_id===a&&null==n?.disabled_by&&c.push(na(e,t,i,s));return c.some(Boolean)?sa(c):sa((Array.isArray(r.identifiers)?r.identifiers:[]).map(t=>{const i=Array.isArray(t)?t[0]:null;return ia(e,i,"identifier-fallback",s)}))}(this.hass,this.presentation.binding,this.registry,ea(this.hass)):[]}_providerSummary(e){if(!e.length)return this._t("marker.preview.unknown_provider");const t=e.slice(0,2).map(e=>this._providerText(e)),i=e.length-t.length;return t.join(", ")+(i>0?` · ${this._t("marker.preview.more_sources",{n:i})}`:"")}_sourceSummary(e){const t=e.visualSources;return t.length?1===t.length?`${t[0].name} · ${t[0].eid} · ${this._sourceProvider(t[0].eid)}`:2===t.length?t.map(e=>e.name).join(" · "):this._t("marker.preview.multiple_sources",{n:t.length}):this._t("marker.preview.no_source")}_stateSummary(e){if(!e.visualSources.length)return this._t("marker.preview.no_state");if(1===e.visualSources.length)return e.visualSources[0].stateText||this._t("marker.preview.no_state");const t=[...new Set(e.visualSources.map(e=>e.stateText).filter(Boolean))];return 1===t.length?t[0]:this._t("marker.preview.mixed_states")}_reason(e){return this._t(`marker.preview.reason.${e}`)}_sourceProvider(e){if(!this.registry)return this._t("marker.preview.unknown_provider");const t=na(this.hass,e,this.registry,ea(this.hass));return t?this._providerText(t):this._t("marker.preview.unknown_provider")}render(){const e=this.presentation;if(!e)return G;const t=!!this._demoKind&&this._demoUntil>Date.now()&&"icon_ripple"===e.display&&"none"===e.pulse.kind,i=t?(()=>{const t=function(e,t,i,s,n=null){return{kind:t,reason:"short"===t?"event":"running",generation:Math.max(1,Math.trunc(i)),expiresAt:"short"===t?n:null,color:e.color,diameterScale:s?1:e.diameterScale,animated:!s,reducedMotionIndicator:s?"dot":"none"}}(e.pulse,this._demoKind,this._demoGeneration,this._reducedMotion,"short"===this._demoKind?this._demoUntil:null);return{...e,pulse:t,classes:[...e.classes.filter(e=>!e.startsWith("pulse-")),`pulse-${t.kind}`,...t.generation%2==0?["pulse-gen2"]:[]]}})():e,s=this._providers(),n="none"!==e.pulse.kind,o=i.scale*(i.pulse.animated?i.pulse.diameterScale:1);let a=-o/2,r=o/2,l=-o/2,c=o/2;if(i.valueBadge){const e=i.scale*Math.min(4,Math.max(.9,.29*i.valueBadge.text.length+.28)),t=.7*i.scale,s=.6*i.scale;"right"===i.valueBadge.position&&(r=Math.max(r,s+e)),"left"===i.valueBadge.position&&(a=Math.min(a,-s-e)),"top"===i.valueBadge.position&&(l=Math.min(l,-s-t)),"bottom"===i.valueBadge.position&&(c=Math.max(c,s+t))}if(null!=i.lqiText){const e=i.scale*("bottom"===i.valueBadge?.position?1.55:.95);c=Math.max(c,e)}const h=Math.min(1,2.35/Math.max(1,r-a),2.35/Math.max(1,c-l)),d=Math.round(100*h),p=[`left:calc(50% - ${54*((a+r)/2)*h}px)`,`top:calc(50% - ${54*((l+c)/2)*h}px)`,h<1?`transform:scale(${h})`:""].filter(Boolean).join(";"),u=["dev",Uo(this.hass),...i.classes,"virtual"===i.binding?"virtual":"",i.haDisabled?"ghost ha-disabled":"",null!=i.valueText?"valonly":""].filter(Boolean).join(" "),_=Go(i).join(";"),m=t?this._t("continuous"===this._demoKind?"marker.preview.demo_continuous_notice":"marker.preview.demo_short_notice"):this._reason(e.explanation.reason),g=[this.deviceName,this._providerSummary(s),this._stateSummary(e),m,e.valueFullText||"",Io(e.valueBadge),null!=e.lqiText&&e.lqiBand?this._t(`marker.lqi_a11y_${e.lqiBand}`,{value:e.lqiText}):""].filter(Boolean).join(". ");return q`
+ `}function Yo(e,t,i){return{kind:"none",reason:"none",generation:e,expiresAt:null,color:t,diameterScale:i,animated:!1,reducedMotionIndicator:"none"}}const Jo=new WeakMap;function Xo(e){const t=function(e){const t=e?.connection||e;return!t||"object"!=typeof t&&"function"!=typeof t?null:t}(e);if(!t)return null;let i=Jo.get(t);return i||(i={revision:0,loaded:!1,configEntries:{},manifests:{},listeners:new Set,refs:0},Jo.set(t,i)),i}function Zo(e){e.revision++;for(const t of[...e.listeners])try{t()}catch{}}function Qo(e,t){const i=Xo(e);if(!i)return()=>{};i.refs++,i.listeners.add(t),async function(e,t){t.loading||"function"!=typeof e?.callWS?t.loading:t.loaded&&t.loadedAt&&Date.now()-t.loadedAt<3e5||(t.loading=(async()=>{const[i,s]=await Promise.allSettled([e.callWS({type:"config_entries/get"}),e.callWS({type:"manifest/list"})]);"fulfilled"===i.status&&Array.isArray(i.value)&&(t.configEntries=Object.fromEntries(i.value.filter(e=>"string"==typeof e?.entry_id).map(e=>[e.entry_id,e]))),"fulfilled"===s.status&&Array.isArray(s.value)&&(t.manifests=Object.fromEntries(s.value.filter(e=>"string"==typeof e?.domain).map(e=>[e.domain,e]))),t.loaded=!0,t.loadedAt=Date.now(),t.loading=void 0,Zo(t)})().catch(()=>{t.loaded=!0,t.loadedAt=Date.now(),t.loading=void 0,Zo(t)}),t.loading)}(e,i),async function(e,t){if(t.unsubscribe||t.subscribing)return t.subscribing;const i=e?.connection?.subscribeMessage;"function"==typeof i&&(t.subscribing=(async()=>{try{t.unsubscribe=await i.call(e.connection,e=>{const i=Array.isArray(e)?e:Array.isArray(e?.entries)?e.entries:e?[e]:[];let s=!1;const n={...t.configEntries};for(const e of i){const t=e?.entry||e?.data?.entry||("string"==typeof e?.entry_id?e:null),i=t?.entry_id;if("string"!=typeof i)continue;const o=e?.type||e?.action||e?.data?.action;"removed"===o||"remove"===o?delete n[i]:n[i]=t,s=!0}s&&(t.configEntries=n,Zo(t))},{type:"config_entries/subscribe"})}catch{}finally{0===t.refs&&(t.unsubscribe?.(),t.unsubscribe=void 0),t.subscribing=void 0}})(),t.subscribing)}(e,i);let s=!1;return()=>{s||(s=!0,i.listeners.delete(t),i.refs=Math.max(0,i.refs-1),i.refs||(i.unsubscribe?.(),i.unsubscribe=void 0))}}function ea(e){const t=Xo(e);return t?{revision:t.revision,loaded:t.loaded,configEntries:t.configEntries,manifests:t.manifests}:{revision:0,loaded:!1,configEntries:{},manifests:{}}}function ta(e,t,i=ea(e),s){const n=i.manifests[t];return function(e,t){if("function"!=typeof e?.localize)return"";try{const i=`component.${t}.title`,s=e.localize(i);return"string"==typeof s&&s!==i?s:""}catch{return""}}(e,t)||String(n?.name||"")||String(s||"")||t.replace(/_/g," ")}function ia(e,t,i,s,n){const o=String(t||"").trim();if(!o)return null;const a=n?s.configEntries[n]:null;return{domain:o,label:ta(e,o,s,a?.title),configEntryId:n,configEntryTitle:a?.title,confidence:i}}function sa(e){const t={"registry-owner":0,"entity-platform":1,"identifier-fallback":2},i=new Set;return e.filter(e=>!!e).sort((e,i)=>t[e.confidence]-t[i.confidence]||e.label.localeCompare(i.label)||e.domain.localeCompare(i.domain)).filter(e=>{const t=`${e.domain}\n${e.configEntryId||""}`;return!i.has(t)&&(i.add(t),!0)})}function na(e,t,i,s=ea(e)){const n=i.entities?.[t]||e?.entities?.[t];if(!n)return null;const o=n.config_entry_id||void 0,a=o?s.configEntries[o]?.domain:"";return ia(e,n.platform||a,"entity-platform",s,o)}var oa={"editor.context_actions":"Actions: {object}","editor.tool_options":"Tool options: {tool}","editor.palette":"Palette: {tool}","editor.open_group":"Tool group: {group}","editor.group_active":"{group} — active: {item}","editor.disabled_action":"{action} is unavailable: {reason}","color_picker.title":"Color picker","color_picker.hue":"Hue","color_picker.saturation":"Saturation","color_picker.value":"Brightness","color_picker.hex":"Hex color","color_picker.invalid_hex":"Enter a 3- or 6-digit hex color","btn.properties":"Properties","btn.keep_as_walls":"Keep as walls","btn.delete_room_keep_walls":"Delete room, keep walls","btn.delete_room_with_walls":"Delete room and walls","title.markup_select":"Select and edit walls, columns and saved outlines","title.markup_column":"Column: click a grid point to place a square column","markup.partition":"Partition","markup.column":"Column","markup.hint_column":"click a grid point to place a square column","history.draft_segment":"Add room-draft segment","history.draft_merge":"Join unfinished room outlines","history.draft_segment_delete":"Remove room-draft segment","history.partition_add":"Add partition","history.column_add":"Add column","history.physical_edit":"Change physical object","history.physical_delete":"Delete physical object","history.physical_move":"Move physical object","history.contour_to_partitions":"Convert contour to closed walls","history.wall_chain_finish":"Finish wall chain","history.wall_face_batch":"Create rooms from walls","toast.column_duplicate":"A column with the same centre and outer size already exists","confirm.delete_draft":"Delete the whole unfinished room contour?","confirm.delete_draft_segment":"Delete this draft segment? The remaining contour may split in two.","physical.partition_properties":"Partition properties","physical.column_properties":"Column properties","physical.draft_properties":"Draft segment properties","physical.shape":"Shape","physical.square":"Square","physical.circle":"Circle","physical.diameter":"Diameter","physical.side":"Side","physical.rotation":"Rotation angle","physical.length":"Length","physical.allowed_range":"Allowed: {min}–{max} {unit}","physical.delete_segment":"Delete segment","physical.delete_draft":"Delete entire outline","physical.column_size_title":"Outer side of the square column placed by the click (1–150 cm).","card.title":"House plan","count.devices":"{n} dev.","empty.no_spaces":"No spaces yet.","empty.add_first":"Add the first space and upload a floor plan.","empty.install":'Install the House Plan integration and add it in "Devices & services".',"btn.add_space":"Add space","btn.cancel":"Cancel","btn.save":"Save","btn.close":"Close","btn.delete":"Delete","btn.edit":"Edit","btn.open_in_ha":"Open in HA","btn.reset":"Reset","btn.attach":"Attach…","btn.upload":"Upload…","btn.replace":"Replace…","title.zoom_in":"Zoom in","title.zoom_out":"Zoom out","title.zoom_fit":"Fit all","title.add_device":"Add a device to the plan","title.show_all":"Show hidden and disabled devices as service markers (this tab only)","title.markup":"Room markup: grid, lines, outlines","title.configure_space":"Configure space","title.add_space":"Add space","title.markup_add":"Walls: draw a continuous chain; Shift locks to 45°. Changing tool or leaving the editor finishes it as independent walls","title.markup_merge":"Merge: click one room, then the neighbour it shares a wall with","title.markup_split":"Split a room: click the room, then two points on its walls","title.markup_delroom":"Delete room: click inside a room and confirm","title.need_plan":"Upload a floor-plan image","markup.add":"Walls","markup.merge":"Merge","markup.split":"Split","markup.resize":"Resize","title.markup_resize":"Resize rooms: drag an available horizontal or vertical wall handle","markup.hint_resize":"drag an available wall handle · the wall stops at the first unsafe position · Esc cancels · Ctrl+Z — undo","resize.disabled.diagonal":"This wall is angled; Resize supports only horizontal and vertical walls","resize.disabled.side-angle":"An adjoining wall is angled; Resize requires both adjoining walls to meet this wall at right angles","resize.disabled.duplicate-physical-wall":"A separate partition, unfinished outline, or column overlaps this wall; remove or move it before resizing","resize.disabled.partial-shared":"A neighbouring room uses only part of this wall, so it cannot be moved safely as one shared wall","resize.disabled.unequal-shared":"The neighbouring room uses different endpoints or wall length, so the walls cannot move together safely","resize.disabled.multiple-rooms":"Moving this wall would affect more than two rooms","resize.disabled.thickness-conflict":"The wall thickness cannot be preserved safely","resize.disabled.opening-conflict":"An opening on this wall cannot be moved safely","resize.disabled.invalid-geometry":"This wall cannot be resized without changing the plan structure","resize.limit_stopped":"Wall stopped: the next step would break a junction limit","resize.commit_failed":"Resize was cancelled because the final plan did not pass the geometry check","resize.preview_failed":"Resize stopped at the last safe position because the plan geometry could not be preserved","markup.opening":"Opening","markup.delete_room":"Delete room","history.undo":"Undo","history.redo":"Redo","history.undo_named":"Undo: {name} (Ctrl+Z)","history.redo_named":"Redo: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Nothing to undo","history.redo_empty":"Nothing to redo","history.undone":"Undone: {name}","history.redone":"Redone: {name}","history.add_room":"Create room","history.split_room":"Split room","history.merge_rooms":"Merge rooms","history.resize_room":"Resize room","history.wall_thickness":"Change wall thickness","history.add_opening":"Add door or window opening","history.edit_opening":"Edit opening","history.move_opening":"Move opening","history.delete_opening":"Delete opening","history.delete_room":"Delete room","history.delete_room_keep_walls":"Delete room, keep walls","history.delete_room_with_walls":"Delete room and walls","history.decor_add":"Add decor object","history.decor_edit":"Edit decor object","history.decor_move":"Move decor object","history.decor_transform":"Transform decor object","history.decor_delete":"Delete decor object","history.backdrop_transform":"Transform plan backdrop","title.markup_opening":"Openings: click a wall to place, click an opening to edit","opening.new":"New opening","opening.edit":"Opening","opening.door":"Door","opening.window":"Window","opening.gate":"Gate","opening.passage":"Open passage","opening.passage_binding_warning":"Saving will remove the open/close sensor and lock.","opening.invalid_passage_fields":"The open passage on plan “{room}” has unsupported settings: {fields}.","opening.type_label":"Type","opening.length_label":"Length, cm","opening.contact_label":"Open/close sensor","opening.lock_label":"Lock","opening.none":"— none —","opening.search_ph":"Search: name or entity id…","opening.invert":"Invert open/closed","opening.flip_h":"Hinge on the other jamb","opening.flip_v":"Opens to the other side","opening.open":"Open","opening.closed":"Closed","opening.locked":"Locked","opening.unlocked":"Unlocked","opening.state_unknown":"unavailable","opening.no_entities":"No sensors bound — a static symbol on the plan.","toast.opening_no_wall":"Click next to a room wall or independent wall","opening.host_partition":"Independent wall","opening.partition_orphan":"The independent wall for this opening no longer exists","opening.partition_jamb_margin":"Leave at least {distance} between the opening and the end of the wall","opening.rebind_partition":"Attach to another independent wall","confirm.delete_partition_openings_title":"Delete wall and openings?","confirm.delete_partition_openings_body":"This wall contains {count} opening(s). They will be deleted together.","confirm.delete_partition_openings_item":"• {type}, {length}","markup.delete":"Delete","markup.hint_points":"points: {n} · Shift — 45° steps · Esc — finish chain · Ctrl+Z — undo a point · closing an area offers a room","markup.hint_start":"click a grid dot to start a wall chain","tip.lqi":"average zigbee signal:","tip.area":"area: {value}","info.device_header":"Device on the plan","info.model":"Model","info.state":"State","info.link":"Link","info.manuals":"Manuals","info.none":"No additional information","marker.new_device":"New device","marker.name_label":"Name (shown on the plan)","marker.name_ph":"Name","marker.binding_label":"Bind to an HA device","marker.binding_disabled":"disabled in Home Assistant","marker.virtual_option":"Virtual device (no binding)","marker.search_ph":"Search device / group…","marker.nothing_found":"nothing found","marker.room_label":"Room","marker.room_override":" (override placement)","marker.room_choose":"— select a room —","marker.room_auto":"— by device area (auto) —","marker.icon_label":"Icon","marker.icon_ph":"mdi:… (empty = auto)","marker.display_label":"Display","display.badge":"Icon + state","display.icon_ripple":"Icon + state and activity","display.static_icon":"Always static icon","marker.display_hint":"Icon + state changes the plate for work, open, alarm and unavailable states. Icon + state and activity additionally shows a short pulse for events and a continuous pulse for ongoing work, movement or presence. Value + state replaces the icon with one unambiguous HA value. A static icon never reacts to device states.","marker.display_hint_badge":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon":"The icon and dynamic plate show device state without the ordinary activity pulse. Red alarms remain visible.","marker.display_hint_icon_ripple":"The icon, dynamic plate and pulse show a short pulse for events and a continuous pulse for work, motion or presence. Red alarms have separate priority.","marker.display_hint_value":"One unambiguous Home Assistant value replaces the icon while the plate continues to show state. Red alarms remain visible.","marker.display_hint_static_icon":"The theme-aware shell and icon always stay the same. State, activity, unavailability and alarms do not change the face.","marker.static_alarm_warning":"This device can report alarms. Static display hides the marker's visual alarm indication.","marker.preview.title":"Display preview","marker.preview.actual":"Now","marker.preview.example":"Example","marker.preview.integration":"Provided by","marker.preview.source":"Display source","marker.preview.current_state":"Current state","marker.preview.result":"On the plan","marker.preview.details":"Source details","marker.preview.select_source":"Choose a Home Assistant device or entity to see its actual display.","marker.preview.unknown_provider":"Unknown integration","marker.preview.virtual_provider":"House Plan · virtual device","marker.preview.no_source":"No active source","marker.preview.no_state":"No current state","marker.preview.mixed_states":"Several different states","marker.preview.multiple_sources":"{n} sources","marker.preview.more_sources":"+{n} more","marker.preview.scaled":"The preview is scaled to {n}% to fit. The saved size is unchanged.","marker.preview.demo_activity":"Show activity example","marker.preview.demo_notice":"Temporary activity example; the actual device state is unchanged.","marker.preview.demo_short":"Show short activity","marker.preview.demo_continuous":"Show continuous activity","marker.preview.stop_continuous":"Stop continuous activity","marker.preview.demo_short_notice":"Short activity example; the actual device state is unchanged.","marker.preview.demo_continuous_notice":"Continuous activity example; the actual device state is unchanged.","marker.preview.demo_already_visible":"The current state already shows a real activity or alarm effect.","marker.preview.reduced_motion":"System reduced-motion is enabled, so ordinary activity is shown as a dot.","marker.preview.reason.neutral":"Neutral dark plate","marker.preview.reason.working":"Yellow plate: the device is working now","marker.preview.reason.working_activity":"Yellow plate and activity effect: the device is working now","marker.preview.reason.open":"Orange plate: physically open or unlocked","marker.preview.reason.cover_icon_state":"Neutral plate; the cover entity controls the displayed state and icon","marker.preview.reason.presence":"Activity effect while presence is detected","marker.preview.reason.event":"Short activity effect after a detected event","marker.preview.reason.transition":"Activity effect while the device is moving or changing state","marker.preview.reason.media_neutral":"Media devices stay on a neutral dark plate while available","marker.preview.reason.unavailable":"Subdued neutral plate: unavailable or off","marker.preview.reason.alarm":"Red alarm plate; alarm indication is always shown","marker.preview.reason.live_states_disabled":"Live-state styling is disabled for this card","marker.preview.reason.value_no_state":"No usable state value; the icon is shown instead","marker.preview.reason.value_ambiguous_sources":"Several possible value sources; the icon is shown instead","marker.preview.reason.value_non_scalar":"The source did not return a simple value; the icon is shown instead","marker.preview.reason.value_virtual":"A virtual device has no Home Assistant value; the icon is shown instead","marker.preview.reason.vacuum_live_plan_only":"Live vacuum position and trail are available only on the full plan","marker.preview.reason.hidden_design_preview":"The device is hidden on the plan; this design preview remains visible","marker.preview.reason.composite_power_source":"State comes from the device Power entity; auxiliary switches are ignored","marker.preview.reason.activity_display_disabled":"The selected display mode does not show ordinary activity effects","marker.preview.reason.ha_disabled":"The Home Assistant binding is disabled and will be hidden on the plan","marker.preview.reason.orphaned":"The saved Home Assistant binding can no longer be found","marker.preview.reason.static_icon":"Static mode: device state does not change the icon","marker.activity_color":"Activity pulse color","marker.ripple_size":"Activity pulse size","marker.activity_alarm_note":"Color and size affect ordinary activity only and do not change red alarms.","marker.pulse_a11y_alarm":"Alarm","marker.pulse_a11y_event":"Recent event","marker.pulse_a11y_presence":"Presence detected","marker.pulse_a11y_transition":"Changing state","marker.pulse_a11y_running":"Working","marker.state_a11y_neutral":"Default state","marker.state_a11y_working":"Working","marker.state_a11y_open":"Open","marker.state_a11y_locked":"Locked","marker.state_a11y_unlocked":"Unlocked","marker.state_a11y_alarm":"Alarm","marker.state_a11y_unavailable":"Unavailable","marker.lqi_a11y_low":"LQI {value}, low signal","marker.lqi_a11y_mid":"LQI {value}, medium signal","marker.lqi_a11y_high":"LQI {value}, high signal","marker.size_label":"Icon size / rotation","marker.angle_label":"Rotate","marker.model_label":"Model","marker.model_ph":"e.g. Aqara T&H","marker.link_label":"Link","marker.desc_label":"Description","marker.desc_ph":"Notes, specs…","marker.manuals_label":"Manuals (PDF etc.)","marker.sub_device":"device","marker.sub_z2m_group":" · Z2M group","marker.sub_group":"group","marker.sub_helper":"helper","space.new":"New space","space.header":"Space","space.title_label":"Title","space.title_ph":"e.g. Garage","space.plan_label":"Floor plan (background)","space.no_plan":"no plan image","space.plan_alt":"plan","room.new":"New room","room.name_label":"Display name","room.name_ph":"e.g. Terrace","room.area_label":"Home Assistant area (unassigned)","room.no_area_option":"— no area —","room.default_name":"Room","device.unnamed":"unnamed","device.light_group":"light group","device.fallback":"device","device.virtual":"virtual device","confirm.delete_room":'Delete room "{name}"?',"confirm.delete_room_title":'Delete room "{name}"?',"confirm.delete_room_body":"Choose whether the room's exclusive physical walls should remain. Walls shared with another room are always kept.","confirm.merge_rooms":'Merge rooms "{a}" and "{b}"?',"confirm.remove_marker":'Delete "{name}" from the plan? The device will disappear completely and stop contributing to plan data. You can add it again later.',"confirm.erase_decor":"Erase the {kind} object? You can undo this action from the editor history.","confirm.delete_space":'Delete space "{title}" with all its rooms and markup?',"space.delete_blocked":"This space is still used by {n} device(s). Move them to another space or delete them first.","toast.pos_save_failed":"Failed to save position: {err}","toast.no_entity":"The device has no suitable entity","toast.ha_disabled_action":"A Home Assistant object that is disabled cannot be used on the plan.","toast.ha_disabled_show_device":"A device disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_show_entity":"An entity disabled in Home Assistant cannot be shown on the plan. Enable it in Home Assistant first.","toast.ha_disabled_add":"A disabled Home Assistant object cannot be added to the plan. Enable it in Home Assistant first.","toast.ha_binding_unverified":"The object status could not be verified through the Home Assistant registry. Display and actions are temporarily unavailable.","toast.markup_needs_server":"Markup is available after the config is moved to the server","toast.space_order_changed":"Order changed. If any card pins its floor by number, check those panels.","toast.conflict":"Config was changed in another window — data refreshed, repeat your last action","toast.cfg_save_failed":"Failed to save config: {err}","toast.room_overlap":"The outline overlaps room “{name}” — rooms must not overlap","toast.contour_min_edges":"Draw at least two edges before closing the outline","toast.contour_cannot_close":"The outline cannot close because it is degenerate or intersects itself","toast.merge_not_adjacent":"Only rooms that share a wall can be merged","toast.rooms_merged":"Rooms merged into “{name}”","toast.split_pick_wall":"Start the cut on the room’s wall","toast.split_bad_cut":"The cut must run wall to wall inside the room, without crossing walls or itself","merge.header":"Merge rooms","merge.hint":"The merged room keeps one name and one area. The other area is released — its devices leave the plan until another room claims it.","merge.keep":"Keep","merge.no_area":"no area","room.split_header":"New room from the split","toast.room_saved":"Room saved ({n}). Devices added: {added}. Outline the next one or exit markup.","toast.room_saved_no_area":"Room saved ({n}, no area). Outline the next one or exit markup.","toast.marker_needs_server":"Device editing is available after the config is moved to the server","toast.virtual_name_required":"Enter a name for the virtual device","toast.marker_saved":"Device saved","toast.marker_removed":"Device deleted from the plan","toast.integration_missing":"The House Plan integration is not installed — management unavailable","toast.plan_formats":"Supported formats: SVG, PNG, JPG, WebP","toast.plan_required":"Upload a floor plan — it is required","toast.space_added_onboard":"Space added. Outline the rooms: click grid dots and close the contour.","toast.space_added":"Space added","toast.space_saved":"Space saved","toast.space_deleted":"Space deleted","toast.delete_failed":"Delete failed: {err}","toast.error":"Error: {err}","toast.toggle_target_unavailable":"Target “{name}” is unavailable — no action was performed","toast.toggle_targets_unavailable":"Targets are unavailable: {names}. No action was performed","toast.file_failed":'File "{name}" was not uploaded: {err}',"toast.files_attached":"Files attached: {n}","err.unknown":"unknown error","err.code":"code {code}","err.too_large":"file larger than {mb} MB","err.bad_ext":"unsupported type (PDF/image expected)","err.unauthorized":"administrator rights required","editor.title":"Title","editor.default_floor":"Initial space","editor.default_floor_missing":"Initial space “{id}” no longer exists. Choose another space.","editor.floor":"Fixed space","editor.floor_none":"— not fixed —","editor.floor_index":"YAML index {index} (preserved)","fixed_floor.loading":"Loading the fixed space…","fixed_floor.invalid_title":"The fixed space is unavailable","fixed_floor.invalid_body":"Check floor in the card configuration. The configured value is: {value}","editor.icon_size":"Icon size, % of plan width","editor.show_temperature":"Show temperature","editor.live_states":"Live states (on/off, open…)","editor.show_signal":"Show zigbee signal (LQI)","editor.language":"Interface language","editor.lang_auto":"Auto (HA profile)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Icon rules: which MDI icon devices get by name","rules.title":"Icon rules","rules.hint":"Rules are checked top-down against “device name + model” (case-insensitive regex); the first match wins. When nothing matches, the entity device class decides, then the generic chip icon.","rules.pattern_ph":"regex, e.g. plug|socket","rules.icon_ph":"mdi:power-socket-de","rules.add":"Add rule","rules.reset":"Reset to defaults","rules.test_ph":"Try a device name…","rules.invalid":"invalid regex","rules.saved":"Icon rules saved","btn.up":"Up","btn.down":"Down","tap.info":"Device card","tap.more_info":"HA more-info dialog","tap.toggle":"Toggle state","marker.tap_label":"Tap action for this device","tap.toggle_note":"The resolved state is shown below. Secure devices are never toggled from the plan.","import.title":"Create spaces from HA floors","import.hint":"Your Home Assistant already knows these floors. Pick the ones to turn into plan spaces — you will upload a floor-plan image for each one next. Rooms are then outlined by hand on the plan.","import.start":"Create {n} space(s)","import.manual":"Start from scratch","import.progress":"Floor {i} of {n}","import.done":"Spaces created. Outline the rooms: click grid dots and close the contour.","btn.skip":"Skip","space.scale_label":"Scale (grid cell size)","space.scale_unit":"cm per cell","space.scale_unit_imperial":"in per cell","space.display_section":"Display","space.show_borders":"Always show room borders","space.zero_wall_style":"Zero-thickness walls","space.zero_wall_dashed":"Dashed","space.zero_wall_solid":"Solid","space.zero_wall_help":"Dashed walls let light through. Solid walls block light even at zero thickness.","space.show_names":"Show room names (drag to move)","space.room_color":"Border & name color","space.opacity":"Opacity","space.fill_label":"Room fill","fill.none":"None","fill.lqi":"Zigbee signal","fill.light":"Lights","fill.custom":"Custom color","space.custom_fill":"Fill color","space.source_file":"I have a floor-plan image","space.source_draw":"No image — I'll outline rooms by hand","space.orientation":"Canvas","orient.landscape":"Landscape","orient.portrait":"Portrait","orient.square":"Square","fill.temp":"Temperature","space.temp_min":"Comfort from","space.temp_max":"to","tip.temp_avg":"average temperature:","tip.hum_avg":"average humidity:","space_card.button":"Open the space plan","space_card.not_found":"Space “{id}” not found","space_card.loading":"Loading…","continuity.restore_plan":"Restoring floor plan…","continuity.restore_connection":"Restoring device connection…","continuity.retry":"Try again","editor.space":"Space","editor.show_button":"Show button","editor.button_label":"Button label","editor.button_target":"Target dashboard path","marker.sub_entity":"entity","title.general_settings":"General settings","gs.title":"General settings","gs.hint":"Fill colors apply to every space; each color has its own opacity. Which fill mode a space uses is set in that space's dialog.","gs.light_group":"Fill: lights","gs.light_on":"Lights on","gs.light_off":"All lights off","gs.temp_group":"Fill: temperature","gs.temp_cold":"Cold","gs.temp_ok":"Comfortable","gs.temp_hot":"Hot","gs.lqi_group":"Fill: zigbee signal","gs.lqi_low":"Weak signal","gs.lqi_high":"Strong signal","gs.reset":"Reset to defaults","gs.saved":"General settings saved","space.show_lqi":"Show zigbee signal (LQI) next to devices","space.hide_decor":"Hide the decorative layer","space.hide_decor_tip":"Lines, shapes, labels and furniture stay where they are — visible in the backdrop editor, not on the plan.","space.hide_openings":"Hide openings","space.hide_openings_tip":"Door, window and gate symbols are not drawn, but the openings keep working: light passes through, the sun comes in at a window, contact sensors still open them. The plan editor always shows them.","gs.light_none":"No light sources","mode.plan":"Plan editor","mode.devices":"Device editor","display.value":"Value + state","marker.subarea":"no area, manual","device.new":"New device — open its editor to dismiss","opening.unlock_action":"Unlock","opening.lock_action":"Lock","opening.lock_pending":"Working…","title.close_editor":"Close editor (back to view)","devbar.add":"Add","devbar.show_all":"Hidden and disabled","devbar.rules":"Icon rules","device_inbox.button":"Devices","device_inbox.title":"Devices on the plan","device_inbox.search":"Search devices, entities and integrations…","device_inbox.add_virtual":"Add virtual device","device_inbox.tab_on_plan":"On plan","device_inbox.tab_available":"Available","device_inbox.tab_hidden":"Hidden","device_inbox.tab_readd":"Available again","device_inbox.only_new":"New only","device_inbox.show_entities":"Show entities","device_inbox.show_hidden":"Show hidden on plan","device_inbox.new":"New","device_inbox.find":"Find on plan","device_inbox.edit":"Edit","device_inbox.hide":"Hide","device_inbox.show":"Show","device_inbox.add":"Add","device_inbox.readd":"Add again","device_inbox.hide_available":"Hide from list","device_inbox.show_more":"Show more","device_inbox.more_actions":"More actions","device_inbox.find_hidden_hint":"Enable “Show hidden on plan” first","device_inbox.show_disabled":"Activate this binding in Home Assistant before showing it","device_inbox.empty_on_plan":"No matching devices are on the plan yet.","device_inbox.empty_available":"No devices are available to add.","device_inbox.empty_hidden":"There are no hidden devices.","device_inbox.empty_readd":"No removed devices are available to add again.","device_inbox.reason_visible_auto":"Discovered automatically","device_inbox.reason_visible_explicit":"Added explicitly","device_inbox.reason_manual_hidden":"Hidden by user","device_inbox.reason_automatic_hidden":"Hidden automatically","device_inbox.reason_service_entry":"Service-only registry entry","device_inbox.reason_excluded_integration":"Integration excluded by device filters","device_inbox.reason_excluded_domain":"Non-spatial entity type","device_inbox.reason_grouped_light":"Represented by a room light group","device_inbox.reason_represented_by_parent":"Represented by its parent device","device_inbox.reason_removed":"Previously removed from the plan","device_inbox.reason_available":"Available to add to the plan","device_inbox.reason_no_bound_room":"HA area is not linked to a plan room","device_inbox.status_ha_disabled":"Disabled in Home Assistant","device_inbox.status_orphaned":"Binding is no longer present in Home Assistant","device_inbox.status_unverified":"Home Assistant registry is temporarily unavailable","device_inbox.saved":"Device list updated","space.roomcard_section":"Room card shows:","space.label_temp":"Temperature","space.label_hum":"Humidity","space.label_lqi":"Average Zigbee signal","space.label_light":"Lights on/off","roomcard.light_on":"On","roomcard.light_off":"Off","roomcard.light_partial":"{on} of {total}","toast.split_pick_inside":"Intermediate cut points must be inside the room","mode.decor":"Background editor","decor.select":"Select","decor.line":"Line","decor.rect":"Rectangle","decor.ellipse":"Oval","decor.text":"Text","decor.erase":"Erase","decor.erase_confirm_title":"Erase object?","decor.color":"Color","decor.width":"Line width","decor.line_style":"Line style","decor.line_style_solid":"Solid","decor.line_style_dashed":"Dashed","decor.fill":"Fill","decor.fill_color":"Fill color","decor.length":"Length","decor.size":"Size","decor.angle":"Rotation angle, °","decor.text_size":"Text size","decor.backdrop_properties":"Plan backdrop properties","decor.text_title":"Text label","decor.object_title":"Edit {kind}","decor.text_label":"Text","decor.live_group":"Insert HA variable","decor.live_entity":"Entity","decor.live_entity_ph":"choose an entity","decor.live_attr":"Value","decor.live_attr_ph":"choose state or attribute","decor.live_state":"State","decor.backdrop":"Backdrop image","decor.backdrop_hint":"Drag to move; pull a corner to resize; use the top handle to rotate. Shift changes proportions or frees the angle.","decor.backdrop_reset":"Reset the picture","decor.backdrop_reset_done":"The picture is back at its original place and size","marker.icon_auto":"Auto: {icon} (by icon rules; pick one to override)","marker.icon_pin_auto":"Pin","mode.plan_tip":"Plan editor — the geometry of the home: draw and split/merge rooms, bind them to HA areas, place doors, windows and gates, move room cards, set the scale","mode.devices_tip":"Device editor — everything about icons: drag to position, click to edit binding/icon/display, add virtual devices, icon rules","mode.decor_tip":"Background editor — purely visual decor under the plan: lines, rectangles, ovals and text labels that never react to clicks","space.glow_enabled":"Light-source glow","gs.glow_group":"Light-source glow","gs.glow_base":"House darkness","gs.glow_light":"Default light color / intensity","gs.wall_group":"Walls","gs.wall_fill":"Wall fill","gs.glow_radius":"Glow radius","gs.unit_m":"m","gs.unit_ft":"ft","marker.controls_label":"Controls other light sources","marker.controls_hint":"With tap action “Toggle”, a click flips every source added here. The marker’s own bound entity is controlled directly and cannot be added; use the switch below to classify it as a light.","marker.controls_filter":"Search lights and switches…","info.controls":"Controls","marker.glow_radius_label":"Glow radius","marker.glow_radius.help":"Sets the glow radius in metres or feet; an empty value uses the radius from general settings.","marker.glow_radius.help.aria":"Help: glow radius","markup.wallthick":"Thickness","markup.select":"Select","title.markup_wallthick":"Thickness — click a wall to set its thickness from 0 to 100 cm.","markup.hint_wallthick":"click a wall · Esc closes without applying","wallthick.field":"Thickness","wallthick.unit_cm":"cm","wallthick.unit_in":"in","wallthick.apply_room":"Apply to all walls of this room","markup.draw_wall_title":"Thickness of each new wall segment (0–100 cm). Shared walls keep the existing value.","room.queue_progress":"Room {current} of {total}","toast.wall_rooms_saved":"Created rooms: {n}","toast.wall_chain_saved":"Wall chain saved","toast.wallthick_pick":"Click a wall","toast.wallthick_set":"Wall thickness set","toast.wallthick_cleared":"Wall thickness removed","toast.physical_range":"Enter a value from {min} to {max} {unit}","toast.zero_wall_opening_conflict":"Remove the opening on this wall segment first.","toast.zero_wall_ambiguous":"The wall segment is ambiguous. Simplify or adjust the junction.","toast.zero_wall_migration_blocked":"The space was not converted: {reason}. No data was changed.","toast.physical_angle":"Enter a rotation angle from 0° up to, but not including, 90°","toast.physical_limit":"The space has reached the limit for this type of geometry","toast.geometry_unsafe":"Change canceled: wall geometry could not be built safely.","junction.limit_angle":"The angle between walls is too sharp: {actual}°, minimum {limit}°.","junction.limit_valence":"Too many walls meet in one node: {actual}, maximum {limit}.","junction.limit_length":"The wall segment is too short: {actual} cm, minimum {limit} cm.","junction.limit_distance":"Walls and nodes are too close: {actual} cm, minimum {limit} cm.","junction.limit_clearance":"No room interior is left: {actual} cm², minimum {limit} cm².","junction.limit_check_failed":"The junction check could not run — the change was not saved","toast.wall_model_migration_blocked":"The wall model could not be updated: {reason}. The plan was not changed. Run “Optimize plans”; if the error repeats, fix the conflicting wall geometry.","toast.wall_model_client_outdated":"Update the card and reload the page before editing the plan.","wall_model.reason.invalid-room":"invalid room contour","wall_model.reason.zero-length":"zero-length wall segment","wall_model.reason.third-owner":"wall shared by more than two rooms","wall_model.reason.duplicate-id":"conflicting wall identifiers","wall_model.reason.thickness-conflict":"conflicting wall thickness values","wall_model.reason.opening-host":"opening cannot be assigned to one wall","toast.delete_room_pick":"Click inside the room you want to delete","toast.plan_snap_ambiguous":"Zoom in to choose the wall node","toast.wall_repair_ambiguous":"The room outline has more than one possible connection. Zoom in and connect it explicitly.","toast.wall_repair_too_large":"The gap is larger than 2 cm. Connect the walls before creating the room.","toast.wall_repair_changed":"The wall geometry changed. Try creating the room again.","toast.opening_on_zero_wall":"Openings cannot sit on a zero-thickness wall","marker.from_ha_option":"Pick from the HA list","marker.show_entities":"Show entities","marker.show_entities_tip":"Adds not only devices to the list, but all their entities too","marker.pick_ph":"Choose a device…","room.open_area":"Open the HA area","view.volumetric":"Volumetric view","view.flat":"Flat view","kiosk.title":"This screen's sizes","kiosk.hint":"Stored on this device only — every wall tablet or TV can have its own comfortable sizes.","kiosk.icon_scale":"Device icon size","kiosk.font_scale":"Room card text size","editor.kiosk":"Wall device (kiosk) mode","editor.cycle":"Auto-switch spaces every N seconds (kiosk, 0 = off)","room.settings_title":"Room settings","room.settings_section":"Room settings (override the space)","room.fill_label":"Fill in THIS room","fill.inherit":"As the space","room.custom_fill_space":"Space color","room.custom_fill_own":"Room color","room.temp_src_label":"Temperature source","room.hum_src_label":"Humidity source","room.src_average":"Average over the room's sensors (default)","room.src_pick":"A specific HA device or entity","room.src_ph":"Choose a source…","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","toast.cfg_reload_failed":"Could not reload the plan from the server: {err}","room.settings_short":"Room settings","room.unnamed":"Unnamed room","marker.use_climate_temp":"Include the device temperature in the room","marker.use_climate_temp_tip":"Adds an air conditioner or thermostat current_temperature to the room average. Configure the external value badge separately below.","marker.value_badge_title":"Value badge","marker.value_badge.help":"Shows one selected value next to the icon. It does not affect room metrics, light, Glow or the tap action.","marker.value_badge.help.aria":"Help: device value badge","marker.value_badge_enabled":"Show a value badge","marker.value_badge_source":"Value","marker.value_badge_source.help":"Choose a specific state, supported attribute, or derived value of this device.","marker.value_badge_source.help.aria":"Help: value badge source","marker.value_badge_position":"Position","marker.value_badge_position.help":"The chosen side stays fixed and does not flip automatically at a plan edge.","marker.value_badge_position.help.aria":"Help: value badge position","marker.value_badge_right":"Right","marker.value_badge_bottom":"Below","marker.value_badge_left":"Left","marker.value_badge_top":"Above","marker.value_badge_empty":"This device has no available values","marker.value_badge_static":"A static icon does not show live values. The setting is preserved.","marker.value_badge_missing":"Source unavailable","marker.value_badge_missing_hint":"The saved source is currently unavailable. Keep it, disable the badge, or choose another value.","marker.value_badge_duplicate":"The same value is already shown inside the icon.","marker.value_badge_state":"State · {name}","marker.value_badge_marker_state":"Light-source state · {name}","marker.value_badge_lqi":"Average Zigbee signal quality","marker.value_badge_attr_current_temperature":"Current temperature · {name}","marker.value_badge_attr_temperature":"Target temperature · {name}","marker.value_badge_attr_current_humidity":"Current humidity · {name}","marker.value_badge_attr_humidity":"Humidity · {name}","marker.value_badge_attr_current_position":"Position · {name}","marker.value_badge_attr_percentage":"Speed · {name}","marker.value_badge_attr_brightness":"Brightness · {name}","marker.value_badge_attr_volume_level":"Volume · {name}","marker.value_badge_attr_battery_level":"Battery · {name}","marker.value_badge_attr_fan_speed":"Fan speed · {name}","marker.light_role_label":"Is this device a light source?","marker.light_role.help":"Controls whether this marker itself is a spatial light source. Linked lights above remain independent room-light controls.","marker.light_role.help.aria":"Help: whether this device is a light source","marker.light_role_auto_yes":"Auto (light source)","marker.light_role_auto_no":"Auto (not a light source)","marker.light_role_always":"Always a light source","marker.light_role_never":"Never a light source","marker.light_entity_label":"Leading light entity","marker.light_entity.help":"For a composite device, choose the entity whose state, colour and brightness represent this plan source. The automatic fallback remains compatible with older plans.","marker.light_entity.help.aria":"Help: leading light entity","marker.light_entity_auto":"Automatic ({entity})","marker.light_entity_none":"no controllable entity","marker.light_entity_missing":"The saved entity {entity} is unavailable. House Plan temporarily uses {fallback}; the saved choice will be restored if it returns.","marker.toggle_entity_label":"Entity to toggle","marker.toggle_entity.help":"For a composite device, choose the exact own light or switch operated by a tap. Automatic keeps the previous target rules and is independent from the leading light entity.","marker.toggle_entity.help.aria":"Help: entity to toggle","marker.toggle_entity_auto":"Automatic ({entity})","marker.toggle_entity_none":"no own controllable entity","marker.toggle_entity_missing":"The saved entity {entity} is no longer among this marker’s selectable channels. House Plan temporarily uses {fallback}; the saved choice will be restored if it returns.","marker.glow_color_label":"Glow colour and brightness","marker.glow_mode.help":"Use live source values, override only its colour, or fix both colour and brightness. The minimum is 1%; to disable the source, choose “Never”.","marker.glow_mode.help.aria":"Help: glow colour and brightness","marker.glow_mode_auto":"From source","marker.glow_mode_color":"Set colour","marker.glow_mode_fixed":"Set colour and brightness","marker.glow_color":"Glow colour","marker.glow_brightness":"Brightness","marker.glow_disabled_never":"Glow settings are unavailable because this marker is explicitly not a light source.","marker.glow_disabled_auto":"Auto mode found no spatial light source for this marker.","marker.glow_disabled_no_entity":"No active controllable Home Assistant entity is available for this spatial source.","marker.glow_passive_hint":"This source has no own Home Assistant data. Set its colour and brightness manually; its radius remains available.","marker.control_broken":"Saved source is missing or no longer marked as a light","marker.control_missing_label":"Missing plan light","marker.control_passive":"passive source","toast.marker_control_cycle":"This link would create a circular chain of light controls.","toast.marker_binding_required":"Choose a Home Assistant device before linking another light source.","confirm.unlock":"Unlock “{name}”?","toast.files_migrate_failed":"Attachments could not be moved to the new binding, links keep pointing at the old files: {err}","space.pick_saved":"Already uploaded","space.pick_saved_hint":"Plans stored on the server, including ones you detached earlier","space.no_saved":"No plans stored on the server yet.","space.loading":"Loading…","space.used_by":"in use: {list}","space.in_use":"A space still uses this plan — detach it first","btn.use":"Use","confirm.delete_plan":'Delete the plan file "{name}" from the server? This cannot be undone.',"toast.plans_list_failed":"Could not list the stored plans: {err}","toast.plan_delete_failed":"Could not delete the plan: {err}","marker.hide":"Hide","marker.hide_tip":'The device will disappear from the plan after saving but will still count toward the room signal. Restore it through the "Devices" catalog in the device editor.',"marker.show":"Show","marker.show_tip":"The device will appear on the plan again after saving.","marker.hidden_ghost":"Device hidden by the user","marker.ha_disabled_device":"The device is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_entity":"The entity is disabled in Home Assistant and hidden from the plan.","marker.ha_disabled_all_entities":"The device has no active Home Assistant entities, so it is hidden from the plan.","marker.ha_registry_limited":"The full Home Assistant registry is unavailable to this user. The unverified object cannot be shown or used for now.","marker.delete_tip":"Completely delete the device from the plan and every aggregate. You can add it again later.","tap.run":"Run automation/script/scene","tap.cover":"Open/close (curtains/blinds)","marker.run_target_label":"What to run","marker.run_search_ph":"Search: automation, script or scene…","marker.run_target_gone":"Target {id} not found — pick again","marker.tap_confirm":"Ask for confirmation","marker.tap_confirm_tip":"Show a confirmation dialog before acting — a guard against accidental taps.","marker.toggle_hint_single":"Target: {name} ({id}).","marker.virtual_light_target":"Virtual light: {name}.","marker.virtual_light_current":"Manual state: {state} → {effect}.","marker.virtual_light_state_on":"on","marker.virtual_light_state_off":"off","marker.toggle_hint_group":"Will toggle {count} source(s): {names}.","marker.toggle_hint_current":"Now: {state} → {effect}.","marker.toggle_hint_group_current":"Currently on: {on} of {count} → {effect}.","marker.toggle_hint_skipped":"Will be skipped ({count}): {targets}.","marker.toggle_effect_turn_on":"will turn on","marker.toggle_effect_turn_off":"will turn off","marker.toggle_effect_open":"will open","marker.toggle_effect_close":"will close","marker.toggle_effect_stop":"will stop","marker.toggle_effect_toggle":"state will toggle","marker.toggle_skip_missing":"not found","marker.toggle_skip_ha_disabled":"disabled in HA","marker.toggle_skip_unavailable":"unavailable","marker.toggle_skip_unsupported":"toggle is unsupported","marker.toggle_skip_secure":"blocked for security","marker.toggle_none_no_binding":"This device has no binding, so there is no state to toggle. Tapping will do nothing.","marker.toggle_none_no_actionable_entity":"This device has no state that can be toggled. Tapping will do nothing.","marker.toggle_none_configured_targets_missing":"The configured targets are unavailable. The device's own entity will not be substituted for them.","marker.toggle_none_ha_disabled":"The target is disabled in Home Assistant and cannot be used on the plan.","marker.toggle_none_unavailable":"The target is currently unavailable. Tapping will do nothing.","marker.toggle_none_unsupported":"Home Assistant exposes no safe toggle operation for this entity. Tapping will do nothing.","marker.toggle_none_secure":"Toggling locks, alarms and secure gates from the plan is blocked for security.","run.automation":"automation","run.script":"script","run.scene":"scene","confirm.tap_run":'Run "{name}"?',"confirm.tap_toggle":'Toggle "{name}"?',"confirm.tap_cover":'Open/close "{name}"?',"confirm.current_state":"Current state: {state}","confirm.expected_state":"After switching: {state}","confirm.group_current":"on {on} of {total}","confirm.group_all_on":"all are on","confirm.group_all_off":"all are off","confirm.unavailable_targets":"Unavailable: {count}","confirm.expected_by_ha":"Home Assistant will determine the state","confirm.state_on":"On","confirm.state_off":"Off","confirm.state_open":"Open","confirm.state_closed":"Closed","confirm.state_opening":"Opening","confirm.state_closing":"Closing","confirm.state_stopped":"Stopped","confirm.state_unknown":"Unknown","toast.run_started":"Started: {name}","toast.run_target_missing":"Run target not found — check the device settings","toast.run_target_required":"Pick an automation, script or scene","toast.tap_target_changed":"The action target changed. Try again.","toast.virtual_light_toggle_failed":"Could not toggle the virtual light: {err}","toast.value_badge_source_required":"Choose a value for the badge","btn.run":"Run","vac.section":"Robot vacuum: live position","vac.autocal":"Set up automatically","vac.live":"Live position on the plan","vac.trail":"Show the robot's path","vac.cal_maps":"Calibrated maps: {maps}","vac.autocal_no_rooms":"The integration reports no room list — open “Fit manually”","vac.autocal_no_match":"Room names did not match (need ≥3 in common) — open “Fit manually”","vac.autocal_done":"Done: bound via {rooms} rooms. Start a cleanup and check","vac.cal_need_pos":"The robot is not reporting coordinates — start a cleanup and pause it","vac.cal_done":"Calibration saved. Start a cleanup and check","vac.cal_cancelled":"Calibration cancelled","vac.fit":"Fit manually","vac.fit_hint":"Drag the robot map into place, stretch by the corners","vac.fit_rotate":"Rotate 90°","vac.fit_mirror":"Mirror","vac.trail_never":"Never","vac.trail_cleaning":"While cleaning","vac.trail_always":"Always","gs.bg_group":"Stage background","gs.bg_color":"Background around the plan","gs.bg_default":"Theme default","gs.bg_theme":"theme default","gs.bg_mode":"Plan background","gs.bg_static":"Static color","gs.bg_daynight":"Follows the Sun","gs.bg_daynight_hint":"The background moves through dawn, day, dusk and night using sun.sun, or your local clock when sun data is unavailable. No compass is needed for the background.","gs.sun_group":"Sun","gs.sun_missing":"The sun.sun entity was not found — the background follows your local clock; window rays are unavailable.","gs.north":"North on the plan","gs.north_ph":"not set","gs.north_hint":"Set north only for window rays (1° steps, 15° with Shift); the Sun background works without it.","gs.north_clear":"Clear","gs.north_letter":"N","gs.sun_rays":"Sunlight through windows","gs.about_group":"About","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · docs & issues","gs.about_telegram":"Telegram chat","space.bg_color":"Background around the plan","space.bg_inherit":"Inherit general","space.bg_inherited":"inherits general settings","space.bg_mode":"Plan background","space.north":"North on the plan (override)","space.north_inherited":"inherited: {v}","space.sun_rays":"Sunlight through windows","space.sun_inherit":"Inherit general","space.sun_on":"On","space.sun_off":"Off","canvas.far_objects":"{n} object(s) far from the plan","canvas.show_far":"Show","canvas.home_tip":"The plan is over there — click to fit it","gs.grid_group":"Plan maintenance","gs.grid_hint":"Updates data models, aligns plan elements to the grid and merges redundant wall fragments. An exact report is shown before anything is stored.","gs.align_all":"Optimize plans","gs.align_title":"Optimize plans","gs.align_none":"All plans already use the current optimized data model.","gs.optimize_no_automatic_changes":"There are no automatic changes to apply. Review the items below.","gs.align_count":"{n} of {total} elements will move, by at most {cm} cm.","gs.align_where":"The largest shift is in “{s}”.","gs.align_turned":"Openings whose angle is corrected: {n}.","gs.align_removed_drafts":"Invalid outlines collapsed by the grid and removed: {n}.","gs.optimize_redundant_drafts":"Saved wall chains hidden by solid room walls and removed: {n}.","gs.align_preflight_failed":"Could not safely verify the geometry of the following spaces: {spaces}{more}.","gs.align_preflight_hint":"Plans were not changed. Copy the diagnostics with the button below and attach them to the bug report together with a space export.","gs.preflight_reason_prepare-exception":"Could not prepare the space geometry (exception while building the model)","gs.preflight_reason_wall-null":"The wall body did not build (the union came back empty)","gs.preflight_reason_wall-degraded-extra":"The wall body degraded with extra geometry","gs.preflight_reason_wall-failed-core":"The wall body core failed to assemble","gs.preflight_reason_wall-exception":"Wall construction threw an exception","gs.preflight_reason_floor-null":"The floor outline did not build","gs.preflight_reason_floor-exception":"Floor construction threw an exception","gs.preflight_copy":"Copy diagnostics","gs.preflight_copied":"Diagnostics copied","gs.preflight_update_hint":"The card and integration versions differ — update House Plan and retry.","gs.align_preflight_space":"Space {n}","gs.align_preflight_more":", and {n} more","gs.optimize_changes":"Model migrations: {m}; spaces updated: {c}; noisy coordinate values removed: {p}; merged real-wall fragments: {w}; merged zero-thickness wall fragments: {s}; independent walls: {i}.","gs.zero_walls_migrated":"Virtual wall spans converted: {n}.","gs.wall_segments_migrated":"Wall segments stabilised: {n}.","gs.optimize_lattice_summary":"Noisy coordinate values canonicalized: {n}; maximum movement: {cm} cm.","gs.optimize_lattice_space":"{space}: coordinate values canonicalized: {n}; off-grid values left unchanged: {far}.","gs.optimize_coincident_partitions":"Hidden independent wall sections absorbed into room walls: {n}.","gs.optimize_openings_rehosted":"Openings reattached to room walls: {n}.","gs.optimize_walls_straightened":"Walls straightened: {n}; maximum movement: {cm} cm.","gs.optimize_walls_straightened_where":"Largest wall correction: {s}.","gs.optimize_walls_straighten_skipped":"Near-axis walls left unchanged because they could not be repaired safely: {n}.","gs.optimize_glow_migration":"Legacy Glow: {spaces} spaces → no data fill + independent Glow; {rooms} rooms → inherited data fill + independent Glow.","gs.optimize_references":"References repaired: spaces — {spaces}; rooms — {rooms}; positions — {positions}; devices detached from missing spaces — {detached}.","gs.optimize_reference_more":", and {n} more","gs.optimize_orphans_removed":"Forgotten records removed: {total} — room labels: {rooms}; devices: {devices}; group markers: {groups}. They belonged to spaces deleted earlier.","gs.optimize_live_positions":"Old positions in deleted spaces belong to existing objects: {n}{names}. They will be kept.","gs.optimize_live_positions_remove":"Old positions in deleted spaces belong to existing objects: {n}{names}. They are selected for removal.","gs.optimize_live_names":": {names}{more}","gs.optimize_live_remove":"Remove old positions","gs.optimize_live_keep":"Keep old positions","gs.optimize_live_selected":"Old positions will be removed after Optimize is applied.","gs.optimize_unverified":"Could not safely verify positions: {n}. They were left unchanged.","gs.optimize_registry_limited":"Full administrator access to the Home Assistant registries is required for a safe check.","gs.optimize_vacuum_warning":"Vacuum room mappings that still need review: {n}.","gs.optimize_details":"Details","gs.optimize_details_more":"And {n} more records.","gs.optimize_detail_removed":"will be removed","gs.optimize_detail_live":"will be kept","gs.optimize_detail_unverified":"not verified","gs.optimize_detail_room_label":"room label","gs.optimize_detail_device":"device","gs.optimize_detail_group":"group marker","gs.optimize_detail_unknown":"unknown owner","gs.optimize_detail_item":"{status}: {kind} {id}; old space {space}","gs.align_warn":"Elements deliberately placed between grid nodes will move. One undo is available after the operation, only until the next plan edit.","gs.align_run":"Optimize","gs.align_done":"Plans optimized: {n} elements moved, {m} records maintained, {r} references repaired","gs.optimize_undo":"Undo last optimization","gs.optimize_undone":"The last optimization was undone","decor.furniture":"Furniture","furn.title":"Furniture library","furn.symbol":"Symbol","furn.group_furniture":"Furniture","furn.group_appliance":"Appliances","furn.group_sanitary":"Plumbing","furn.group_other":"Other","furn.width":"Width","furn.depth":"Depth","furn.pick_hint":"Pick a symbol, then click on the plan.","furn.place_hint":"Click on the plan — the piece lands against the nearest wall. Shift places it free.","furn.sym_sofa":"Sofa","furn.sym_armchair":"Armchair","furn.sym_coffee_table":"Coffee table","furn.sym_table_dining":"Dining table","furn.sym_table_round":"Round table","furn.sym_chair":"Chair","furn.sym_desk":"Desk","furn.sym_bed_double":"Double bed","furn.sym_bed_single":"Single bed","furn.sym_nightstand":"Nightstand","furn.sym_wardrobe":"Wardrobe","furn.sym_bookshelf":"Bookshelf","furn.sym_fridge":"Fridge","furn.sym_stove":"Cooker","furn.sym_dishwasher":"Dishwasher","furn.sym_washer":"Washing machine","furn.sym_dryer":"Tumble dryer","furn.sym_tv":"TV","furn.sym_ac":"Air conditioner","furn.sym_water_heater":"Water heater","furn.sym_toilet":"Toilet","furn.sym_bathtub":"Bathtub","furn.sym_shower":"Shower","furn.sym_sink":"Washbasin","furn.sym_kitchen_sink":"Kitchen sink","furn.sym_bidet":"Bidet","furn.sym_stairs":"Stairs","furn.sym_fireplace":"Fireplace","furn.sym_plant":"Plant","furn.sym_rug":"Rug","common.yes":"Yes","common.no":"No","vac.diag_source":"Source","vac.diag_platform":"Integration","vac.diag_status":"Status","vac.diag_position":"Position","vac.diag_rooms":"Rooms","vac.diag_rooms_value":"{total} · {matched} names match · {readiness}","vac.autocal_ready":"auto-calibration available","vac.autocal_not_ready":"need 3 matching names","vac.diag_path":"Integration path","vac.diag_map":"Map ID","vac.source_none":"not selected","vac.source_status_ok":"Ready","vac.source_status_missing":"Missing","vac.source_status_disabled":"Disabled in Home Assistant","vac.source_status_unavailable":"Unavailable","vac.source_status_unverified":"Cannot verify with current permissions","vac.source_status_unsupported":"No position data","vac.source_status_none":"No source","vac.source_banner_missing":"The saved source no longer exists. It was not replaced automatically; choose another source or restore it in Home Assistant.","vac.source_banner_disabled":"The saved source is disabled in Home Assistant. Enable it there or choose another source.","vac.source_banner_unverified":"Current Home Assistant permissions cannot verify this saved source. It remains pinned and will not be replaced automatically.","vac.choose_source":"Choose source","vac.source_auto":"Automatic","vac.source_auto_hint":"Use a compatible entity from this device","vac.all_cameras":"All cameras","vac.all_cameras_warn":"A camera may not provide robot data. Choose one only if it is your vacuum map.","vac.all_cameras_empty":"No other camera entities found.","vac.platform_unknown":"unknown integration","vac.cap_position":"position","vac.cap_rooms_short":"rooms","vac.cap_path":"path","vac.cap_map":"map ID","vac.cap_none":"no robot data detected","vac.xcme_hint":"Enable these Xiaomi Cloud Map Extractor attributes:","vac.documentation":"Documentation","vac.residual_title":"Check automatic calibration","vac.residual_message":"The matched rooms disagree by up to {error}. Apply this approximate calibration, refine it manually, or cancel without changing the saved setup.","vac.apply_proposal":"Apply","gs.backup_group":"Backup and transfer","gs.backup_hint":"Download a portable JSON backup or preview and import a backup made by House Plan.","backup.export_open":"Export","backup.import_open":"Import","backup.export_title":"Export House Plan","backup.import_title":"Import House Plan","backup.export_hint":"Choose whether the backup should contain the whole House Plan configuration or only the current space.","backup.full":"Full backup","backup.current_space":"Current space","backup.current_space_title":"Current space: {title}","backup.no_current_space":"No current space","backup.plan_only":"Plan only","backup.plan_only_hint":"Keep rooms, walls, openings, decor and room-label positions without devices or Home Assistant bindings.","backup.plan_only_preview":"This file contains the plan only","backup.privacy_warning":"The archive keeps names, Home Assistant identifiers and exact coordinates. Internal plans and attachments are referenced, not embedded; runtime states and vacuum trails are not included.","backup.download":"Download JSON","backup.export_done":"Backup downloaded","backup.reading":"Checking the backup…","backup.revalidated":"The plan changed after this preview. The summary was refreshed; review it and confirm again.","backup.error.unauthorized":"You do not have permission to export or import this plan.","backup.error.not_ready":"House Plan is not ready yet. Try again in a moment.","backup.error.too_large":"The backup exceeds the 8 MiB limit.","backup.error.invalid_json":"The selected file is not valid JSON.","backup.error.invalid_format":"The selected file is not a House Plan backup.","backup.error.unsupported_export_version":"This backup format is not supported by the installed version.","backup.error.future_model":"The backup was created by a newer House Plan data model.","backup.error.invalid_config":"The backup contains an invalid House Plan configuration.","backup.error.wall_model_migration_blocked":"The backup wall model could not be upgraded safely. Optimize the source plan first, then export it again.","backup.error.invalid_layout":"The backup contains invalid object positions.","backup.error.invalid_content":"The backup contains invalid or inconsistent content references.","backup.error.space_not_found":"The selected space no longer exists.","backup.error.capacity_exceeded":"Adding this backup would exceed the plan limits.","backup.error.preview_expired":"The preview expired. Select the backup file again.","backup.error.preview_owner_mismatch":"This preview belongs to another Home Assistant user.","backup.error.conflict":"The plan changed after the preview. Review the refreshed summary.","backup.error.content_confirmation_required":"Confirm that unavailable local content may be detached.","backup.error.commit_failed":"The backup could not be applied safely. The previous plan was restored or is pending recovery.","backup.error.missing_plan":"A referenced plan file disappeared after the preview. Review the backup again.","backup.error.missing_content":"A referenced local attachment disappeared after the preview. Review the backup again.","backup.error.marker_control_missing":"A linked plan light is missing from the backup.","backup.error.marker_control_not_light":"A linked plan target is no longer marked as a light source.","backup.error.marker_control_self":"A light source cannot control itself.","backup.error.marker_control_cycle":"The backup contains a circular chain of light controls.","backup.error.duplicate_marker_control":"The backup contains a duplicate plan-light link.","backup.error.no_backup":"There is no import or optimization snapshot to restore.","backup.same_source":"Created on this Home Assistant instance","backup.foreign_source":"Created on another Home Assistant instance","backup.created":"Created: {value}","backup.versions":"Card {card}; integration {integration}; data model {model}","backup.count_spaces":"Spaces: {n}","backup.count_rooms":"Rooms: {n}","backup.count_walls":"Walls: {n}","backup.count_openings":"Openings: {n}","backup.count_decor":"Decor objects: {n}","backup.count_markers":"Devices: {n}","backup.count_layout":"Positions: {n}","backup.bindings":"Bindings — devices: {device}, entities: {entity}, virtual: {virtual}; positions without a space: {legacy}","backup.binding_status":"Target status — active: {active}, disabled: {disabled}, missing: {missing}","backup.missing_areas":"Areas missing on the target: {areas}","backup.dropped_marker_links":"Plan-light links outside this transferred space were omitted: {n}.","backup.repaired_target_refs":"Existing references restored by this space import: {n}.","backup.preserved_unresolved_refs":"References that could not be restored unambiguously were preserved: {n}.","backup.preserved_unresolved_hint":"No data was guessed or deleted. After the import, run Optimize plans to inspect the remaining references.","backup.import_details":"Import reference details","backup.import_detail.incoming_remapped":"References updated inside the imported copy: {n}","backup.import_detail.target_repaired":"Existing references restored: {n}","backup.import_detail.preserved_unresolved":"Unresolved references preserved: {n}","backup.import_detail.collisions":"Destination conflicts preserved safely: {n}","backup.import_detail.dropped_links":"Incoming links omitted by transfer rules: {n}","backup.import_detail.bounded_lineages":"Overly nested identifiers left bounded: {n}","backup.replace_warning":"This replaces the current configuration and layout. Uploaded files are never deleted. One undo remains available until the next plan edit.","backup.foreign_bookkeeping":"Instance-specific known/new-device bookkeeping will not be imported.","backup.final_name":"New space name","backup.target_settings":"The target instance's global settings remain unchanged.","backup.duplicates":"Bindings already present: {n}","backup.skip":"Skip duplicate devices","backup.virtual_copy":"Add safe static virtual copies","backup.content":"Referenced content","backup.content_available":"available locally","backup.content_external":"external link","backup.content_detach_required":"will be detached","backup.confirm_detach":"I understand that unavailable internal plans and attachments will be detached from the imported configuration.","backup.replace":"Replace","backup.add":"Add space","backup.space_done":"Space imported: {rooms} rooms, {markers} devices, {refs} existing references restored","backup.full_done":"Backup restored: {spaces} spaces, {rooms} rooms, {markers} devices","backup.undo_import":"Undo last full import","backup.import_undone":"Full import undone"};const aa=[{code:"en",nativeLabel:"English",dictionary:oa},{code:"ru",nativeLabel:"Русский",dictionary:{"editor.context_actions":"Действия: {object}","editor.tool_options":"Параметры инструмента: {tool}","editor.palette":"Палитра: {tool}","editor.open_group":"Группа инструментов: {group}","editor.group_active":"{group} — активно: {item}","editor.disabled_action":"{action} недоступно: {reason}","color_picker.title":"Выбор цвета","color_picker.hue":"Оттенок","color_picker.saturation":"Насыщенность","color_picker.value":"Яркость","color_picker.hex":"Цвет HEX","color_picker.invalid_hex":"Введите цвет HEX из 3 или 6 цифр","btn.properties":"Свойства","btn.keep_as_walls":"Оставить стенами","btn.delete_room_keep_walls":"Удалить комнату, оставить стены","btn.delete_room_with_walls":"Удалить комнату и стены","title.markup_select":"Выбор и редактирование стен, колонн и сохранённых контуров","title.markup_column":"Колонна: кликните точку сетки для квадратной колонны","markup.partition":"Перегородка","markup.column":"Колонна","markup.hint_column":"кликните точку сетки, чтобы поставить квадратную колонну","history.draft_segment":"Добавление сегмента черновика комнаты","history.draft_merge":"Соединение незавершённых контуров комнаты","history.draft_segment_delete":"Удаление сегмента черновика комнаты","history.partition_add":"Добавление перегородки","history.column_add":"Добавление колонны","history.physical_edit":"Изменение физического объекта","history.physical_delete":"Удаление физического объекта","history.physical_move":"Перемещение физического объекта","history.contour_to_partitions":"Преобразование контура в замкнутые стены","history.wall_chain_finish":"Завершение цепочки стен","history.wall_face_batch":"Создание комнат из стен","toast.column_duplicate":"Колонна с тем же центром и внешним размером уже существует","confirm.delete_draft":"Удалить весь незавершённый контур комнаты?","confirm.delete_draft_segment":"Удалить этот сегмент черновика? Оставшийся контур может разделиться на два.","physical.partition_properties":"Свойства перегородки","physical.column_properties":"Свойства колонны","physical.draft_properties":"Свойства сегмента черновика","physical.shape":"Форма","physical.square":"Квадрат","physical.circle":"Круг","physical.diameter":"Диаметр","physical.side":"Сторона","physical.rotation":"Угол поворота","physical.length":"Длина","physical.allowed_range":"Допустимо: {min}–{max} {unit}","physical.delete_segment":"Удалить сегмент","physical.delete_draft":"Удалить весь контур","physical.column_size_title":"Внешняя сторона квадратной колонны, создаваемой кликом (1–150 см).","card.title":"План дома","count.devices":"{n} устр.","empty.no_spaces":"Пространств пока нет.","empty.add_first":"Добавьте первое пространство и загрузите план этажа.","empty.install":"Установите интеграцию House Plan и добавьте запись в «Устройства и службы».","btn.add_space":"Добавить пространство","btn.cancel":"Отмена","btn.save":"Сохранить","btn.close":"Закрыть","btn.delete":"Удалить","btn.edit":"Редактировать","btn.open_in_ha":"Открыть в HA","btn.reset":"Сброс","btn.attach":"Прикрепить…","btn.upload":"Загрузить…","btn.replace":"Заменить…","title.zoom_in":"Приблизить","title.zoom_out":"Отдалить","title.zoom_fit":"Вписать всё","title.add_device":"Добавить устройство на план","title.show_all":"Показать скрытые и деактивированные устройства служебными маркерами (только в этой вкладке)","title.markup":"Разметка комнат: сетка, линии, контуры","title.configure_space":"Настроить пространство","title.add_space":"Добавить пространство","title.markup_add":"Стены: рисуйте непрерывную цепочку; Shift фиксирует 45°. Смена инструмента или выход завершает её как независимые стены","title.markup_merge":"Объединить: клик по одной комнате, затем по соседней с общей стеной","title.markup_split":"Разделить комнату: клик по комнате, затем две точки на её стенах","title.markup_delroom":"Удалить комнату: кликните внутри комнаты и подтвердите удаление","title.need_plan":"Загрузите подложку (план этажа)","markup.add":"Стены","markup.merge":"Объединить","markup.split":"Разделить","markup.resize":"Размер","title.markup_resize":"Изменение размера комнат: тяните доступную ручку горизонтальной или вертикальной стены","markup.hint_resize":"тяните доступную ручку стены · стена упирается в первую небезопасную позицию · Esc — отмена · Ctrl+Z — отмена шага","resize.disabled.diagonal":"Стена расположена под углом: Resize поддерживает только горизонтальные и вертикальные стены","resize.disabled.side-angle":"Примыкающая стена расположена под углом: для Resize обе примыкающие стены должны образовывать прямой угол","resize.disabled.duplicate-physical-wall":"Стену перекрывает отдельная перегородка, незавершённый контур или колонна; удалите или переместите её перед изменением размера","resize.disabled.partial-shared":"Соседняя комната использует только часть этой стены, поэтому её нельзя безопасно двигать как одну общую стену","resize.disabled.unequal-shared":"У соседней комнаты другие конечные точки или длина стены, поэтому стены нельзя безопасно двигать вместе","resize.disabled.multiple-rooms":"Перемещение затронуло бы больше двух комнат","resize.disabled.thickness-conflict":"Толщину стены нельзя безопасно сохранить","resize.disabled.opening-conflict":"Проём на этой стене нельзя безопасно переместить","resize.disabled.invalid-geometry":"Эту стену нельзя переместить без изменения структуры плана","resize.limit_stopped":"Стена остановлена: дальше нарушается ограничение стыков","resize.commit_failed":"Изменение размера отменено: итоговый план не прошёл проверку геометрии","resize.preview_failed":"Стена остановлена в последней безопасной позиции: геометрию плана нельзя сохранить без потерь","markup.opening":"Проём","markup.delete_room":"Удалить комнату","history.undo":"Отменить","history.redo":"Повторить","history.undo_named":"Отменить: {name} (Ctrl+Z)","history.redo_named":"Повторить: {name} (Ctrl+Shift+Z / Ctrl+Y)","history.undo_empty":"Нет операций для отмены","history.redo_empty":"Нет операций для повтора","history.undone":"Отменено: {name}","history.redone":"Повторено: {name}","history.add_room":"Создание комнаты","history.split_room":"Разделение комнаты","history.merge_rooms":"Объединение комнат","history.resize_room":"Изменение размера комнаты","history.wall_thickness":"Изменение толщины стены","history.add_opening":"Добавление дверного или оконного проёма","history.edit_opening":"Изменение проёма","history.move_opening":"Перемещение проёма","history.delete_opening":"Удаление проёма","history.delete_room":"Удаление комнаты","history.delete_room_keep_walls":"Удаление комнаты с сохранением стен","history.delete_room_with_walls":"Удаление комнаты и её стен","history.decor_add":"Добавление объекта декора","history.decor_edit":"Изменение объекта декора","history.decor_move":"Перемещение объекта декора","history.decor_transform":"Трансформация объекта декора","history.decor_delete":"Удаление объекта декора","history.backdrop_transform":"Трансформация подложки плана","title.markup_opening":"Проёмы: клик по стене — добавить, клик по проёму — редактировать","opening.new":"Новый проём","opening.edit":"Проём","opening.door":"Дверь","opening.window":"Окно","opening.gate":"Ворота","opening.passage":"Открытый проём","opening.passage_binding_warning":"При сохранении датчик открытия и замок будут удалены.","opening.invalid_passage_fields":"У открытого проёма на плане «{room}» есть недопустимые параметры: {fields}.","opening.type_label":"Тип","opening.length_label":"Длина, см","opening.contact_label":"Датчик открытия","opening.lock_label":"Замок","opening.none":"— нет —","opening.search_ph":"Поиск: имя или entity_id…","opening.invert":"Инвертировать открыто/закрыто","opening.flip_h":"Петли с другой стороны","opening.flip_v":"Открывается в другую сторону","opening.open":"Открыто","opening.closed":"Закрыто","opening.locked":"Заперто","opening.unlocked":"Не заперто","opening.state_unknown":"недоступно","opening.no_entities":"Датчики не привязаны — статичный символ на плане.","toast.opening_no_wall":"Кликните рядом со стеной комнаты или независимой стеной","opening.host_partition":"Независимая стена","opening.partition_orphan":"Независимая стена этого проёма больше не существует","opening.partition_jamb_margin":"Оставьте от края проёма до торца стены минимум {distance}","opening.rebind_partition":"Привязать к другой независимой стене","confirm.delete_partition_openings_title":"Удалить стену и проёмы?","confirm.delete_partition_openings_body":"В стене есть проёмы: {count}. Они будут удалены вместе со стеной.","confirm.delete_partition_openings_item":"• {type}, {length}","markup.delete":"Удалить","markup.hint_points":"точек: {n} · Shift — шаг 45° · Esc — завершить цепочку · Ctrl+Z — убрать точку · при замыкании будет предложена комната","markup.hint_start":"кликните точку сетки, чтобы начать цепочку стен","tip.lqi":"средний сигнал zigbee:","tip.area":"площадь: {value}","info.device_header":"Устройство на плане","info.model":"Модель","info.state":"Состояние","info.link":"Ссылка","info.manuals":"Инструкции","info.none":"Нет дополнительной информации","marker.new_device":"Новое устройство","marker.name_label":"Имя (отображается на плане)","marker.name_ph":"Название","marker.binding_label":"Привязка к устройству HA","marker.binding_disabled":"деактивировано в Home Assistant","marker.virtual_option":"Виртуальное устройство (без привязки)","marker.search_ph":"Поиск устройства / группы…","marker.nothing_found":"ничего не найдено","marker.room_label":"Комната","marker.room_override":" (переопределить размещение)","marker.room_choose":"— выберите комнату —","marker.room_auto":"— по зоне устройства (авто) —","marker.icon_label":"Иконка","marker.icon_ph":"mdi:… (пусто = авто)","marker.display_label":"Отображение","display.badge":"Значок + состояние","display.icon_ripple":"Значок + состояние и активность","display.static_icon":"Всегда статичный значок","marker.display_hint":"Значок + состояние меняет подложку при работе, открытии, тревоге и недоступности. Значок + состояние и активность дополнительно показывает короткую пульсацию для событий и постоянную — для работы, движения или присутствия. Значение + состояние заменяет значок одним однозначным значением HA. Статичный значок не реагирует на состояния устройства.","marker.display_hint_badge":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon":"Значок и динамическая подложка показывают состояние устройства без обычной пульсации активности. Красная тревога сохраняется.","marker.display_hint_icon_ripple":"Значок, динамическая подложка и пульсация: короткая — для событий, постоянная — для работы, движения или присутствия. Красная тревога имеет отдельный приоритет.","marker.display_hint_value":"Значок заменяется одним однозначным значением Home Assistant; подложка продолжает показывать состояние. Красная тревога сохраняется.","marker.display_hint_static_icon":"Подложка в цветах темы и значок всегда остаются одинаковыми. Состояния, активность, недоступность и тревоги не меняют отображение.","marker.static_alarm_warning":"Это устройство может сообщать о тревогах. Статичный режим скрывает визуальную тревогу маркера.","marker.preview.title":"Предпросмотр отображения","marker.preview.actual":"Сейчас","marker.preview.example":"Пример","marker.preview.integration":"Интеграция","marker.preview.source":"Источник отображения","marker.preview.current_state":"Текущее состояние","marker.preview.result":"На плане","marker.preview.details":"Подробнее об источниках","marker.preview.select_source":"Выберите устройство или сущность Home Assistant, чтобы увидеть фактическое отображение.","marker.preview.unknown_provider":"Интеграция неизвестна","marker.preview.virtual_provider":"House Plan · виртуальное устройство","marker.preview.no_source":"Нет активного источника","marker.preview.no_state":"Нет текущего состояния","marker.preview.mixed_states":"Несколько разных состояний","marker.preview.multiple_sources":"Источников: {n}","marker.preview.more_sources":"ещё {n}","marker.preview.scaled":"Предпросмотр уменьшен до {n}%, чтобы поместиться. Сохранённый размер не изменится.","marker.preview.demo_activity":"Показать пример активности","marker.preview.demo_notice":"Временный пример активности; фактическое состояние устройства не меняется.","marker.preview.demo_short":"Показать краткую активность","marker.preview.demo_continuous":"Показать постоянную активность","marker.preview.stop_continuous":"Остановить постоянную активность","marker.preview.demo_short_notice":"Пример краткой активности; фактическое состояние устройства не меняется.","marker.preview.demo_continuous_notice":"Пример постоянной активности; фактическое состояние устройства не меняется.","marker.preview.demo_already_visible":"Текущее состояние уже показывает реальную активность или тревогу.","marker.preview.reduced_motion":"В системе включено уменьшение движения, поэтому обычная активность показана точкой.","marker.preview.reason.neutral":"Нейтральная тёмная подложка","marker.preview.reason.working":"Жёлтая подложка: устройство сейчас работает","marker.preview.reason.working_activity":"Жёлтая подложка и эффект активности: устройство сейчас работает","marker.preview.reason.open":"Оранжевая подложка: физически открыто или разблокировано","marker.preview.reason.cover_icon_state":"Нейтральная подложка; состояние и значок определяет сущность шторы или заслонки","marker.preview.reason.presence":"Эффект активности, пока обнаружено присутствие","marker.preview.reason.event":"Короткий эффект активности после события","marker.preview.reason.transition":"Эффект активности во время движения или смены состояния","marker.preview.reason.media_neutral":"Медиаустройство остаётся на нейтральной тёмной подложке, пока доступно","marker.preview.reason.unavailable":"Приглушённая нейтральная подложка: отключено или недоступно","marker.preview.reason.alarm":"Красная тревожная подложка; тревога отображается всегда","marker.preview.reason.live_states_disabled":"Оформление по живым состояниям отключено в настройках карточки","marker.preview.reason.value_no_state":"Нет подходящего значения состояния — вместо него показан значок","marker.preview.reason.value_ambiguous_sources":"Подходит несколько источников значения — вместо него показан значок","marker.preview.reason.value_non_scalar":"Источник вернул не простое значение — вместо него показан значок","marker.preview.reason.value_virtual":"У виртуального устройства нет значения HA — вместо него показан значок","marker.preview.reason.vacuum_live_plan_only":"Живая позиция и след пылесоса доступны только на полном плане","marker.preview.reason.hidden_design_preview":"Устройство скрыто на плане, но его оформление видно в предпросмотре","marker.preview.reason.composite_power_source":"Состояние берётся из сущности питания устройства; вспомогательные переключатели не учитываются","marker.preview.reason.activity_display_disabled":"Выбранный режим отображения не показывает обычные эффекты активности","marker.preview.reason.ha_disabled":"Привязка отключена в Home Assistant, поэтому устройство будет скрыто на плане","marker.preview.reason.orphaned":"Сохранённая привязка Home Assistant больше не найдена","marker.preview.reason.static_icon":"Статичный режим: состояние устройства не меняет значок","marker.activity_color":"Цвет пульсации активности","marker.ripple_size":"Размер пульсации активности","marker.activity_alarm_note":"Цвет и размер относятся только к обычной активности и не влияют на красную тревогу.","marker.pulse_a11y_alarm":"Тревога","marker.pulse_a11y_event":"Недавнее событие","marker.pulse_a11y_presence":"Обнаружено присутствие","marker.pulse_a11y_transition":"Изменение состояния","marker.pulse_a11y_running":"Работает","marker.state_a11y_neutral":"Обычное состояние","marker.state_a11y_working":"Работает","marker.state_a11y_open":"Открыто","marker.state_a11y_locked":"Заблокировано","marker.state_a11y_unlocked":"Разблокировано","marker.state_a11y_alarm":"Тревога","marker.state_a11y_unavailable":"Недоступно","marker.lqi_a11y_low":"LQI {value}, слабый сигнал","marker.lqi_a11y_mid":"LQI {value}, средний сигнал","marker.lqi_a11y_high":"LQI {value}, сильный сигнал","marker.size_label":"Размер / поворот значка","marker.angle_label":"Поворот","marker.model_label":"Модель","marker.model_ph":"напр. Aqara T&H","marker.link_label":"Ссылка","marker.desc_label":"Описание","marker.desc_ph":"Заметки, характеристики…","marker.manuals_label":"Инструкции (PDF и т.п.)","marker.sub_device":"устройство","marker.sub_z2m_group":" · Z2M-группа","marker.sub_group":"группа","marker.sub_helper":"хелпер","space.new":"Новое пространство","space.header":"Пространство","space.title_label":"Название","space.title_ph":"Например: Гараж","space.plan_label":"Подложка (план)","space.no_plan":"нет подложки","space.plan_alt":"план","room.new":"Новая комната","room.name_label":"Отображаемое имя","room.name_ph":"Например: Терраса","room.area_label":"Зона Home Assistant (свободные)","room.no_area_option":"— без зоны —","room.default_name":"Комната","device.unnamed":"без имени","device.light_group":"группа света","device.fallback":"устройство","device.virtual":"виртуальное устройство","confirm.delete_room":"Удалить комнату «{name}»?","confirm.delete_room_title":"Удалить комнату «{name}»?","confirm.delete_room_body":"Выберите, нужно ли оставить физические стены, принадлежащие только этой комнате. Общие с другой комнатой стены сохраняются всегда.","confirm.merge_rooms":"Слить комнаты «{a}» и «{b}»?","confirm.remove_marker":"Удалить «{name}» с плана? Устройство исчезнет полностью и перестанет участвовать в данных плана. Позже его можно будет добавить заново.","confirm.erase_decor":"Стереть объект «{kind}»? Действие можно отменить из истории редактора.","confirm.delete_space":"Удалить пространство «{title}» со всеми комнатами и разметкой?","space.delete_blocked":"Это пространство всё ещё используется устройствами: {n}. Сначала перенесите их в другое пространство или удалите.","toast.pos_save_failed":"Не удалось сохранить позицию: {err}","toast.no_entity":"У устройства нет подходящей сущности","toast.ha_disabled_action":"Деактивированный объект Home Assistant нельзя использовать на плане.","toast.ha_disabled_show_device":"Деактивированное в Home Assistant устройство нельзя показать на плане. Сначала активируйте его в Home Assistant.","toast.ha_disabled_show_entity":"Деактивированную в Home Assistant сущность нельзя показать на плане. Сначала активируйте её в Home Assistant.","toast.ha_disabled_add":"Деактивированный объект Home Assistant нельзя добавить на план. Сначала активируйте его в Home Assistant.","toast.ha_binding_unverified":"Статус объекта не удалось подтвердить по реестру Home Assistant. Отображение и действия временно недоступны.","toast.markup_needs_server":"Разметка доступна после переноса конфига на сервер","toast.space_order_changed":"Порядок изменён. Если где-то этаж карточки задан номером, проверьте такие панели.","toast.conflict":"Конфиг изменён в другом окне — данные обновлены, повторите последнее действие","toast.cfg_save_failed":"Не удалось сохранить конфиг: {err}","toast.room_overlap":"Контур накладывается на комнату «{name}» — комнаты не должны накладываться","toast.contour_min_edges":"Чтобы замкнуть контур, сначала нарисуйте минимум две грани","toast.contour_cannot_close":"Контур нельзя замкнуть: он вырожден или пересекает сам себя","toast.merge_not_adjacent":"Объединять можно только комнаты с общей стеной","toast.rooms_merged":"Комнаты объединены в «{name}»","toast.split_pick_wall":"Начните разрез на стене комнаты","toast.split_bad_cut":"Разрез — от стены до стены внутри комнаты, без пересечения стен и самого себя","merge.header":"Объединение комнат","merge.hint":"У объединённой комнаты одно имя и одна зона. Вторая зона освобождается — её устройства уйдут с плана, пока их не заберёт другая комната.","merge.keep":"Оставить","merge.no_area":"без зоны","room.split_header":"Новая комната после разделения","toast.room_saved":"Комната сохранена ({n}). Устройств добавлено: {added}. Обведите следующую или выйдите из разметки.","toast.room_saved_no_area":"Комната сохранена ({n}, без зоны). Обведите следующую или выйдите из разметки.","toast.marker_needs_server":"Редактирование устройств доступно после переноса конфига на сервер","toast.virtual_name_required":"Укажите имя виртуального устройства","toast.marker_saved":"Устройство сохранено","toast.marker_removed":"Устройство удалено с плана","toast.integration_missing":"Интеграция House Plan не установлена — управление недоступно","toast.plan_formats":"Поддерживаются SVG, PNG, JPG, WebP","toast.plan_required":"Загрузите подложку — план этажа обязателен","toast.space_added_onboard":"Пространство добавлено. Обведите комнаты: кликайте по точкам сетки и замкните контур.","toast.space_added":"Пространство добавлено","toast.space_saved":"Пространство сохранено","toast.space_deleted":"Пространство удалено","toast.delete_failed":"Ошибка удаления: {err}","toast.error":"Ошибка: {err}","toast.toggle_target_unavailable":"Цель «{name}» недоступна — действие не выполнено","toast.toggle_targets_unavailable":"Цели недоступны: {names}. Действие не выполнено","toast.file_failed":"Файл «{name}» не загружен: {err}","toast.files_attached":"Прикреплено файлов: {n}","err.unknown":"неизвестная ошибка","err.code":"код {code}","err.too_large":"файл больше {mb} МБ","err.bad_ext":"недопустимый тип (нужен PDF/изображение)","err.unauthorized":"нужны права администратора","editor.title":"Заголовок","editor.default_floor":"Стартовое пространство","editor.default_floor_missing":"Стартовое пространство «{id}» больше не существует. Выберите другое.","editor.floor":"Закреплённое пространство","editor.floor_none":"— не закреплять —","editor.floor_index":"YAML-индекс {index} (сохранён)","fixed_floor.loading":"Загружаем закреплённое пространство…","fixed_floor.invalid_title":"Закреплённое пространство недоступно","fixed_floor.invalid_body":"Проверьте floor в конфигурации карточки. Настроенное значение: {value}","editor.icon_size":"Размер иконок, % ширины плана","editor.show_temperature":"Показывать температуру","editor.live_states":"Живые состояния (вкл/выкл, открыто…)","editor.show_signal":"Показывать сигнал zigbee (LQI)","editor.language":"Язык интерфейса","editor.lang_auto":"Авто (профиль HA)","editor.lang_en":"English","editor.lang_ru":"Русский","title.icon_rules":"Правила иконок: какая MDI-иконка достаётся устройству по имени","rules.title":"Правила иконок","rules.hint":"Правила проверяются сверху вниз по строке «имя устройства + модель» (regex без учёта регистра); срабатывает первое совпадение. Если ничего не подошло — решает device class сущности, затем — иконка-заглушка.","rules.pattern_ph":"regex, напр. розетк|plug","rules.icon_ph":"mdi:power-socket-de","rules.add":"Добавить правило","rules.reset":"Сбросить к умолчаниям","rules.test_ph":"Проверьте имя устройства…","rules.invalid":"некорректный regex","rules.saved":"Правила иконок сохранены","btn.up":"Вверх","btn.down":"Вниз","tap.info":"Карточка устройства","tap.more_info":"Диалог HA (more-info)","tap.toggle":"Переключить состояние","marker.tap_label":"Действие по нажатию для этого устройства","tap.toggle_note":"Под селектором показано, какое состояние изменится. Защищённые устройства не переключаются с плана.","import.title":"Создать пространства из этажей HA","import.hint":"Home Assistant уже знает эти этажи. Отметьте, какие превратить в пространства плана — далее для каждого попросим картинку плана. Комнаты затем обводятся вручную по плану.","import.start":"Создать: {n}","import.manual":"Начать с нуля","import.progress":"Этаж {i} из {n}","import.done":"Пространства созданы. Обведите комнаты: кликайте по точкам сетки и замкните контур.","btn.skip":"Пропустить","space.scale_label":"Масштаб (размер клетки сетки)","space.scale_unit":"см на клетку","space.scale_unit_imperial":"дюйм на клетку","space.display_section":"Отображение","space.show_borders":"Всегда отображать границы комнат","space.zero_wall_style":"Стены нулевой толщины","space.zero_wall_dashed":"Пунктирные","space.zero_wall_solid":"Сплошные","space.zero_wall_help":"Пунктирные стены пропускают свет. Сплошные стены блокируют свет, даже при нулевой толщине.","space.show_names":"Отображать названия комнат (перетаскиваются)","space.room_color":"Цвет границ и названий","space.opacity":"Прозрачность","space.fill_label":"Заливка комнат","fill.none":"Нет","fill.lqi":"По силе зигби-сигнала","fill.light":"По освещению","fill.custom":"Свой цвет","space.custom_fill":"Цвет заливки","space.source_file":"У меня есть картинка плана","space.source_draw":"Нет подложки — нарисую комнаты вручную","space.orientation":"Холст","orient.landscape":"Альбомный","orient.portrait":"Портретный","orient.square":"Квадрат","fill.temp":"По температуре","space.temp_min":"Комфорт от","space.temp_max":"до","tip.temp_avg":"средняя температура:","tip.hum_avg":"средняя влажность:","space_card.button":"Перейти к пространству","space_card.not_found":"Пространство «{id}» не найдено","space_card.loading":"Загрузка…","continuity.restore_plan":"Восстанавливаем план…","continuity.restore_connection":"Восстанавливаем подключение к устройствам…","continuity.retry":"Повторить","editor.space":"Пространство","editor.show_button":"Показывать кнопку","editor.button_label":"Текст кнопки","editor.button_target":"Путь дашборда (куда вести)","marker.sub_entity":"сущность","title.general_settings":"Общие настройки","gs.title":"Общие настройки","gs.hint":"Цвета заливок действуют на все пространства; у каждого цвета своя прозрачность. Какой режим заливки использует пространство — задаётся в его диалоге.","gs.light_group":"Заливка: освещение","gs.light_on":"Свет включён","gs.light_off":"Весь свет выключен","gs.temp_group":"Заливка: температура","gs.temp_cold":"Холодно","gs.temp_ok":"Комфорт","gs.temp_hot":"Жарко","gs.lqi_group":"Заливка: зигби-сигнал","gs.lqi_low":"Слабый сигнал","gs.lqi_high":"Сильный сигнал","gs.reset":"Сбросить к умолчаниям","gs.saved":"Общие настройки сохранены","space.show_lqi":"Показывать зигби-сигнал (LQI) у устройств","space.hide_decor":"Скрыть декоративный слой","space.hide_decor_tip":"Линии, фигуры, надписи и мебель остаются на месте — их видно в редакторе подложки, но не на плане.","space.hide_openings":"Скрыть проёмы","space.hide_openings_tip":"Двери, окна и ворота не рисуются, но продолжают работать: свет проходит, солнце светит в окна, датчики открытия срабатывают. В редакторе плана проёмы видно всегда.","gs.light_none":"Нет источников света","mode.plan":"Редактор плана","mode.devices":"Редактор устройств","display.value":"Значение + состояние","marker.subarea":"без зоны, вручную","device.new":"Новое устройство — откройте его редактор, чтобы снять отметку","opening.unlock_action":"Открыть замок","opening.lock_action":"Закрыть замок","opening.lock_pending":"Выполняется…","title.close_editor":"Закрыть редактор (вернуться к просмотру)","devbar.add":"Добавить","devbar.show_all":"Скрытые и деактивированные","devbar.rules":"Правила иконок","device_inbox.button":"Устройства","device_inbox.title":"Устройства на плане","device_inbox.search":"Поиск по устройствам, сущностям и интеграциям…","device_inbox.add_virtual":"Добавить виртуальное устройство","device_inbox.tab_on_plan":"На плане","device_inbox.tab_available":"Доступны","device_inbox.tab_hidden":"Скрытые","device_inbox.tab_readd":"Доступны снова","device_inbox.only_new":"Только новые","device_inbox.show_entities":"Показывать сущности","device_inbox.show_hidden":"Показывать скрытые на плане","device_inbox.new":"Новое","device_inbox.find":"Найти на плане","device_inbox.edit":"Настроить","device_inbox.hide":"Скрыть","device_inbox.show":"Показать","device_inbox.add":"Добавить","device_inbox.readd":"Добавить заново","device_inbox.hide_available":"Скрыть из списка","device_inbox.show_more":"Показать ещё","device_inbox.more_actions":"Другие действия","device_inbox.find_hidden_hint":"Сначала включите «Показывать скрытые на плане»","device_inbox.show_disabled":"Перед показом активируйте эту привязку в Home Assistant","device_inbox.empty_on_plan":"На плане пока нет подходящих устройств.","device_inbox.empty_available":"Нет доступных устройств для добавления.","device_inbox.empty_hidden":"Скрытых устройств нет.","device_inbox.empty_readd":"Нет удалённых устройств, доступных для повторного добавления.","device_inbox.reason_visible_auto":"Найдено автоматически","device_inbox.reason_visible_explicit":"Добавлено явно","device_inbox.reason_manual_hidden":"Скрыто пользователем","device_inbox.reason_automatic_hidden":"Скрыто автоматически","device_inbox.reason_service_entry":"Служебная запись реестра","device_inbox.reason_excluded_integration":"Интеграция исключена фильтрами устройств","device_inbox.reason_excluded_domain":"Непространственный тип сущности","device_inbox.reason_grouped_light":"Представлено группой света комнаты","device_inbox.reason_represented_by_parent":"Представлено родительским устройством","device_inbox.reason_removed":"Ранее удалено с плана","device_inbox.reason_available":"Можно добавить на план","device_inbox.reason_no_bound_room":"Зона HA не связана с комнатой плана","device_inbox.status_ha_disabled":"Отключено в Home Assistant","device_inbox.status_orphaned":"Привязка больше не найдена в Home Assistant","device_inbox.status_unverified":"Реестр Home Assistant временно недоступен","device_inbox.saved":"Список устройств обновлён","space.roomcard_section":"В карточке комнаты:","space.label_temp":"Температура","space.label_hum":"Влажность","space.label_lqi":"Средний Zigbee-сигнал","space.label_light":"Свет вкл/выкл","roomcard.light_on":"Вкл","roomcard.light_off":"Выкл","roomcard.light_partial":"{on} из {total}","toast.split_pick_inside":"Промежуточные точки разреза — внутри комнаты","mode.decor":"Редактор подложки","decor.select":"Выбрать","decor.line":"Линия","decor.rect":"Прямоугольник","decor.ellipse":"Овал","decor.text":"Надпись","decor.erase":"Стереть","decor.erase_confirm_title":"Стереть объект?","decor.color":"Цвет","decor.width":"Толщина линии","decor.line_style":"Стиль линии","decor.line_style_solid":"Сплошная","decor.line_style_dashed":"Пунктирная","decor.fill":"Заливка","decor.fill_color":"Цвет заливки","decor.length":"Длина","decor.size":"Размер","decor.angle":"Угол поворота, °","decor.text_size":"Размер текста","decor.backdrop_properties":"Свойства картинки-подложки","decor.text_title":"Надпись","decor.object_title":"Редактирование: {kind}","decor.text_label":"Текст","decor.live_group":"Вставить переменную HA","decor.live_entity":"Сущность","decor.live_entity_ph":"выберите сущность","decor.live_attr":"Значение","decor.live_attr_ph":"выберите состояние или атрибут","decor.live_state":"Состояние","decor.backdrop":"Картинка-подложка","decor.backdrop_hint":"Тяните картинку — перемещение; угол — размер; верхнюю ручку — поворот. Shift меняет пропорции или снимает шаг угла.","decor.backdrop_reset":"Вернуть картинку","decor.backdrop_reset_done":"Картинка вернулась на своё место и в свой размер","marker.icon_auto":"Авто: {icon} (по правилам иконок; выберите свою, чтобы заменить)","marker.icon_pin_auto":"Закрепить","mode.plan_tip":"Редактор плана — геометрия дома: рисование и объединение/разделение комнат, привязка к зонам HA, двери, окна и ворота, карточки комнат, масштаб","mode.devices_tip":"Редактор устройств — всё про значки: перетаскивание, клик — настройка привязки/иконки/отображения, виртуальные устройства, правила иконок","mode.decor_tip":"Редактор подложки — чисто визуальный декор под планом: линии, прямоугольники, овалы и надписи, не реагирующие на клики","space.glow_enabled":"Свечение источников света","gs.glow_group":"Свечение источников света","gs.glow_base":"Темнота дома","gs.glow_light":"Цвет света по умолчанию / интенсивность","gs.wall_group":"Стены","gs.wall_fill":"Цвет заливки стен","gs.glow_radius":"Радиус свечения","gs.unit_m":"м","gs.unit_ft":"фут","marker.controls_label":"Управляет другими источниками света","marker.controls_hint":"При действии «Переключить» клик разом переключает все добавленные здесь источники. Собственная сущность маркера управляется напрямую и сюда не добавляется; чтобы считать её светом, включите флаг ниже.","marker.controls_filter":"Поиск ламп и выключателей…","info.controls":"Управляет","marker.glow_radius_label":"Радиус свечения","marker.glow_radius.help":"Задаёт радиус свечения в метрах или футах; пустое значение использует радиус из общих настроек.","marker.glow_radius.help.aria":"Подсказка: радиус свечения","markup.wallthick":"Толщина","markup.select":"Выбрать","title.markup_wallthick":"Толщина — клик по стене задаёт толщину от 0 до 100 см.","markup.hint_wallthick":"клик по стене · Esc закрывает без применения","wallthick.field":"Толщина","wallthick.unit_cm":"см","wallthick.unit_in":"дюйм","wallthick.apply_room":"Применить ко всем стенам комнаты","markup.draw_wall_title":"Толщина каждого нового сегмента стены (0–100 см). Общие стены сохраняют существующее значение.","room.queue_progress":"Комната {current} из {total}","toast.wall_rooms_saved":"Создано комнат: {n}","toast.wall_chain_saved":"Цепочка стен сохранена","toast.wallthick_pick":"Кликните по стене","toast.wallthick_set":"Толщина стены задана","toast.wallthick_cleared":"Толщина стены убрана","toast.physical_range":"Введите значение от {min} до {max} {unit}","toast.zero_wall_opening_conflict":"Сначала удалите проём на этом участке стены.","toast.zero_wall_ambiguous":"Не удалось однозначно выбрать участок стены. Уточните геометрию узла.","toast.zero_wall_migration_blocked":"Пространство не преобразовано: {reason}. Данные не изменены.","toast.physical_angle":"Введите угол поворота от 0° до 90°, не включая 90°","toast.physical_limit":"В пространстве достигнут лимит объектов этого типа","toast.geometry_unsafe":"Изменение отменено: геометрию стен нельзя безопасно построить.","junction.limit_angle":"Слишком острый угол между стенами: {actual}°, минимум {limit}°.","junction.limit_valence":"Слишком много стен в одном узле: {actual}, максимум {limit}.","junction.limit_length":"Слишком короткий участок стены: {actual} см, минимум {limit} см.","junction.limit_distance":"Стены и узлы слишком близко: {actual} см, минимум {limit} см.","junction.limit_clearance":"Внутри комнаты не остаётся места: {actual} см², минимум {limit} см².","junction.limit_check_failed":"Проверку стыков выполнить не удалось — изменение не сохранено","toast.wall_model_migration_blocked":"Не удалось обновить модель стен: {reason}. План не изменён. Запустите «Оптимизировать планы»; если ошибка повторится, исправьте конфликтующую геометрию стен.","toast.wall_model_client_outdated":"Обновите карточку и перезагрузите страницу перед редактированием плана.","wall_model.reason.invalid-room":"некорректный контур комнаты","wall_model.reason.zero-length":"отрезок стены нулевой длины","wall_model.reason.third-owner":"стена принадлежит более чем двум комнатам","wall_model.reason.duplicate-id":"конфликт идентификаторов стен","wall_model.reason.thickness-conflict":"конфликт значений толщины стены","wall_model.reason.opening-host":"проём нельзя однозначно привязать к стене","toast.delete_room_pick":"Кликните внутри комнаты, которую нужно удалить","toast.plan_snap_ambiguous":"Увеличьте масштаб, чтобы выбрать узел стены","toast.wall_repair_ambiguous":"У контура несколько возможных соединений. Увеличьте масштаб и соедините стены явно.","toast.wall_repair_too_large":"Разрыв больше 2 см. Соедините стены перед созданием комнаты.","toast.wall_repair_changed":"Геометрия стен изменилась. Попробуйте создать комнату ещё раз.","toast.opening_on_zero_wall":"Проёмы на стене нулевой толщины запрещены","marker.from_ha_option":"Выбрать из списка HA","marker.show_entities":"Показывать сущности","marker.show_entities_tip":"Добавляет в список не только устройства, но и все их сущности","marker.pick_ph":"Выберите устройство…","room.open_area":"Открыть зону в HA","view.volumetric":"Объёмный вид","view.flat":"Плоский вид","kiosk.title":"Размеры на этом экране","kiosk.hint":"Хранится только на этом устройстве — у каждого настенного планшета или ТВ свои удобные размеры.","kiosk.icon_scale":"Размер значков устройств","kiosk.font_scale":"Размер текста карточек комнат","editor.kiosk":"Режим настенного устройства (киоск)","editor.cycle":"Автосмена пространств каждые N секунд (киоск, 0 = выкл)","room.settings_title":"Настройки комнаты","room.settings_section":"Настройки комнаты (переопределяют пространство)","room.fill_label":"Заливка в ЭТОЙ комнате","fill.inherit":"Как у пространства","room.custom_fill_space":"Цвет пространства","room.custom_fill_own":"Цвет комнаты","room.temp_src_label":"Источник температуры","room.hum_src_label":"Источник влажности","room.src_average":"Средняя по датчикам комнаты (по умолчанию)","room.src_pick":"Конкретное устройство или сущность HA","room.src_ph":"Выберите источник…","toast.room_updated":"Комната обновлена","space.card_font":"Размер шрифта карточек комнат (всё пространство)","room.sizes_section":"Размеры шрифтов","room.name_scale":"Размер названия","room.label_scale":"Размер подписей","preview.room_name":"Гостиная","toast.cfg_reload_failed":"Не удалось перечитать план с сервера: {err}","room.settings_short":"Настройки комнаты","room.unnamed":"Комната без имени","marker.use_climate_temp":"Учитывать температуру устройства в комнате","marker.use_climate_temp_tip":"Добавляет current_temperature кондиционера или термостата в среднюю температуру комнаты. Внешний бейдж со значением настраивается отдельно ниже.","marker.value_badge_title":"Бейдж со значением","marker.value_badge.help":"Показывает одно выбранное значение рядом со значком. Не влияет на комнатные показатели, свет, Glow и действие по нажатию.","marker.value_badge.help.aria":"Подсказка: бейдж со значением устройства","marker.value_badge_enabled":"Отображать бейдж со значением","marker.value_badge_source":"Значение","marker.value_badge_source.help":"Выберите конкретное состояние, поддерживаемый атрибут или производное значение этого устройства.","marker.value_badge_source.help.aria":"Подсказка: источник значения бейджа","marker.value_badge_position":"Расположение","marker.value_badge_position.help":"Выбранная сторона сохраняется постоянно и не переворачивается автоматически у края плана.","marker.value_badge_position.help.aria":"Подсказка: расположение бейджа","marker.value_badge_right":"Справа","marker.value_badge_bottom":"Снизу","marker.value_badge_left":"Слева","marker.value_badge_top":"Сверху","marker.value_badge_empty":"У этого устройства нет доступных значений","marker.value_badge_static":"Статичный значок не показывает живые значения. Настройка сохранится.","marker.value_badge_missing":"Источник недоступен","marker.value_badge_missing_hint":"Сохранённый источник сейчас недоступен. Можно сохранить его, отключить бейдж или выбрать другой.","marker.value_badge_duplicate":"То же значение уже показано внутри значка.","marker.value_badge_state":"Состояние · {name}","marker.value_badge_marker_state":"Состояние источника света · {name}","marker.value_badge_lqi":"Качество Zigbee-сигнала (среднее)","marker.value_badge_attr_current_temperature":"Текущая температура · {name}","marker.value_badge_attr_temperature":"Целевая температура · {name}","marker.value_badge_attr_current_humidity":"Текущая влажность · {name}","marker.value_badge_attr_humidity":"Влажность · {name}","marker.value_badge_attr_current_position":"Положение · {name}","marker.value_badge_attr_percentage":"Скорость · {name}","marker.value_badge_attr_brightness":"Яркость · {name}","marker.value_badge_attr_volume_level":"Громкость · {name}","marker.value_badge_attr_battery_level":"Заряд батареи · {name}","marker.value_badge_attr_fan_speed":"Скорость вентилятора · {name}","marker.light_role_label":"Является источником света","marker.light_role.help":"Определяет, является ли сам маркер пространственным источником света. Привязанные выше лампы остаются независимыми источниками для комнаты.","marker.light_role.help.aria":"Подсказка: является ли устройство источником света","marker.light_role_auto_yes":"Автоматически (источник света)","marker.light_role_auto_no":"Автоматически (не источник света)","marker.light_role_always":"Всегда источник света","marker.light_role_never":"Никогда не источник света","marker.light_entity_label":"Ведущая сущность света","marker.light_entity.help":"Для составного устройства выберите сущность, состояние, цвет и яркость которой представляют этот источник на плане. Автоматический вариант сохраняет совместимость со старыми планами.","marker.light_entity.help.aria":"Подсказка: ведущая сущность света","marker.light_entity_auto":"Автоматически ({entity})","marker.light_entity_none":"нет управляемой сущности","marker.light_entity_missing":"Сохранённая сущность {entity} недоступна. Временно используется {fallback}; выбор восстановится, если сущность вернётся.","marker.toggle_entity_label":"Переключаемая сущность","marker.toggle_entity.help":"Для составного устройства выберите собственную лампу или переключатель, которой управляет нажатие. Автоматический вариант сохраняет прежние правила цели и не зависит от ведущей сущности света.","marker.toggle_entity.help.aria":"Подсказка: переключаемая сущность","marker.toggle_entity_auto":"Автоматически ({entity})","marker.toggle_entity_none":"нет собственной управляемой сущности","marker.toggle_entity_missing":"Сохранённой сущности {entity} больше нет среди выбираемых каналов этого маркера. Временно используется {fallback}; выбор восстановится, если сущность вернётся.","marker.glow_color_label":"Цвет и яркость свечения","marker.glow_mode.help":"Используйте данные источника, задайте только цвет или зафиксируйте цвет и яркость. Минимум — 1%; чтобы отключить источник, выберите «Никогда».","marker.glow_mode.help.aria":"Подсказка: цвет и яркость свечения","marker.glow_mode_auto":"Из источника","marker.glow_mode_color":"Задать цвет","marker.glow_mode_fixed":"Задать цвет и яркость","marker.glow_color":"Цвет свечения","marker.glow_brightness":"Яркость","marker.glow_disabled_never":"Настройки свечения недоступны: маркер явно исключён из источников света.","marker.glow_disabled_auto":"В автоматическом режиме у этого маркера не найден пространственный источник света.","marker.glow_disabled_no_entity":"Для пространственного источника нет активной управляемой сущности Home Assistant.","marker.glow_passive_hint":"У источника нет собственных данных Home Assistant. Задайте цвет и яркость вручную; радиус остаётся доступным.","marker.control_broken":"Сохранённый источник отсутствует или больше не отмечен как источник света","marker.control_missing_label":"Отсутствующий источник на плане","marker.control_passive":"пассивный источник","toast.marker_control_cycle":"Такая связь создаст циклическую цепочку управления светом.","toast.marker_binding_required":"Сначала выберите устройство Home Assistant, затем добавьте управляемый источник света.","confirm.unlock":"Открыть замок «{name}»?","toast.files_migrate_failed":"Не удалось перенести вложения к новой привязке, ссылки остались на старые файлы: {err}","space.pick_saved":"Уже загруженные","space.pick_saved_hint":"Планы, сохранённые на сервере, включая отцеплённые ранее","space.no_saved":"На сервере пока нет сохранённых планов.","space.loading":"Загрузка…","space.used_by":"используется: {list}","space.in_use":"План используется пространством — сначала отцепите его","btn.use":"Выбрать","confirm.delete_plan":"Удалить файл плана «{name}» с сервера? Действие необратимо.","toast.plans_list_failed":"Не удалось получить список планов: {err}","toast.plan_delete_failed":"Не удалось удалить план: {err}","marker.hide":"Скрыть","marker.hide_tip":"Устройство исчезнет с плана после сохранения, но продолжит участвовать в расчёте сигнала комнаты. Вернуть его можно через каталог «Устройства» в редакторе устройств.","marker.show":"Показать","marker.show_tip":"Устройство снова появится на плане после сохранения.","marker.hidden_ghost":"Устройство скрыто пользователем","marker.ha_disabled_device":"Устройство деактивировано в Home Assistant и скрыто с плана.","marker.ha_disabled_entity":"Сущность деактивирована в Home Assistant и скрыта с плана.","marker.ha_disabled_all_entities":"У устройства нет активных сущностей Home Assistant, поэтому оно скрыто с плана.","marker.ha_registry_limited":"Полный реестр Home Assistant недоступен этому пользователю. Неподтверждённый объект временно нельзя показывать или использовать.","marker.delete_tip":"Полностью удалить устройство с плана и из всех расчётов. Позже его можно добавить заново.","tap.run":"Запустить автоматизацию/скрипт/сцену","tap.cover":"Открыть/закрыть (шторы/жалюзи)","marker.run_target_label":"Что запускать","marker.run_search_ph":"Поиск: автоматизация, скрипт или сцена…","marker.run_target_gone":"Цель {id} не найдена — выберите заново","marker.tap_confirm":"Спрашивать подтверждение","marker.tap_confirm_tip":"Перед выполнением показать диалог подтверждения — защита от случайных нажатий.","marker.toggle_hint_single":"Цель: {name} ({id}).","marker.virtual_light_target":"Виртуальный свет: {name}.","marker.virtual_light_current":"Ручное состояние: {state} → {effect}.","marker.virtual_light_state_on":"включён","marker.virtual_light_state_off":"выключен","marker.toggle_hint_group":"Переключатся источники ({count}): {names}.","marker.toggle_hint_current":"Сейчас: {state} → {effect}.","marker.toggle_hint_group_current":"Сейчас включено {on} из {count} → {effect}.","marker.toggle_hint_skipped":"Не будут затронуты ({count}): {targets}.","marker.toggle_effect_turn_on":"включится","marker.toggle_effect_turn_off":"выключится","marker.toggle_effect_open":"откроется","marker.toggle_effect_close":"закроется","marker.toggle_effect_stop":"остановится","marker.toggle_effect_toggle":"состояние переключится","marker.toggle_skip_missing":"не найдено","marker.toggle_skip_ha_disabled":"деактивировано в HA","marker.toggle_skip_unavailable":"недоступно","marker.toggle_skip_unsupported":"переключение не поддерживается","marker.toggle_skip_secure":"заблокировано из соображений безопасности","marker.toggle_none_no_binding":"У устройства нет привязки, состояние переключать нечем. По нажатию ничего не произойдёт.","marker.toggle_none_no_actionable_entity":"У устройства нет состояния, которое можно переключить. По нажатию ничего не произойдёт.","marker.toggle_none_configured_targets_missing":"Настроенные цели сейчас недоступны. Собственная сущность устройства не будет подставлена вместо них.","marker.toggle_none_ha_disabled":"Цель деактивирована в Home Assistant и не может использоваться на плане.","marker.toggle_none_unavailable":"Цель сейчас недоступна. По нажатию ничего не произойдёт.","marker.toggle_none_unsupported":"Для этой сущности Home Assistant не предоставляет безопасного переключения. По нажатию ничего не произойдёт.","marker.toggle_none_secure":"Переключение замков, сигнализации и защитных ворот с плана заблокировано из соображений безопасности.","run.automation":"автоматизация","run.script":"скрипт","run.scene":"сцена","confirm.tap_run":"Запустить «{name}»?","confirm.tap_toggle":"Переключить «{name}»?","confirm.tap_cover":"Открыть/закрыть «{name}»?","confirm.current_state":"Текущее состояние: {state}","confirm.expected_state":"После переключения: {state}","confirm.group_current":"включено {on} из {total}","confirm.group_all_on":"все включены","confirm.group_all_off":"все выключены","confirm.unavailable_targets":"Недоступно: {count}","confirm.expected_by_ha":"Состояние определит Home Assistant","confirm.state_on":"Включено","confirm.state_off":"Выключено","confirm.state_open":"Открыто","confirm.state_closed":"Закрыто","confirm.state_opening":"Открывается","confirm.state_closing":"Закрывается","confirm.state_stopped":"Остановлено","confirm.state_unknown":"Неизвестно","toast.run_started":"Запущено: {name}","toast.run_target_missing":"Цель запуска не найдена — проверьте настройки устройства","toast.run_target_required":"Выберите автоматизацию, скрипт или сцену","toast.tap_target_changed":"Цель действия изменилась. Повторите попытку.","toast.virtual_light_toggle_failed":"Не удалось переключить виртуальный свет: {err}","toast.value_badge_source_required":"Выберите значение для бейджа","btn.run":"Выполнить","vac.section":"Робот-пылесос: живая позиция","vac.autocal":"Настроить автоматически","vac.live":"Живая позиция на плане","vac.trail":"Показывать путь робота","vac.cal_maps":"Откалиброваны карты: {maps}","vac.autocal_no_rooms":"Интеграция не отдаёт список комнат — откройте «Подогнать вручную»","vac.autocal_no_match":"Не совпали имена комнат (нужно ≥3 общих) — откройте «Подогнать вручную»","vac.autocal_done":"Готово: привязка по {rooms} комнатам. Запустите уборку и проверьте","vac.cal_need_pos":"Робот сейчас не отдаёт координаты — запустите уборку и поставьте на паузу","vac.cal_done":"Калибровка сохранена. Запустите уборку и проверьте","vac.cal_cancelled":"Калибровка отменена","vac.fit":"Подогнать вручную","vac.fit_hint":"Перетащите карту робота на место, растяните за уголки","vac.fit_rotate":"Повернуть 90°","vac.fit_mirror":"Отразить","vac.trail_never":"Не показывать никогда","vac.trail_cleaning":"Во время уборки","vac.trail_always":"Показывать всегда","gs.bg_group":"Фон сцены","gs.bg_color":"Цвет фона вокруг плана","gs.bg_default":"Как в теме","gs.bg_theme":"по умолчанию — из темы","gs.bg_mode":"Фон плана","gs.bg_static":"Статичный цвет","gs.bg_daynight":"Следует за Солнцем","gs.bg_daynight_hint":"Фон проходит рассвет, день, закат и ночь по данным sun.sun, а при их недоступности — по локальным часам. Компас для фона не нужен.","gs.sun_group":"Солнце","gs.sun_missing":"Сущность sun.sun не найдена — фон следует локальным часам, а оконные лучи недоступны.","gs.north":"Север на плане","gs.north_ph":"не задан","gs.north_hint":"Север нужен только для оконных лучей (шаг 1°, с Shift 15°); солнечный фон работает без него.","gs.north_clear":"Сбросить","gs.north_letter":"С","gs.sun_rays":"Солнце в окнах","gs.about_group":"О карточке","gs.about_version":"Houseplan Card v{v}","gs.about_github":"GitHub · документация и issues","gs.about_telegram":"Чат в Telegram","space.bg_color":"Цвет фона вокруг плана","space.bg_inherit":"Наследовать общий","space.bg_inherited":"наследуется из общих настроек","space.bg_mode":"Фон плана","space.north":"Север на плане (переопределение)","space.north_inherited":"наследуется: {v}","space.sun_rays":"Солнце в окнах","space.sun_inherit":"Наследовать общий","space.sun_on":"Вкл","space.sun_off":"Выкл","canvas.far_objects":"Объектов далеко от плана: {n}","canvas.show_far":"Показать","canvas.home_tip":"План там — нажмите, чтобы вписать","gs.grid_group":"Обслуживание планов","gs.grid_hint":"Обновляет модели данных, выравнивает элементы по сетке и объединяет лишние фрагменты стен. Перед записью будет показан точный отчёт.","gs.align_all":"Оптимизировать планы","gs.align_title":"Оптимизировать планы","gs.align_none":"Все планы уже используют актуальную и оптимальную модель данных.","gs.optimize_no_automatic_changes":"Автоматических изменений нет. Проверьте пункты ниже.","gs.align_count":"Сдвинется элементов: {n} из {total}, максимум на {cm} см.","gs.align_where":"Наибольший сдвиг — в пространстве «{s}».","gs.align_turned":"Проёмов с исправлением угла: {n}.","gs.align_removed_drafts":"Схлопнувшиеся на сетке некорректные контуры удалены: {n}.","gs.optimize_redundant_drafts":"Сохранённые цепочки стен, полностью скрытые стенами комнат, удалены: {n}.","gs.align_preflight_failed":"Не удалось безопасно проверить геометрию следующих пространств: {spaces}{more}.","gs.align_preflight_hint":"Планы не изменены. Скопируйте диагностику кнопкой ниже и приложите её к отчёту об ошибке вместе с экспортом пространства.","gs.preflight_reason_prepare-exception":"Не удалось подготовить геометрию пространства (исключение при сборке модели)","gs.preflight_reason_wall-null":"Тело стен не построилось (объединение вернуло пустоту)","gs.preflight_reason_wall-degraded-extra":"Тело стен деградировало с лишней геометрией","gs.preflight_reason_wall-failed-core":"Ядро тела стен не собралось","gs.preflight_reason_wall-exception":"Построение стен упало с исключением","gs.preflight_reason_floor-null":"Контур пола не построился","gs.preflight_reason_floor-exception":"Построение пола упало с исключением","gs.preflight_copy":"Скопировать диагностику","gs.preflight_copied":"Диагностика скопирована","gs.preflight_update_hint":"Версии карточки и интеграции различаются — обновите House Plan и повторите.","gs.align_preflight_space":"Пространство {n}","gs.align_preflight_more":" и ещё {n}","gs.optimize_changes":"Миграций модели: {m}; обновлено пространств: {c}; устранён шум координат: {p}; объединено отрезков реальных стен: {w}; объединено отрезков стен нулевой толщины: {s}; независимых: {i}.","gs.zero_walls_migrated":"Преобразовано виртуальных участков: {n}.","gs.wall_segments_migrated":"Стабилизировано сегментов стен: {n}.","gs.optimize_lattice_summary":"Канонизировано шумовых значений координат: {n}; максимальный сдвиг: {cm} см.","gs.optimize_lattice_space":"{space}: канонизировано значений координат: {n}; оставлено значений вне сетки: {far}.","gs.optimize_coincident_partitions":"Скрытые участки независимых стен поглощены стенами комнат: {n}.","gs.optimize_openings_rehosted":"Проёмы перепривязаны к стенам комнат: {n}.","gs.optimize_walls_straightened":"Выпрямлено стен: {n}; максимальное перемещение: {cm} см.","gs.optimize_walls_straightened_where":"Максимальная правка стены: {s}.","gs.optimize_walls_straighten_skipped":"Оставлено почти осевых стен, которые нельзя безопасно выпрямить: {n}.","gs.optimize_glow_migration":"Старый Glow: пространств — {spaces} → без заливки данных + независимый Glow; комнат — {rooms} → наследуемая заливка + независимый Glow.","gs.optimize_references":"Исправлено ссылок: пространства — {spaces}; комнаты — {rooms}; позиции — {positions}; устройств отвязано от отсутствующих пространств — {detached}.","gs.optimize_reference_more":", и ещё {n}","gs.optimize_orphans_removed":"Убрано забытых записей: {total} — подписи комнат: {rooms}; устройства: {devices}; групповые метки: {groups}. Все они принадлежали пространствам, удалённым ранее.","gs.optimize_live_positions":"Старые позиции в удалённых пространствах принадлежат существующим объектам: {n}{names}. Они будут сохранены.","gs.optimize_live_positions_remove":"Старые позиции в удалённых пространствах принадлежат существующим объектам: {n}{names}. Они выбраны для удаления.","gs.optimize_live_names":": {names}{more}","gs.optimize_live_remove":"Убрать старые позиции","gs.optimize_live_keep":"Сохранить старые позиции","gs.optimize_live_selected":"Старые позиции будут убраны после применения оптимизации.","gs.optimize_unverified":"Не удалось безопасно проверить позиций: {n}. Они оставлены без изменений.","gs.optimize_registry_limited":"Для безопасной проверки нужен полный доступ администратора к реестрам Home Assistant.","gs.optimize_vacuum_warning":"Сопоставления комнат пылесоса требуют проверки: {n}.","gs.optimize_details":"Подробности","gs.optimize_details_more":"И ещё записей: {n}.","gs.optimize_detail_removed":"будет удалено","gs.optimize_detail_live":"будет сохранено","gs.optimize_detail_unverified":"не проверено","gs.optimize_detail_room_label":"подпись комнаты","gs.optimize_detail_device":"устройство","gs.optimize_detail_group":"групповая метка","gs.optimize_detail_unknown":"неизвестный владелец","gs.optimize_detail_item":"{status}: {kind} {id}; прежнее пространство {space}","gs.align_warn":"Элементы, намеренно поставленные между узлами, будут сдвинуты. После операции доступна одна отмена — только до следующего изменения плана.","gs.align_run":"Оптимизировать","gs.align_done":"Планы оптимизированы: сдвинуто элементов — {n}, обслужено записей — {m}, исправлено ссылок — {r}","gs.optimize_undo":"Отменить последнюю оптимизацию","gs.optimize_undone":"Последняя оптимизация отменена","decor.furniture":"Мебель","furn.title":"Библиотека мебели","furn.symbol":"Символ","furn.group_furniture":"Мебель","furn.group_appliance":"Техника","furn.group_sanitary":"Сантехника","furn.group_other":"Прочее","furn.width":"Ширина","furn.depth":"Глубина","furn.pick_hint":"Выберите символ и кликните по плану.","furn.place_hint":"Кликните по плану — предмет встанет к ближайшей стене. Shift — свободно.","furn.sym_sofa":"Диван","furn.sym_armchair":"Кресло","furn.sym_coffee_table":"Журнальный столик","furn.sym_table_dining":"Обеденный стол","furn.sym_table_round":"Круглый стол","furn.sym_chair":"Стул","furn.sym_desk":"Письменный стол","furn.sym_bed_double":"Двуспальная кровать","furn.sym_bed_single":"Односпальная кровать","furn.sym_nightstand":"Тумбочка","furn.sym_wardrobe":"Шкаф","furn.sym_bookshelf":"Стеллаж","furn.sym_fridge":"Холодильник","furn.sym_stove":"Плита","furn.sym_dishwasher":"Посудомоечная машина","furn.sym_washer":"Стиральная машина","furn.sym_dryer":"Сушильная машина","furn.sym_tv":"Телевизор","furn.sym_ac":"Кондиционер","furn.sym_water_heater":"Бойлер","furn.sym_toilet":"Унитаз","furn.sym_bathtub":"Ванна","furn.sym_shower":"Душ","furn.sym_sink":"Раковина","furn.sym_kitchen_sink":"Кухонная мойка","furn.sym_bidet":"Биде","furn.sym_stairs":"Лестница","furn.sym_fireplace":"Камин","furn.sym_plant":"Растение","furn.sym_rug":"Ковёр","common.yes":"Да","common.no":"Нет","vac.diag_source":"Источник","vac.diag_platform":"Интеграция","vac.diag_status":"Статус","vac.diag_position":"Позиция","vac.diag_rooms":"Комнаты","vac.diag_rooms_value":"{total} · совпало имён: {matched} · {readiness}","vac.autocal_ready":"автокалибровка доступна","vac.autocal_not_ready":"нужно 3 совпавших имени","vac.diag_path":"Путь интеграции","vac.diag_map":"ID карты","vac.source_none":"не выбран","vac.source_status_ok":"Готов","vac.source_status_missing":"Не найден","vac.source_status_disabled":"Деактивирован в Home Assistant","vac.source_status_unavailable":"Недоступен","vac.source_status_unverified":"Нельзя проверить с текущими правами","vac.source_status_unsupported":"Нет данных позиции","vac.source_status_none":"Нет источника","vac.source_banner_missing":"Сохранённый источник больше не существует. Он не заменён автоматически: выберите другой или восстановите его в Home Assistant.","vac.source_banner_disabled":"Сохранённый источник деактивирован в Home Assistant. Активируйте его там или выберите другой.","vac.source_banner_unverified":"Текущих прав Home Assistant недостаточно для проверки источника. Привязка сохранена и не будет заменена автоматически.","vac.choose_source":"Выбрать источник","vac.source_auto":"Автоматически","vac.source_auto_hint":"Использовать совместимую сущность этого устройства","vac.all_cameras":"Все камеры","vac.all_cameras_warn":"Камера может не отдавать данные робота. Выбирайте её только если это карта вашего пылесоса.","vac.all_cameras_empty":"Других сущностей камеры не найдено.","vac.platform_unknown":"интеграция неизвестна","vac.cap_position":"позиция","vac.cap_rooms_short":"комнаты","vac.cap_path":"путь","vac.cap_map":"ID карты","vac.cap_none":"данные робота не обнаружены","vac.xcme_hint":"Включите атрибуты Xiaomi Cloud Map Extractor:","vac.documentation":"Документация","vac.residual_title":"Проверьте автокалибровку","vac.residual_message":"Совпавшие комнаты расходятся максимум на {error}. Примените приблизительную калибровку, подгоните её вручную либо отмените без изменения сохранённых настроек.","vac.apply_proposal":"Применить","gs.backup_group":"Резервная копия и перенос","gs.backup_hint":"Скачайте переносимую JSON-копию или проверьте и импортируйте копию, созданную House Plan.","backup.export_open":"Экспорт","backup.import_open":"Импорт","backup.export_title":"Экспорт House Plan","backup.import_title":"Импорт House Plan","backup.export_hint":"Выберите, должна ли копия содержать всю конфигурацию House Plan или только текущее пространство.","backup.full":"Полная копия","backup.current_space":"Текущее пространство","backup.current_space_title":"Текущее пространство: {title}","backup.no_current_space":"Нет текущего пространства","backup.plan_only":"Только планировка","backup.plan_only_hint":"Сохранить комнаты, стены, проёмы, декор и позиции подписей комнат без устройств и привязок Home Assistant.","backup.plan_only_preview":"Файл содержит только планировку","backup.privacy_warning":"Архив сохраняет названия, идентификаторы Home Assistant и точные координаты. Внутренние планы и вложения указываются ссылками, но не вкладываются; текущие состояния и маршруты пылесосов не включаются.","backup.download":"Скачать JSON","backup.export_done":"Резервная копия скачана","backup.reading":"Проверяем резервную копию…","backup.revalidated":"После предпросмотра план изменился. Сводка обновлена — проверьте её и подтвердите ещё раз.","backup.error.unauthorized":"У вас нет прав на экспорт или импорт этого плана.","backup.error.not_ready":"House Plan ещё не готов. Повторите попытку через несколько секунд.","backup.error.too_large":"Размер резервной копии превышает 8 МиБ.","backup.error.invalid_json":"Выбранный файл не является корректным JSON.","backup.error.invalid_format":"Выбранный файл не является резервной копией House Plan.","backup.error.unsupported_export_version":"Эта версия формата резервной копии не поддерживается установленной версией.","backup.error.future_model":"Резервная копия создана в более новой версии модели данных House Plan.","backup.error.invalid_config":"Резервная копия содержит некорректную конфигурацию House Plan.","backup.error.wall_model_migration_blocked":"Не удалось безопасно обновить модель стен из резервной копии. Сначала оптимизируйте исходный план и повторите экспорт.","backup.error.invalid_layout":"Резервная копия содержит некорректные позиции объектов.","backup.error.invalid_content":"Резервная копия содержит некорректные или несогласованные ссылки на файлы.","backup.error.space_not_found":"Выбранное пространство больше не существует.","backup.error.capacity_exceeded":"Добавление этой копии превысит ограничения размера плана.","backup.error.preview_expired":"Время предпросмотра истекло. Выберите файл резервной копии заново.","backup.error.preview_owner_mismatch":"Этот предпросмотр принадлежит другому пользователю Home Assistant.","backup.error.conflict":"После предпросмотра план изменился. Проверьте обновлённую сводку.","backup.error.content_confirmation_required":"Подтвердите отсоединение недоступных локальных файлов.","backup.error.commit_failed":"Не удалось безопасно применить копию. Предыдущий план восстановлен или ожидает восстановления.","backup.error.missing_plan":"После предпросмотра исчез связанный файл плана. Проверьте копию ещё раз.","backup.error.missing_content":"После предпросмотра исчез связанный локальный файл. Проверьте копию ещё раз.","backup.error.marker_control_missing":"В резервной копии отсутствует связанный источник света на плане.","backup.error.marker_control_not_light":"Связанная цель больше не отмечена как источник света.","backup.error.marker_control_self":"Источник света не может управлять самим собой.","backup.error.marker_control_cycle":"Резервная копия содержит циклическую цепочку управления светом.","backup.error.duplicate_marker_control":"Резервная копия содержит повторяющуюся связь с источником света.","backup.error.no_backup":"Нет снимка импорта или оптимизации, который можно восстановить.","backup.same_source":"Создано на этом экземпляре Home Assistant","backup.foreign_source":"Создано на другом экземпляре Home Assistant","backup.created":"Создано: {value}","backup.versions":"Карточка {card}; интеграция {integration}; модель данных {model}","backup.count_spaces":"Пространств: {n}","backup.count_rooms":"Комнат: {n}","backup.count_walls":"Стен: {n}","backup.count_openings":"Проёмов: {n}","backup.count_decor":"Объектов декора: {n}","backup.count_markers":"Устройств: {n}","backup.count_layout":"Позиций: {n}","backup.bindings":"Привязки — устройства: {device}, сущности: {entity}, виртуальные: {virtual}; позиций без пространства: {legacy}","backup.binding_status":"Статус на целевом экземпляре — активных: {active}, деактивированных: {disabled}, отсутствующих: {missing}","backup.missing_areas":"На целевом экземпляре отсутствуют зоны: {areas}","backup.dropped_marker_links":"Связи с источниками света вне переносимого пространства пропущены: {n}.","backup.repaired_target_refs":"Существующих ссылок восстановлено этим импортом: {n}.","backup.preserved_unresolved_refs":"Неоднозначные ссылки сохранены без изменений: {n}.","backup.preserved_unresolved_hint":"House Plan ничего не угадывал и не удалял. После импорта запустите «Оптимизировать планы», чтобы проверить оставшиеся ссылки.","backup.import_details":"Подробности восстановления ссылок","backup.import_detail.incoming_remapped":"Ссылок обновлено внутри импортируемой копии: {n}","backup.import_detail.target_repaired":"Существующих ссылок восстановлено: {n}","backup.import_detail.preserved_unresolved":"Неразрешимых ссылок сохранено: {n}","backup.import_detail.collisions":"Конфликтов назначения безопасно сохранено: {n}","backup.import_detail.dropped_links":"Входящих связей пропущено по правилам переноса: {n}","backup.import_detail.bounded_lineages":"Чрезмерно вложенных идентификаторов ограничено: {n}","backup.replace_warning":"Текущая конфигурация и расположение будут заменены. Загруженные файлы не удаляются. Одну отмену можно выполнить до следующего изменения плана.","backup.foreign_bookkeeping":"Служебные списки известных и новых устройств другого экземпляра импортированы не будут.","backup.final_name":"Название нового пространства","backup.target_settings":"Глобальные настройки целевого экземпляра не изменятся.","backup.duplicates":"Уже существующих привязок: {n}","backup.skip":"Пропустить повторяющиеся устройства","backup.virtual_copy":"Добавить безопасные статичные виртуальные копии","backup.content":"Связанное содержимое","backup.content_available":"доступно локально","backup.content_external":"внешняя ссылка","backup.content_detach_required":"будет отвязано","backup.confirm_detach":"Я понимаю, что недоступные внутренние планы и вложения будут отвязаны от импортируемой конфигурации.","backup.replace":"Заменить","backup.add":"Добавить пространство","backup.space_done":"Пространство импортировано: комнат — {rooms}, устройств — {markers}, существующих ссылок восстановлено — {refs}","backup.full_done":"Копия восстановлена: пространств — {spaces}, комнат — {rooms}, устройств — {markers}","backup.undo_import":"Отменить последний полный импорт","backup.import_undone":"Полный импорт отменён"}}],ra=oa;function la(e){return"string"==typeof e?e.trim().replaceAll("_","-").toLowerCase():""}const ca=new Map(aa.map(e=>[la(e.code),e]));function ha(e){return ca.get(la(e))}function da(e,t){const i=[{value:"",label:e},...aa.map(({code:e,nativeLabel:t})=>({value:e,label:t}))],s="string"==typeof t?t:"";return s&&!i.some(e=>e.value===s)&&i.push({value:s,label:ha(s)?.nativeLabel??s}),i}function pa(e,t){return function(e,t,i,s){const n=new Map(i.map(e=>[la(e),e])),o=n.get(la(e));if(o)return o;const a=la(t),r=n.get(a);return r||(n.get(a.split("-")[0]||"")??s)}(t,e?.locale?.language||e?.language,aa.map(({code:e})=>e),"en")}function ua(e,t,i){const s=ha(e)?.dictionary;return ss(s?.[t]??ra[t]??t,i)}function _a(e,t){const i=ha(e)?.dictionary[t]??ra[t];return"string"==typeof i&&i.trim().length>0}class ma extends he{constructor(){super(...arguments),this.deviceName="",this._metadataConnection=null,this._demoUntil=0,this._demoTimer=0,this._demoGeneration=1,this._demoKind=null,this._reducedMotion=!1,this._onMotionChange=e=>{this._reducedMotion=e.matches,this.requestUpdate()},this._startShortDemo=()=>{const e=this.presentation;e&&"icon_ripple"===e.display&&"none"===e.pulse.kind&&(this._demoGeneration++,this._demoKind="short",this._demoUntil=Date.now()+3300,window.clearTimeout(this._demoTimer),this._demoTimer=window.setTimeout(()=>{this._demoUntil=0,this._demoTimer=0,this._demoKind=null,this.requestUpdate()},3330),this.requestUpdate())},this._toggleContinuousDemo=()=>{const e=this.presentation;e&&"icon_ripple"===e.display&&"none"===e.pulse.kind&&("continuous"===this._demoKind?this._clearDemo():(this._clearDemo(),this._demoGeneration++,this._demoKind="continuous",this._demoUntil=Number.POSITIVE_INFINITY),this.requestUpdate())}}connectedCallback(){super.connectedCallback(),this._motionMedia=window.matchMedia?.("(prefers-reduced-motion: reduce)"),this._reducedMotion=!!this._motionMedia?.matches,this._motionMedia?.addEventListener?.("change",this._onMotionChange),this._ensureMetadata()}disconnectedCallback(){this._metadataRelease?.(),this._metadataRelease=void 0,this._metadataConnection=null,this._clearDemo(),this._motionMedia?.removeEventListener?.("change",this._onMotionChange),this._motionMedia=void 0,super.disconnectedCallback()}willUpdate(e){e.has("hass")&&this._ensureMetadata();const t=this.presentation,i=e.get("presentation");e.has("presentation")&&i?.binding!==t?.binding&&this._clearDemo(),t&&this._demoKind&&("icon_ripple"!==t.display||"none"!==t.pulse.kind)&&this._clearDemo()}_ensureMetadata(){const e=this.hass?.connection||null;e&&e!==this._metadataConnection&&(this._metadataRelease?.(),this._metadataConnection=e,this._metadataRelease=Qo(this.hass,()=>this.requestUpdate()))}_clearDemo(){window.clearTimeout(this._demoTimer),this._demoTimer=0,this._demoUntil=0,this._demoKind=null}get _lang(){return pa(this.hass)}_t(e,t){return ua(this._lang,e,t)}_providerText(e){return"houseplan"===e.domain?this._t("marker.preview.virtual_provider"):e.label.toLowerCase()===e.domain.toLowerCase()?e.domain:`${e.label} (${e.domain})`}_providers(){return this.presentation&&this.registry?function(e,t,i,s=ea(e)){if("virtual"===t)return[{domain:"houseplan",label:"House Plan",confidence:"registry-owner"}];const n=t.indexOf(":");if(n<1)return[];const o=t.slice(0,n),a=t.slice(n+1);if("entity"===o){const t=na(e,a,i,s);return t?[t]:[]}if("device"!==o)return[];const r=i.devices?.[a]||e?.devices?.[a];if(!r)return[];const l=[r.config_entry_id,...Array.isArray(r.config_entries)?r.config_entries:[]].filter(e=>"string"==typeof e&&!!e).map(t=>{const i=s.configEntries[t];return ia(e,i?.domain,"registry-owner",s,t)});if(l.some(Boolean))return sa(l);const c=[];for(const[t,n]of Object.entries(i.entities||{}))n?.device_id===a&&null==n?.disabled_by&&c.push(na(e,t,i,s));return c.some(Boolean)?sa(c):sa((Array.isArray(r.identifiers)?r.identifiers:[]).map(t=>{const i=Array.isArray(t)?t[0]:null;return ia(e,i,"identifier-fallback",s)}))}(this.hass,this.presentation.binding,this.registry,ea(this.hass)):[]}_providerSummary(e){if(!e.length)return this._t("marker.preview.unknown_provider");const t=e.slice(0,2).map(e=>this._providerText(e)),i=e.length-t.length;return t.join(", ")+(i>0?` · ${this._t("marker.preview.more_sources",{n:i})}`:"")}_sourceSummary(e){const t=e.visualSources;return t.length?1===t.length?`${t[0].name} · ${t[0].eid} · ${this._sourceProvider(t[0].eid)}`:2===t.length?t.map(e=>e.name).join(" · "):this._t("marker.preview.multiple_sources",{n:t.length}):this._t("marker.preview.no_source")}_stateSummary(e){if(!e.visualSources.length)return this._t("marker.preview.no_state");if(1===e.visualSources.length)return e.visualSources[0].stateText||this._t("marker.preview.no_state");const t=[...new Set(e.visualSources.map(e=>e.stateText).filter(Boolean))];return 1===t.length?t[0]:this._t("marker.preview.mixed_states")}_reason(e){return this._t(`marker.preview.reason.${e}`)}_sourceProvider(e){if(!this.registry)return this._t("marker.preview.unknown_provider");const t=na(this.hass,e,this.registry,ea(this.hass));return t?this._providerText(t):this._t("marker.preview.unknown_provider")}render(){const e=this.presentation;if(!e)return G;const t=!!this._demoKind&&this._demoUntil>Date.now()&&"icon_ripple"===e.display&&"none"===e.pulse.kind,i=t?(()=>{const t=function(e,t,i,s,n=null){return{kind:t,reason:"short"===t?"event":"running",generation:Math.max(1,Math.trunc(i)),expiresAt:"short"===t?n:null,color:e.color,diameterScale:s?1:e.diameterScale,animated:!s,reducedMotionIndicator:s?"dot":"none"}}(e.pulse,this._demoKind,this._demoGeneration,this._reducedMotion,"short"===this._demoKind?this._demoUntil:null);return{...e,pulse:t,classes:[...e.classes.filter(e=>!e.startsWith("pulse-")),`pulse-${t.kind}`,...t.generation%2==0?["pulse-gen2"]:[]]}})():e,s=this._providers(),n="none"!==e.pulse.kind,o=i.scale*(i.pulse.animated?i.pulse.diameterScale:1);let a=-o/2,r=o/2,l=-o/2,c=o/2;if(i.valueBadge){const e=i.scale*Math.min(4,Math.max(.9,.29*i.valueBadge.text.length+.28)),t=.7*i.scale,s=.6*i.scale;"right"===i.valueBadge.position&&(r=Math.max(r,s+e)),"left"===i.valueBadge.position&&(a=Math.min(a,-s-e)),"top"===i.valueBadge.position&&(l=Math.min(l,-s-t)),"bottom"===i.valueBadge.position&&(c=Math.max(c,s+t))}if(null!=i.lqiText){const e=i.scale*("bottom"===i.valueBadge?.position?1.55:.95);c=Math.max(c,e)}const h=Math.min(1,2.35/Math.max(1,r-a),2.35/Math.max(1,c-l)),d=Math.round(100*h),p=[`left:calc(50% - ${54*((a+r)/2)*h}px)`,`top:calc(50% - ${54*((l+c)/2)*h}px)`,h<1?`transform:scale(${h})`:""].filter(Boolean).join(";"),u=["dev",Uo(this.hass),...i.classes,"virtual"===i.binding?"virtual":"",i.haDisabled?"ghost ha-disabled":"",null!=i.valueText?"valonly":""].filter(Boolean).join(" "),_=Go(i).join(";"),m=t?this._t("continuous"===this._demoKind?"marker.preview.demo_continuous_notice":"marker.preview.demo_short_notice"):this._reason(e.explanation.reason),g=[this.deviceName,this._providerSummary(s),this._stateSummary(e),m,e.valueFullText||"",Io(e.valueBadge),null!=e.lqiText&&e.lqiBand?this._t(`marker.lqi_a11y_${e.lqiBand}`,{value:e.lqiText}):""].filter(Boolean).join(". ");return q`
${this._t("marker.preview.title")} @@ -4629,7 +4629,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="5b79a0695b7e36440d9e2ee2cdb8a68b4e3b data-day-cycle-layer=${t} style=${s}>
`})} - `}const Br={color:"#607d8b",opacity:1,widthCm:3.6,fill:!1,fillColor:"#607d8b",fillOpacity:.25},jr=(e,t=1)=>{const i=Number(e);return Number.isFinite(i)?Math.min(1,Math.max(0,i)):t},qr=e=>{let t=Number(e);return Number.isFinite(t)?(t=(t%360+360)%360,t>180?t-360:t):0};function Wr(e,t,i){return qr(t)?[e.x,e.y]:i([e.x,e.y])}const Ur=(e,t,i)=>{const s=Number(e),n=Number.isFinite(t)&&t>0?t:5;return Number.isFinite(s)&&s>0?s/n*i:0},Gr=(e,t,i)=>{const s=Number(e),n=Number.isFinite(t)&&t>0?t:5;return Number.isFinite(s)&&i>0?s/i*n:0},Vr=(e,t,i,s=Br.widthCm)=>{const n=Number(e?.width_cm);if(Number.isFinite(n)&&n>0)return n;const o=Number(e?.width);return Number.isFinite(o)&&o>0?Gr(o,t,i):s};function Kr(e,t){return{color:e.color,opacity:jr(e.opacity),width_cm:Math.max(.1,Math.min(100,Number(e.widthCm)||.1)),...t?{fill:e.fill,fill_color:e.fillColor,fill_opacity:jr(e.fillOpacity,.25)}:{}}}function Yr(e){const t=e.x+e.w/2,i=e.y+e.h/2,s=qr(e.angle)*Math.PI/180,n=Math.cos(s),o=Math.sin(s),a=(e,s)=>{const a=e-t,r=s-i;return[t+a*n-r*o,i+a*o+r*n]};return[a(e.x,e.y),a(e.x+e.w,e.y),a(e.x+e.w,e.y+e.h),a(e.x,e.y+e.h)]}function Jr(e){const t=Yr(e),i=(e,t)=>[(e[0]+t[0])/2,(e[1]+t[1])/2],s=[(t[0][0]+t[2][0])/2,(t[0][1]+t[2][1])/2];return{points:[...t,i(t[0],t[1]),i(t[1],t[2]),i(t[2],t[3]),i(t[3],t[0]),s],segments:t.map((e,i)=>({a:e,b:t[(i+1)%4]}))}}function Xr(e,t,i,s,n,o,a,r){const l=qr(e.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l),d=-Math.sin(l),p=Math.cos(l),u=e.x+e.w/2,_=e.y+e.h/2,m=t>0?-e.w/2:e.w/2,g=i>0?-e.h/2:e.h/2,f=u+m*c+g*d,v=_+m*h+g*p,b=s-f,y=n-v;let w=(b*c+y*h)*(t>0?1:-1),k=(b*d+y*p)*(i>0?1:-1),x=null;if(o){const t=w/Math.max(e.w,r),i=k/Math.max(e.h,r);x=Math.max(r/Math.max(e.w,r),r/Math.max(e.h,r),t,i),w=e.w*x,k=e.h*x}if(a>0)if(o){const t=Math.max(1,Math.round(e.w/a)),i=Math.max(1,Math.round(e.h/a));if(Math.abs(e.w-t*a)<1e-6&&Math.abs(e.h-i*a)<1e-6){const s=(e,t)=>{let i=Math.abs(e),s=Math.abs(t);for(;s;)[i,s]=[s,i%s];return Math.max(1,i)},n=1/s(t,i),o=Math.max(r/e.w,r/e.h),a=Math.max(o,Math.round((x??1)/n)*n);w=e.w*a,k=e.h*a}else{e.w>=e.h?(w=Math.round(w/a)*a,k=w*(e.h/Math.max(e.w,r))):(k=Math.round(k/a)*a,w=k*(e.w/Math.max(e.h,r)))}}else w=Math.round(w/a)*a,k=Math.round(k/a)*a;w=Math.max(r,w),k=Math.max(r,k);return{x:f+(t>0?w/2:-w/2)*c+(i>0?k/2:-k/2)*d-w/2,y:v+(t>0?w/2:-w/2)*h+(i>0?k/2:-k/2)*p-k/2,w:w,h:k,angle:qr(e.angle)||void 0}}const Zr=(e,t,i)=>{const s=i[0]-t[0],n=i[1]-t[1],o=s*s+n*n;if(o<1e-12)return[...t];const a=Math.max(0,Math.min(1,((e[0]-t[0])*s+(e[1]-t[1])*n)/o));return[t[0]+s*a,t[1]+n*a]};const Qr=10**9,el=240,tl=1e-4;function il(e){if(Array.isArray(e))return e.map(e=>il(e));if(null!==e&&"object"==typeof e){const t={};for(const[i,s]of Object.entries(e))t[i]=il(s);return t}return e}function sl(e){if("number"!=typeof e||!Number.isFinite(e))return e;const t=(e<0||Object.is(e,-0)?-1:1)*(Math.floor(Math.abs(e)*Qr+.5)/Qr);return 0===t?0:t}function nl(e){if("number"!=typeof e||!Number.isFinite(e))return e;const t=e*el,i=Math.round(t);if(Math.abs(t-i)null!==al(e)):[]}function ll(e,t){for(const i of t)Object.prototype.hasOwnProperty.call(e,i)&&(e[i]=sl(e[i]))}function cl(e,t){for(const i of t)Object.prototype.hasOwnProperty.call(e,i)&&(e[i]=nl(e[i]))}function hl(e){if(Array.isArray(e))for(let t=0;t{for(const n of s){const s=t[n];"number"==typeof s&&Number.isFinite(s)&&i(s,e)}},s=t=>{if(Array.isArray(t))for(let s=0;s{if(Array.isArray(e))for(const t of e)s(t)};for(const i of rl(e.rooms))t(i,["x","y","w","h"]),n(i.poly);for(const t of rl(e.walls))s(t.a),s(t.b);for(const t of rl(e.wall_segments))s(t.a),s(t.b);for(const i of rl(e.openings))t(i,["x","y"]);for(const i of rl(e.decor))"line"===i.kind?t(i,["x1","y1","x2","y2"]):"rect"===i.kind||"ellipse"===i.kind||"furniture"===i.kind?t(i,["x","y","w","h"]):"text"===i.kind&&t(i,["x","y"]);for(const t of rl(e.room_drafts))n(t.points);for(const t of rl(e.partitions))s(t.a),s(t.b);for(const t of rl(e.wall_columns))s(t.center);for(const t of rl(e.open_spans))s(t.a),s(t.b)}const n=al(t);if(!n)return;const o=new Map;if(s)for(const e of rl(s.spaces))null!=e.id&&o.set(String(e.id),e);for(const e of Object.values(n)){const t=al(e);if(!t)continue;const s=null!=t.s&&o.get(String(t.s))||null;for(const e of["x","y"]){const n=t[e];"number"==typeof n&&Number.isFinite(n)&&i(n,s)}}}function ul(e){const t=Number(e?.cell_cm);return t>0?t:5}function _l(e,t={}){const i={canonicalized:0,far:0,maxShift:0,maxShiftCm:0,spaces:[],bySpace:new Map},s=rl(al(e)?.spaces);let n=5;for(const e of s)n=Math.max(n,ul(e));pl(e,t,(e,t)=>{const s=e*el,o=Math.abs(s-Math.round(s)),a=nl(e),r=o>0&&o=tl))return;const l=r&&"number"==typeof a?Math.abs(a-e):0;if(r?i.canonicalized++:i.far++,i.maxShift=Math.max(i.maxShift,l),i.maxShiftCm=Math.max(i.maxShiftCm,l*el*(t?ul(t):n)),!t?.id||!r)return;const c=String(t.id);let h=i.bySpace.get(c);h||(h={spaceId:c,space:String(t.title||c),canonicalized:0,far:0,maxShift:0,maxShiftCm:0},i.bySpace.set(c,h)),h.canonicalized++,h.maxShift=Math.max(h.maxShift,l),h.maxShiftCm=Math.max(h.maxShiftCm,l*el*ul(t))}),i.bySpace.size&&pl(e,t,(e,t)=>{if(!t?.id)return;const s=i.bySpace.get(String(t.id));if(!s)return;const n=e*el;Math.abs(n-Math.round(n))>=tl&&s.far++}),i.spaces=[...i.bySpace.values()];const{bySpace:o,...a}=i;return a}function ml(e){return function(e){const t=e,i=al(t);i&&cl(i,["x","y"]);return t}(il(e))}function gl(e){const t=e,i=al(t);if(!i)return t;for(const e of Object.values(i)){const t=al(e);t&&cl(t,["x","y"])}return t}function fl(e){const t=e,i=al(t);if(!i)return t;for(const e of rl(i.spaces)){ll(e,["plan_x","plan_y","plan_scale","plan_scale_x","plan_scale_y","plan_angle"]);for(const t of rl(e.rooms))cl(t,["x","y","w","h"]),dl(t.poly);for(const t of rl(e.walls))hl(t.a),hl(t.b);for(const t of rl(e.wall_segments))hl(t.a),hl(t.b);for(const t of rl(e.openings)){cl(t,["x","y"]),ll(t,["angle","length"]);const e=al(t.host);e&&ll(e,["t"])}for(const t of rl(e.decor))"line"===t.kind?cl(t,["x1","y1","x2","y2"]):"rect"===t.kind||"ellipse"===t.kind||"furniture"===t.kind?(cl(t,["x","y","w","h"]),ll(t,["angle"])):"text"===t.kind&&(cl(t,["x","y"]),ll(t,["scale","angle"]));for(const t of rl(e.room_drafts))dl(t.points);for(const t of rl(e.partitions))hl(t.a),hl(t.b);for(const t of rl(e.wall_columns))hl(t.center),"square"===t.shape&&ll(t,["angle"]);for(const t of rl(e.open_spans))hl(t.a),hl(t.b)}for(const e of rl(i.markers))ll(e,["angle"]);return t}const vl=15;function bl(e,t,i,s,n){const o=i[0]-e[0],a=i[1]-e[1],r=Math.hypot(o,a);if(!(r>0)||r<=n+1e-9)return null;const l=o/r,c=a/r,h=t=>{const s=(t[0]-e[0])*l+(t[1]-e[1])*c,o=(n-s)/(r-s);return!Number.isFinite(o)||o<-1e-9||o>1+1e-9?null:[t[0]+(i[0]-t[0])*o,t[1]+(i[1]-t[1])*o]},d=h(t),p=h(s);return d&&p?[[e[0],e[1]],t,d,p,s]:null}const yl=Math.sin(.25*Math.PI/180);function wl(e,t){return Number.isFinite(e)&&e>0&&Number.isFinite(t)&&t>0&&e*t<3}function kl(e){const t=Number(e)>0?Number(e):5;if(5===t)return 8;const i=5/t*8;return Math.min(80,Math.max(.5,i))}function xl(e,t){return Number.isFinite(e)&&e>0&&Number.isFinite(t)&&t>0&&e*t<2}function $l(e){return Number.isFinite(e)?Math.max(1,Math.min(100,e)):1}function Sl(e,t){return!Number.isFinite(e)||e<0?"":String(t?Math.round(e/2.54*100)/100:Math.round(100*e)/100)}function Ml(e,t,i){if(!Number.isFinite(e)||e<=0)return 0;const s=Number(t)>0?Number(t):5;return $l(e)/s*i}function Cl(e,t){return t>0&&Number.isFinite(e)?Math.round(e/t)*t:e}function Dl(e){return Math.max(1e-6*Math.abs(e),1e-9)}function Pl(e,t){if(!(t>0&&Number.isFinite(e)))return e;const i=Cl(e,t);return Math.abs(i-e)<=Dl(t)?i:e}function Tl(e,t){let i=t[0]-e[0],s=t[1]-e[1];const n=Math.hypot(i,s);return n<1e-12?[1,0]:(i/=n,s/=n,(i<-1e-12||Math.abs(i)<=1e-12&&s<0)&&(i=-i,s=-s),[i,s])}function Il(e,t,i){const s=[Pl(e[0],i),Pl(e[1],i)],n=[Pl(t[0],i),Pl(t[1],i)],o=Cl((s[0]+n[0])/2,i),a=Cl((s[1]+n[1])/2,i),[r,l]=Tl(s,n);let c=Math.atan2(l,r);c<0&&(c+=Math.PI);const h=Math.round(1800*c)/1800,d=i>0&&i<.01?6:i<1?4:2;return`${o.toFixed(d)},${a.toFixed(d)}@${h.toFixed(4)}`}function Rl(e,t,i,s){return 1===s?Il(e,t,i):Il([e[0]/s,e[1]/s],[t[0]/s,t[1]/s],i)}function Al(e,t){if(!Array.isArray(e.a)||!Array.isArray(e.b)||e.a.length<2||e.b.length<2)return null;const i=[Number(e.a[0]),Number(e.a[1]),Number(e.b[0]),Number(e.b[1])];if(!i.every(Number.isFinite))return null;const s=t>0?t:1;return[[i[0]*s,i[1]*s],[i[2]*s,i[3]*s]]}function zl(e,t,i,s,n){const o=n>0?n:1;return{key:Rl(e,t,s,o),cm:$l(i),a:[e[0]/o,e[1]/o],b:[t[0]/o,t[1]/o]}}function Fl(e,t){const i=t>0?t:1,s=[];for(const t of e){const e=t.key.lastIndexOf("@");if(e<0)continue;const[n,o]=t.key.slice(0,e).split(",").map(Number),a=Number(t.key.slice(e+1));[n,o,a].every(Number.isFinite)&&s.push({w:t,x:n*i,y:o*i,ang:a})}return s}function Nl(e,t){const[i,s]=Tl(e,t);let n=Math.atan2(s,i);return n<0&&(n+=Math.PI),n}function El(e,t){let i=Math.abs(e-t);return i>Math.PI/2&&(i=Math.PI-i),i<.02}function Ol(e,t,i,s,n=1){if(!e?.length)return null;const o=Rl(t,i,s,n),a=e.find(e=>e.key===o);if(a)return a;const r=n>0?n:1,l=Dl(s)*r,c=(e,t)=>Math.abs(e[0]-t[0])<=l&&Math.abs(e[1]-t[1])<=l;for(const s of e){const e=Al(s,r);if(e&&(c(e[0],t)&&c(e[1],i)||c(e[0],i)&&c(e[1],t)))return s}const h=(t[0]+i[0])/2,d=(t[1]+i[1])/2,p=Nl(t,i),u=Math.max(.5*s,1e-9)*r;for(const t of Fl(e,r))if(El(t.ang,p)&&Math.hypot(t.x-h,t.y-d)<=u)return t.w;return null}function Hl(e,t,i,s,n=1){const o=Ol(e,t,i,s,n);if(o&&o.cm>0)return $l(o.cm);const a=function(e,t,i,s,n){if(!e?.length)return null;const o=n>0?n:1,a=Math.hypot(i[0]-t[0],i[1]-t[1]);if(a<1e-12)return null;const r=Nl(t,i),l=Math.max(.5*s,1e-9)*o;let c=null;for(const s of e){if(!(s.cm>0))continue;const e=Al(s,o);if(!e)continue;const n=Math.hypot(e[1][0]-e[0][0],e[1][1]-e[0][1]);if(n<1e-12||!El(Nl(e[0],e[1]),r))continue;if(Sc(t[0],t[1],e[0][0],e[0][1],e[1][0],e[1][1])>l||Sc(i[0],i[1],e[0][0],e[0][1],e[1][0],e[1][1])>l)continue;if(n+l(o.has(e.key)||(e=>{const t=Al(e,s);if(!t)return!1;const[i,o]=t,r=o[0]-i[0],c=o[1]-i[1],h=Math.hypot(r,c);if(h<=l)return!1;if(!a.some(e=>{const t=[e[0],e[1]],s=[e[2],e[3]];return El(Nl(i,o),Nl(t,s))&&Sc(i[0],i[1],t[0],t[1],s[0],s[1])<=l&&Sc(o[0],o[1],t[0],t[1],s[0],s[1])<=l}))return!1;const d=(n||[]).some(e=>{const t=[e[0],e[1]],s=[e[2],e[3]];if(!El(Nl(i,o),Nl(t,s)))return!1;const n=e=>Math.abs((e[0]-i[0])*c-(e[1]-i[1])*r)/h;if(n(t)>l||n(s)>l)return!1;const a=h*h,d=((t[0]-i[0])*r+(t[1]-i[1])*c)/a,p=((s[0]-i[0])*r+(s[1]-i[1])*c)/a;return Math.min(1,Math.max(d,p))-Math.max(0,Math.min(d,p))>l/h});return!d})(e))&&e.cm>=1&&e.cm<=100)}function Bl(e,t,i,s=8){const[n,o]=Tl(e,t);let a=Math.atan2(o,n);a<0&&(a+=Math.PI);let r=i*Math.PI/180%Math.PI;r<0&&(r+=Math.PI);let l=Math.abs(a-r);return l>Math.PI/2&&(l=Math.PI-l),l<=s*Math.PI/180}function jl(e,t,i,s,n=1,o="affine",a){if(!e?.length)return[];if(t.length!==i.length)return"fixed-topology"===o&&a?.(),e.slice();const r=n>0?n:1,l=Math.max(.5*s,1e-9)*r,c=Math.max(s*r*1e-6,1e-9),h=[],d=new Map,p=new Map,u=(e,t,i)=>{const s=e.get(t)||new Set;s.add(i),e.set(t,s)};for(let e=0;e[e[0]+(t[0]-e[0])*i,e[1]+(t[1]-e[1])*i],m=(e,t)=>Math.hypot(e[0]-t[0],e[1]-t[1])<=c,g=(e,t)=>{if("fixed-topology"===o){const i=t.na[0]-t.oa[0],s=t.na[1]-t.oa[1],n=t.nb[0]-t.ob[0],o=t.nb[1]-t.ob[1];return Math.hypot(i-n,s-o)<=c?[e[0]+i,e[1]+s]:m(e,t.oa)?[...t.na]:m(e,t.ob)?[...t.nb]:[...e]}const i=Math.max(0,Math.min(1,((e[0]-t.oa[0])*t.dx+(e[1]-t.oa[1])*t.dy)/t.len2));return _(t.na,t.nb,i)},f=(e,t)=>{const[i,s]=Tl(e,t);return(t[0]-e[0])*i+(t[1]-e[1])*s>=0?[[...e],[...t]]:[[...t],[...e]]},v=[],b=[],y=(e,t,i)=>{if(Math.hypot(t[0]-e[0],t[1]-e[1])<=c)return;const[n,o]=f(e,t),a=$l(i);if(b.some(e=>e.entry.cm===a&&m(e.span[0],n)&&m(e.span[1],o)))return;const l=zl(n,o,a,s,r);v.push(l),b.push({entry:l,span:[n,o]})};for(const t of e){const e=Al(t,r);if(e){const[i,s]=f(e[0],e[1]),n=s[0]-i[0],a=s[1]-i[1],r=n*n+a*a,d=Math.sqrt(r);if(d<=c){v.push({...t,cm:$l(t.cm)});continue}const p=[];for(const e of h){if(!El(Nl(i,s),Nl(e.oa,e.ob)))continue;const t=e=>Math.abs((e[0]-i[0])*a-(e[1]-i[1])*n)/d;if(t(e.oa)>l||t(e.ob)>l)continue;const o=((e.oa[0]-i[0])*n+(e.oa[1]-i[1])*a)/r,h=((e.ob[0]-i[0])*n+(e.ob[1]-i[1])*a)/r,u=Math.max(0,Math.min(o,h)),_=Math.min(1,Math.max(o,h));(_-u)*d>c&&p.push({lo:u,hi:_,move:e})}if(!p.length){"fixed-topology"===o?v.push({...t}):y(i,s,t.cm);continue}const u=[0,1,...p.flatMap(({lo:e,hi:t})=>[e,t])].sort((e,t)=>e-t).filter((e,t,i)=>0===t||Math.abs(e-i[t-1])*d>c),b=[];for(let e=0;e+1r>=e.lo-1e-12&&r<=e.hi+1e-12);if(!l.length){b.push([o,a]);continue}const h=[g(o,l[0].move),g(a,l[0].move)],f=l.slice(1).some(e=>{const t=g(o,e.move),i=g(a,e.move);return!m(h[0],t)||!m(h[1],i)});b.push(f?[o,a]:h)}const w=(e,t)=>{const i=e[1][0]-e[0][0],s=e[1][1]-e[0][1],n=t[1][0]-t[0][0],o=t[1][1]-t[0][1],a=Math.hypot(i,s);return!(a<=c||i*n+s*o<=0)&&Math.abs(i*o-s*n)/a<=c},k=[];for(const e of b){const t=k[k.length-1];t&&m(t[1],e[0])&&w(t,e)?t[1]=e[1]:k.push([[...e[0]],[...e[1]]])}if("fixed-topology"===o&&1===k.length){const[i,s]=k[0];if(m(i,e[0])&&m(s,e[1])||m(i,e[1])&&m(s,e[0])){v.push({...t});continue}}for(const[e,i]of k)y(e,i,t.cm);continue}if("fixed-topology"===o){const e=p.get(t.key);if(1===e?.size){const i=[...e][0];v.push(i===t.key?{...t}:{...t,key:i});continue}const i=[Fl([t],r)[0]];1!==r&&i.push(Fl([t],1)[0]);const s=i.filter(Boolean).some(e=>h.some(t=>{if(!El(e.ang,Nl(t.oa,t.ob)))return!1;const i=((e.x-t.oa[0])*t.dx+(e.y-t.oa[1])*t.dy)/t.len2;return i>=-1e-6&&i<=1.000001&&Sc(e.x,e.y,t.oa[0],t.oa[1],t.ob[0],t.ob[1])<=l}));((e?.size||0)>1||s)&&a?.(),v.push({...t});continue}let i="";const n=d.get(t.key);if(1===n?.size&&(i=[...n][0]),!i){const e=Fl([t],r)[0];if(e){const t=new Set;for(const i of h){if(!El(e.ang,Nl(i.oa,i.ob)))continue;const n=((e.x-i.oa[0])*i.dx+(e.y-i.oa[1])*i.dy)/i.len2;if(n<-1e-6||n>1.000001)continue;if(Sc(e.x,e.y,i.oa[0],i.oa[1],i.ob[0],i.ob[1])>l)continue;const o=g([e.x,e.y],i),[a,c]=Tl(i.na,i.nb),h=Math.max(s*r,1e-6);t.add(Rl([o[0]-a*h,o[1]-c*h],[o[0]+a*h,o[1]+c*h],s,r))}1===t.size&&(i=[...t][0])}}v.push({...t,key:i||t.key,cm:$l(t.cm)})}return v}function ql(e,t,i,s,n=1,o="affine"){return jl(e,t,i,s,n,o)}function Wl(e,t,i,s=1,n=[]){const o=s>0?s:1,a=Math.abs(i),r=Math.max(a*o*tl,1e-9),l=(n||[]).flatMap(e=>{const t=Al(e,o);return t?t.map(e=>[...e]):[]}),c=e=>e.every((e,t)=>(e=>{if(!(a>0))return!0;const t=e/o/a;return Math.abs(t-Math.round(t))Math.abs(e-i[t])<=r)),h=[],d=e=>JSON.stringify([e.key,e.cm,e.a,e.b]);for(const i of e||[]){const e=Al(i,o);if(!e)continue;const[s,n]=e;if(![s[0],s[1],n[0],n[1]].every(Number.isFinite)||!c(s)||!c(n)){h.push(d(i));continue}const a=n[0]-s[0],l=n[1]-s[1],p=Math.hypot(a,l);if(p<=r){h.push(d(i));continue}const u=a/p,_=l/p,m=[];for(const e of t){const[t,i]=e;if(![t?.[0],t?.[1],i?.[0],i?.[1]].every(Number.isFinite))continue;const n=e=>Math.abs((e[0]-s[0])*_-(e[1]-s[1])*u);if(n(t)>r||n(i)>r)continue;const o=(t[0]-s[0])*u+(t[1]-s[1])*_,a=(i[0]-s[0])*u+(i[1]-s[1])*_,l=Math.max(0,Math.min(o,a)),c=Math.min(p,Math.max(o,a));c-l>r&&m.push([l,c])}m.sort((e,t)=>e[0]-t[0]||e[1]-t[1]);let g=0;for(const[e,t]of m){if(e>g+r)break;if(g=Math.max(g,t),g>=p-r)break}ge.key!==a);return null==s||s<1?r:[...r,zl(t,i,s,n,o)]}function Gl(e,t,i,s,n,o=[],a=1){let r=e?e.slice():[];for(const e of function(e,t,i,s,n=1,o=[]){const a=dc(e,t,i,s,n,o);if(!a)return[];const r=[];for(let e=0;e0||(c=Ul(c,t,i,s,n,a)))}return c}function Kl(e){const{a:t,b:i,halfDepth:s}=e;if(!Array.isArray(t)||!Array.isArray(i)||t.length<2||i.length<2||![t[0],t[1],i[0],i[1]].every(Number.isFinite))return null;const n=i[0]-t[0],o=i[1]-t[1],a=Math.hypot(n,o);if(!(a>1e-9&&s>0&&Number.isFinite(s)))return null;const r=-o/a*s,l=n/a*s;return[[t[0]+r,t[1]+l],[i[0]+r,i[1]+l],[i[0]-r,i[1]-l],[t[0]-r,t[1]-l]]}function Yl(e,t,i){return Math.hypot(e[0]-t[0],e[1]-t[1])<=i}function Jl(e,t,i){const s=t.b[0]-t.a[0],n=t.b[1]-t.a[1],o=s*s+n*n;if(!(o>i*i))return!1;const a=((e[0]-t.a[0])*s+(e[1]-t.a[1])*n)/o;if(!(a>0&&a<1))return!1;const r=[t.a[0]+s*a,t.a[1]+n*a];return Math.hypot(e[0]-r[0],e[1]-r[1])<=i}function Xl(e,t,i,s){const n=Math.hypot(t,i);if(!(n>1e-9&&s>0))return;const o=[t/n,i/n],a=e.find(e=>Math.abs(e.u[0]*o[1]-e.u[1]*o[0])<1e-9&&e.u[0]*o[0]+e.u[1]*o[1]>1-1e-9);a?a.halfDepth=Math.max(a.halfDepth,s):e.push({u:o,halfDepth:s})}function Zl(e,t){try{const i=oi(Dc(e),Dc(t));let s=null,n=0;for(const e of i||[]){const t=e?.[0]||[],i=Math.abs(nc(t));t.length>=4&&i>n&&(n=i,s=t.slice(0,-1).map(e=>[e[0],e[1]]))}return s}catch{return null}}function Ql(e,t=1e-6){const i=(e||[]).map((e,t)=>({segment:e,index:t})).filter(({segment:e})=>e&&Array.isArray(e.a)&&Array.isArray(e.b)&&e.a.length>=2&&e.b.length>=2&&e.a.every(Number.isFinite)&&e.b.every(Number.isFinite)&&Number.isFinite(e.halfDepth)&&e.halfDepth>0&&Math.hypot(e.b[0]-e.a[0],e.b[1]-e.a[1])>1e-9);if(i.length<2)return[];const s=Math.max(Number.isFinite(t)?t:0,1e-9),n=i.flatMap(({segment:e})=>[e.a,e.b]).map(e=>[e[0],e[1]]).sort((e,t)=>e[0]-t[0]||e[1]-t[1]),o=[];for(const e of n)o.some(t=>Yl(t,e,s))||o.push(e);const a=[];for(const e of o){const t=[];let n=!1;for(const{segment:o,index:a}of i){const i=Math.hypot(o.b[0]-o.a[0],o.b[1]-o.a[1]);Yl(e,o.a,s)?t.push({u:[(o.b[0]-o.a[0])/i,(o.b[1]-o.a[1])/i],halfDepth:o.halfDepth,length:i,index:a}):Yl(e,o.b,s)?t.push({u:[(o.a[0]-o.b[0])/i,(o.a[1]-o.b[1])/i],halfDepth:o.halfDepth,length:i,index:a}):Jl(e,o,s)&&(n=!0)}if(n||2!==t.length)continue;const[o,r]=t,l=o.u[0]*r.u[1]-o.u[1]*r.u[0];if(Math.abs(l)<1e-9)continue;const c=l<0?1:-1,h=[-o.u[1],o.u[0]],d=[-r.u[1],r.u[0]],p=[e[0]+h[0]*o.halfDepth*c,e[1]+h[1]*o.halfDepth*c],u=[e[0]-d[0]*r.halfDepth*c,e[1]-d[1]*r.halfDepth*c];if(!rc(p,o.u,u,r.u))continue;const _=[[o,r,u,[d[0]*-c,d[1]*-c]],[r,o,p,[h[0]*c,h[1]*c]]];for(const[t,,i,n]of _){const o=Math.min(2*t.halfDepth,t.length),r=[-t.u[1]*t.halfDepth,t.u[0]*t.halfDepth],l=[[e[0]+r[0],e[1]+r[1]],[e[0]+t.u[0]*o+r[0],e[1]+t.u[1]*o+r[1]],[e[0]+t.u[0]*o-r[0],e[1]+t.u[1]*o-r[1]],[e[0]-r[0],e[1]-r[1]]],c=e=>(e[0]-i[0])*n[0]+(e[1]-i[1])*n[1],h=[];for(let e=0;e=-1e-12&&h.push(t),s>1e-12&&n<-1e-12||s<-1e-12&&n>1e-12){const e=s/(s-n);h.push([t[0]+(i[0]-t[0])*e,t[1]+(i[1]-t[1])*e])}}h.length>=3&&Math.abs(nc(h))>s*s&&a.push({segmentIndex:t.index,wedge:h})}}return a}function ec(e,t=1e-6){const i=(e||[]).filter(e=>e&&Array.isArray(e.a)&&Array.isArray(e.b)&&e.a.length>=2&&e.b.length>=2&&e.a.every(Number.isFinite)&&e.b.every(Number.isFinite)&&Number.isFinite(e.halfDepth)&&e.halfDepth>0&&Math.hypot(e.b[0]-e.a[0],e.b[1]-e.a[1])>1e-9);if(i.length<2)return[];const s=Math.max(Number.isFinite(t)?t:0,1e-9),n=i.flatMap(e=>[e.a,e.b]).map(e=>[e[0],e[1]]).sort((e,t)=>e[0]-t[0]||e[1]-t[1]),o=[];for(const e of n)o.some(t=>Yl(t,e,s))||o.push(e);const a=[],r=i.map((e,t)=>({roomId:"",a:[e.a[0],e.a[1]],b:[e.b[0],e.b[1]],key:`join-${t}`,kind:"outer",cm:0,open:!1,half:e.halfDepth})),l=gc(r,s);for(const e of fc(l).fans)a.push(e);const c=e=>!!vc(l,e);for(const e of o){if(c(e))continue;const t=[];for(const n of i)Yl(e,n.a,s)?Xl(t,n.b[0]-n.a[0],n.b[1]-n.a[1],n.halfDepth):Yl(e,n.b,s)?Xl(t,n.a[0]-n.b[0],n.a[1]-n.b[1],n.halfDepth):Jl(e,n,s)&&(Xl(t,n.a[0]-e[0],n.a[1]-e[1],n.halfDepth),Xl(t,n.b[0]-e[0],n.b[1]-e[1],n.halfDepth));if(!(t.length<2)){t.sort((e,t)=>Math.atan2(e.u[1],e.u[0])-Math.atan2(t.u[1],t.u[0])||e.halfDepth-t.halfDepth);for(let i=0;is*s&&a.push(m)}}}return a}function tc(e,t,i,s){if(!(t>0)||!e||e.length<2)return"";if(i&&e.length>=3){let i=e;const n=e[e.length-1];if(e.length>=4&&Math.hypot(e[0][0]-n[0],e[0][1]-n[1])<1e-9&&(i=e.slice(0,-1)),i.length>=3){const e=i.map((e,i)=>s?.[i]||t),n=Vc(i,e),o=lc(i,e);if(n&&o)return`${cc(n)} ${cc(hc(o))}`}}const n=[];for(let i=0;i=1e-9&&r>0&&n.push({a:o,b:a,halfDepth:r})}const o=n.map(Kl);for(const{segmentIndex:e,wedge:t}of Ql(n)){const i=o[e];if(!i)continue;const s=Zl(i,t);s&&(o[e]=s)}const a=[...o.filter(e=>!!e),...ec(n)],r=function(e){let t=null;try{for(const i of e){if(i.length<3)continue;const e=Dc(i);t=t?si(t,e):[e]}return t}catch{return null}}(a);return r?Lc(r):a.map(e=>cc(e)).join(" ")}function ic(e,t,i,s,n=1){if(!i.length)return!1;const o=sc(s,n),a=(e[0]+t[0])/2,r=(e[1]+t[1])/2,[l,c]=Tl(e,t);for(const e of i){const[t,i]=Tl([e[0],e[1]],[e[2],e[3]]);if(!(Math.abs(l*i-c*t)>.05)&&Sc(a,r,e[0],e[1],e[2],e[3])<=o)return!0}return!1}function sc(e,t){return Math.max(e*(t>0?t:1)*.04,1e-9)}function nc(e){let t=0;for(let i=0;ie[1]!=l>e[1]&&e[0]<(r-o)*(e[1]-a)/(l-a+0)+o&&(i=!i)}return i}([c[0]+.001*r,c[1]+.001*l],e)||(r=-r,l=-l),[r,l]}function ac(e,t){const i=e[0]*t[1]-e[1]*t[0],s=e[0]*t[0]+e[1]*t[1];return Math.abs(i)<1e-9&&s>0}function rc(e,t,i,s){const n=t[0]*s[1]-t[1]*s[0];if(Math.abs(n)<1e-12)return null;const o=[i[0]-e[0],i[1]-e[1]],a=(o[0]*s[1]-o[1]*s[0])/n;return[e[0]+a*t[0],e[1]+a*t[1]]}function lc(e,t,i){const s=e?.length||0;if(s<3||t.length!==s)return null;if(t.every(e=>!(e>0)))return e.map(e=>[e[0],e[1]]);const n=[];for(let o=0;o0||p>0)){n.push([e[o][0],e[o][1]]);continue}if(d>0!=p>0){const t=e[o],i=d>0?[t[0]+u*d,t[1]+_*d]:[t[0],t[1]],s=p>0?[t[0]+m*p,t[1]+g*p]:[t[0],t[1]];n.push(i),Math.hypot(s[0]-i[0],s[1]-i[1])>1e-9&&n.push(s);continue}if(ac(w,k)){const t=e[o],i=[t[0]+u*d,t[1]+_*d],s=[t[0]+m*p,t[1]+g*p];n.push(i),Math.hypot(s[0]-i[0],s[1]-i[1])>1e-9&&n.push(s);continue}const S=rc(x,w,$,k),M=Math.max(d,p,1e-9),C=vc(i,e[o])?.limit??4*M;if(S){const i=Math.hypot(S[0]-e[o][0],S[1]-e[o][1]);if(Number.isFinite(i)&&i<=C){n.push(S);continue}if(jc(e,t,o)){n.push(S);continue}}d>0&&n.push([e[o][0]+u*d,e[o][1]+_*d]),p>0&&n.push([e[o][0]+m*p,e[o][1]+g*p]),d>0||p>0||n.push([e[o][0],e[o][1]])}return n.length>=3?n:null}function cc(e,t=!0){if(!e.length)return"";let i=`M ${e[0][0]} ${e[0][1]}`;for(let t=1;te?.id===t),r=hi(a);if(!r||r.length<3)return null;const l=sc(s,n),c=[];for(const i of e||[]){if(!i||i.id===t)continue;const e=hi(i);if(e)for(const t of Cs(r,e,l))c.push([t[0],t[1]],[t[2],t[3]])}for(const e of i||[])c.push([e[0],e[1]],[e[2],e[3]]);for(const e of o||[]){const t=Al(e,n);t&&c.push(t[0],t[1])}const h=[],d=[];for(let e=0;el)continue;const a=((e[0]-t[0])*(i[0]-t[0])+(e[1]-t[1])*(i[1]-t[1]))/(s*s);a<=n||a>=1-n||(o.some(e=>Math.abs(e-a)*s<=2*l)||o.push(a))}o.sort((e,t)=>e-t);for(const s of o)h.push([t[0]+(i[0]-t[0])*s,t[1]+(i[1]-t[1])*s]),d.push(e)}return{poly:h,parent:d,orig:r}}function pc(e,t,i,s){const n=t.poly.length,o=new Array(n).fill(0);if(!e?.length)return o;const a=new Set,r=[];for(let l=0;l0?(o[l]=$l(c.cm),a.add(c.key)):r.push(l)}if(!r.length)return o;const l=s>0?s:1,c=Math.max(.5*i,1e-9)*l,h=Fl(e,l).filter(e=>e.w.cm>0);for(let e=r.length-1;e>=0;e--){const i=r[e],s=t.poly[i],a=t.poly[(i+1)%n],d=Nl(s,a);let p=null;for(const e of h){const t=Al(e.w,l);if(!t||!El(Nl(t[0],t[1]),d))continue;if(Sc(s[0],s[1],t[0][0],t[0][1],t[1][0],t[1][1])>c||Sc(a[0],a[1],t[0][0],t[0][1],t[1][0],t[1][1])>c)continue;const i=Math.hypot(a[0]-s[0],a[1]-s[1]),n=Math.hypot(t[1][0]-t[0][0],t[1][1]-t[0][1]),o=Math.max(0,n-i);(!p||oc||Sc(n[0],n[1],t[0][0],t[0][1],t[1][0],t[1][1])>c)continue;i=!0,o=Math.max(0,Math.hypot(t[1][0]-t[0][0],t[1][1]-t[0][1])-_)}else{if(Sc(e.x,e.y,s[0],s[1],n[0],n[1])>c)continue;o=Math.hypot(e.x-d,e.y-p)}(!u||i&&!u.exact||i===u.exact&&o0?i:1,o=(e||[]).filter(e=>e&&!e.open&&null!==e.kind&&Number.isFinite(e.half)&&e.half>0&&Array.isArray(e.a)&&Array.isArray(e.b)&&e.a.length>=2&&e.b.length>=2&&[e.a[0],e.a[1],e.b[0],e.b[1]].every(Number.isFinite)&&Math.hypot(e.b[0]-e.a[0],e.b[1]-e.a[1])>s).sort((e,t)=>e.key.localeCompare(t.key)||e.a[0]-t.a[0]||e.a[1]-t.a[1]||e.b[0]-t.b[0]||e.b[1]-t.b[1]||e.half-t.half),a=new Map;for(const e of o){const t=a.get(e.key);t?e.half>t.half&&a.set(e.key,{...t,half:e.half}):a.set(e.key,e)}const r=[...a.values()].flatMap(e=>[{point:[e.a[0],e.a[1]],other:e.b,halfDepth:e.half,kind:e.kind,key:e.key},{point:[e.b[0],e.b[1]],other:e.a,halfDepth:e.half,kind:e.kind,key:e.key}]).sort((e,t)=>e.point[0]-t.point[0]||e.point[1]-t.point[1]||e.other[0]-t.other[0]||e.other[1]-t.other[1]||e.halfDepth-t.halfDepth),l=new Map;for(const e of r){const[t,i]=uc(e.point,s),n=_c(t,i),o=l.get(n)||[];o.push(e),l.set(n,o)}const c=[],h=new Map;for(const e of r){const t=mc(h,e.point,s).filter(t=>Math.hypot(t.point[0]-e.point[0],t.point[1]-e.point[1])<=s).sort((t,i)=>Math.hypot(t.point[0]-e.point[0],t.point[1]-e.point[1])-Math.hypot(i.point[0]-e.point[0],i.point[1]-e.point[1])||t.point[0]-i.point[0]||t.point[1]-i.point[1]);let i=t[0];if(!i){i={point:[...e.point],rays:[]},c.push(i);const[t,n]=uc(i.point,s),o=_c(t,n),a=h.get(o)||[];a.push(i),h.set(o,a)}const n=e.other[0]-e.point[0],o=e.other[1]-e.point[1],a=Math.hypot(n,o);if(!(a>s))continue;const r=[n/a,o/a];let l=Math.atan2(r[1],r[0]);l<0&&(l+=2*Math.PI),i.rays.push({u:r,halfDepth:e.halfDepth,length:a,angle:l})}const d=[],p=e=>{const t=1e-9*Math.max(1,n),i=e.filter(e=>Number.isFinite(e.halfDepth)&&e.halfDepth>0&&Number.isFinite(e.length)&&e.length>s);return i.filter((e,s)=>!i.some((i,n)=>n!==s&&i.halfDepth>=e.halfDepth-t&&i.length>=e.length-t&&(i.halfDepth>e.halfDepth+t||i.length>e.length+t||ne.length-t.length||e.halfDepth-t.halfDepth).map(e=>({...e}))};for(const e of c){const t=e.rays.sort((e,t)=>e.angle-t.angle||e.length-t.length||e.halfDepth-t.halfDepth),i=[];for(const e of t){const t=i[i.length-1];t&&Math.abs(e.angle-t.angle)<=1e-9?t.supports.push({halfDepth:e.halfDepth,length:e.length}):i.push({u:[...e.u],angle:e.angle,supports:[{halfDepth:e.halfDepth,length:e.length}]})}if(i.length>1&&2*Math.PI-i[i.length-1].angle+i[0].angle<=1e-9){const e=i.pop();i[0].supports.push(...e.supports)}if(i.length<3)continue;const n=i.map(t=>{const i=p(t.supports),n=new Set,o=[];for(const a of i){const i=[e.point[0]+t.u[0]*a.length,e.point[1]+t.u[1]*a.length];for(const t of mc(l,i,s)){if("shared"!==t.kind||Math.hypot(t.point[0]-i[0],t.point[1]-i[1])>s)continue;const a=t.other[0]-t.point[0],r=t.other[1]-t.point[1],l=Math.hypot(a,r);if(!(l>s))continue;const c=a/l,h=r/l;if(Math.hypot(t.other[0]-e.point[0],t.other[1]-e.point[1])<=s)continue;const d=`${t.key}|${t.point[0]}|${t.point[1]}|${t.other[0]}|${t.other[1]}|${t.halfDepth}`;n.has(d)||(n.add(d),o.push({start:[t.point[0],t.point[1]],u:[c,h],length:l,halfDepth:t.halfDepth}))}}return o.sort((e,t)=>e.start[0]-t.start[0]||e.start[1]-t.start[1]||e.u[0]-t.u[0]||e.u[1]-t.u[1]||e.length-t.length||e.halfDepth-t.halfDepth),{u:[...t.u],halfDepth:Math.max(...i.map(e=>e.halfDepth)),length:Math.max(...i.map(e=>e.length)),supports:i,continuations:o}}).filter(e=>Number.isFinite(e.halfDepth)&&e.halfDepth>0&&Number.isFinite(e.length)&&e.length>s);if(n.length<3)continue;const o=Math.max(...n.map(e=>e.halfDepth));o>0&&Number.isFinite(o)&&d.push({point:[...e.point],rays:n,halfDepth:o,limit:1.25*o})}d.sort((e,t)=>e.point[0]-t.point[0]||e.point[1]-t.point[1]);const u=new Map;for(const e of d){const[t,i]=uc(e.point,s),n=_c(t,i),o=u.get(n)||[];o.push(e),u.set(n,o)}return{epsilon:s,coordinateScale:n,nodes:d,index:u}}function fc(e){const t={fans:[],supports:[]};if(!e?.nodes?.length)return t;const i=Math.max(e.epsilon,1e-9)**2;for(const s of e.nodes){const e=s.rays.filter(e=>Number.isFinite(e.halfDepth)&&e.halfDepth>0).map(e=>({...e,thickLength:Math.max(...e.supports.filter(t=>t.halfDepth>=e.halfDepth-1e-12).map(e=>e.length),0),angle:(()=>{const t=Math.atan2(e.u[1],e.u[0]);return t<0?t+2*Math.PI:t})()})).sort((e,t)=>e.angle-t.angle);if(e.length<2)continue;const n=s.point;for(const i of e)for(const e of i.supports){if(!(e.halfDepth>0&&e.length>0))continue;const s=-i.u[1]*e.halfDepth,o=i.u[0]*e.halfDepth,a=[n[0]+i.u[0]*e.length,n[1]+i.u[1]*e.length];t.supports.push([[n[0]+s,n[1]+o],[a[0]+s,a[1]+o],[a[0]-s,a[1]-o],[n[0]-s,n[1]-o]])}for(let s=0;s{const e=a.angle-o.angle;return e>0?e:e+2*Math.PI})();if(r<1e-9)continue;const l=r>Math.PI+1e-9,c=4*Math.max(o.halfDepth,a.halfDepth),h=[n[0]-o.u[1]*o.halfDepth,n[1]+o.u[0]*o.halfDepth],d=[n[0]+a.u[1]*a.halfDepth,n[1]-a.u[0]*a.halfDepth],p=o.u[0]*a.u[1]-o.u[1]*a.u[0],u=e=>{let t=Math.atan2(e[1]-n[1],e[0]-n[0])-o.angle;for(;t<0;)t+=2*Math.PI;return t<=r+1e-9};let _=null;if(Math.abs(p)>1e-9){const e=((d[0]-h[0])*a.u[1]-(d[1]-h[1])*a.u[0])/p,t=((d[0]-h[0])*o.u[1]-(d[1]-h[1])*o.u[0])/p,i=[h[0]+o.u[0]*e,h[1]+o.u[1]*e];(l?e<=1e-9&&t<=1e-9:e>1e-9&&e<=o.thickLength&&t<=a.thickLength)&&Math.hypot(i[0]-n[0],i[1]-n[1])<=c&&u(i)&&(_=i)}const m=e=>{Math.abs(nc(e))>i&&t.fans.push(e)};if(_){const e=1.5*Math.max(o.halfDepth,a.halfDepth);m(bl([n[0],n[1]],h,_,d,e)??[[n[0],n[1]],h,_,d]);continue}if(l){m([[n[0],n[1]],h,d]);continue}const g=(e,t)=>Math.min(t,Math.sqrt(Math.max(c**2-e**2,0)),2*Math.max(o.halfDepth,a.halfDepth)),f=[h[0]+o.u[0]*g(o.halfDepth,o.thickLength),h[1]+o.u[1]*g(o.halfDepth,o.thickLength)],v=[d[0]+a.u[0]*g(a.halfDepth,a.thickLength),d[1]+a.u[1]*g(a.halfDepth,a.thickLength)];m([[n[0],n[1]],h,f,v,d])}}return t}function vc(e,t){return!e||!Array.isArray(t)||t.length<2||!t.slice(0,2).every(Number.isFinite)?null:mc(e.index,t,e.epsilon).filter(i=>Math.hypot(i.point[0]-t[0],i.point[1]-t[1])<=e.epsilon).sort((e,i)=>Math.hypot(e.point[0]-t[0],e.point[1]-t[1])-Math.hypot(i.point[0]-t[0],i.point[1]-t[1])||e.point[0]-i.point[0]||e.point[1]-i.point[1])[0]||null}function bc(e,t,i,s,n,o,a){return gc(wc(e,t,i,s,n,o,a),4*sc(s,a),a)}function yc(e,t,i,s,n,o,a,r=1){const l=dc(e,t,s,n,r,i);if(!l)return null;const c=function(e,t,i){const s=(e||[]).find(e=>e?.id===t),n=hi(s);if(!n)return[];const o=[];for(const s of e||[]){if(!s||s.id===t)continue;const e=hi(s);if(e)for(const t of Cs(n,e,i))o.push(t)}return o}(e,t,sc(n,r)),h=function(e,t,i,s,n){const o=sc(s,n),a=[];for(let r=0;rSc(h,d,e[0],e[1],e[2],e[3])<=o);a.push(p?"shared":"outer")}return a}(l.poly,c,s,n,r),d=pc(i,l,n,r),p=d.map((e,t)=>h[t]&&e>0?Ml(e,o,a)/2:0);return{...l,kinds:h,cms:d,offsets:p}}function wc(e,t,i,s,n,o,a=1){const r=[];for(const l of e||[]){if(!l?.id)continue;const c=yc(e,l.id,t,i,s,n,o,a);if(c)for(let e=0;e0&&(r=Ul(r,e.a,e.b,e.cm,s,a));return r}function xc(e,t,i,s,n,o,a=1){if(!t?.length)return[];const r=wc(e,t,i,s,n,o,a),l=new Map;for(const e of r){if(e.open||!e.kind||!e.roomId)continue;const t=l.get(e.key)||new Set;t.add(e.roomId),l.set(e.key,t)}const c=e=>{const t=[...l.get(e)||[]].sort();return 1!==t.length&&2!==t.length?`ambiguous:${e}`:`${1===t.length?"outer":"shared"}:${t.join("|")}`},h=[],d=new Set;for(const e of r)!e.open&&e.cm>0&&!d.has(e.key)&&(d.add(e.key),h.push({...e,ownerSignature:c(e.key)}));const p=[];for(const r of e||[]){if(!r?.id)continue;const l=yc(e,r.id,t,i,s,n,o,a);if(l)for(let e=0;e0)||null===l.kinds[i]){e++;continue}const o=c(Rl(l.poly[i],l.poly[(i+1)%l.poly.length],s,a));let r=e;for(;r+10&&p.push({a:[d[0],d[1]],b:[u[0],u[1]],key:Rl(d,u,s,a),cm:n,len:_,ownerSignature:o}),e=r+1}}}p.sort((e,t)=>t.len-e.len||e.key.localeCompare(t.key));const u=[],_=new Set,m=new Set,g=4*sc(s,a);for(const e of p){const t=h.filter(t=>!m.has(t.key)&&t.cm===e.cm&&t.ownerSignature===e.ownerSignature&&El(Nl(t.a,t.b),Nl(e.a,e.b))&&Sc(t.a[0],t.a[1],e.a[0],e.a[1],e.b[0],e.b[1])<=g&&Sc(t.b[0],t.b[1],e.a[0],e.a[1],e.b[0],e.b[1])<=g);if(t.length){for(const e of t)m.add(e.key);_.has(e.key)||(_.add(e.key),u.push(zl(e.a,e.b,e.cm,s,a)))}}for(const e of h)m.has(e.key)||_.has(e.key)||(_.add(e.key),u.push(zl(e.a,e.b,e.cm,s,a)));return u}function $c(e,t,i,s,n,o,a,r=1){const l=sc(n,r),c=(s[0]+s[2])/2,h=(s[1]+s[3])/2,d=Nl([s[0],s[1]],[s[2],s[3]]);let p=null;for(const s of wc(e,t,i,n,o,a,r)){if(!El(Nl(s.a,s.b),d))continue;const e=Sc(c,h,s.a[0],s.a[1],s.b[0],s.b[1]);e>4*l||(!p||e[e[0],e[1]]),o=Math.abs(nc(n));n.length>=3&&o>i&&(t=n,i=o)}return t}function Cc(e,t,i,s,n,o,a,r=1,l,c){const h=(e||[]).find(e=>e?.id===t),d=hi(h);if(!d||d.length<3)return null;if(!i?.length)return d.map(e=>[e[0],e[1]]);const p=yc(e,t,i,s,n,o,a,r);if(!p||!p.offsets.some(e=>e>0))return d.map(e=>[e[0],e[1]]);const u=c||bc(e,i,s,n,o,a,r),_=lc(p.poly,p.offsets,u);if(!_)return d.map(e=>[e[0],e[1]]);if(!u.nodes.length)return _;const m=void 0===l?qc(e,i,s,[],n,o,a,r):null,g=l??("ok"===m?.status||"degraded-extra"===m?.status?m.roomGeom:void 0);if(g)try{const e=Mc(oi(Dc(p.poly),g));if(e)return e}catch{}return function(e,t){try{return Mc(ni(Dc(e),Dc(t)))}catch{return null}}(_,p.poly)||d.map(e=>[e[0],e[1]])}function Dc(e){const t=e.map(e=>[e[0],e[1]]);return t.push([e[0][0],e[0][1]]),[t]}function Pc(e){return!(!Array.isArray(e)||!e.length)&&e.every(e=>Array.isArray(e)&&e.length&&e.every(e=>Array.isArray(e)&&e.length>=4&&e.every(e=>Array.isArray(e)&&e.length>=2&&Number.isFinite(e[0])&&Number.isFinite(e[1])))&&Math.abs(Ti(e[0]))>1e-9)}function Tc(e,t,i,s){const n=s?new Set(s):null;let o=null;for(let s=0;st.epsilon))continue;const l=Fc([[e.point[0]+r[0]*s.halfDepth,e.point[1]+r[1]*s.halfDepth],[e.point[0]+a.u[0]*n+r[0]*s.halfDepth,e.point[1]+a.u[1]*n+r[1]*s.halfDepth],[e.point[0]+a.u[0]*n-r[0]*s.halfDepth,e.point[1]+a.u[1]*n-r[1]*s.halfDepth],[e.point[0]-r[0]*s.halfDepth,e.point[1]-r[1]*s.halfDepth]],t.coordinateScale);if(!l)continue;const c=Dc(l);o=o?si(o,c):c}}return o}function Ic(e,t,i){let s=null;for(const n of e.rays)for(const e of n.continuations){const n=[-e.u[1],e.u[0]],o=[e.start[0]+e.u[0]*e.length,e.start[1]+e.u[1]*e.length],a=Fc([[e.start[0]+n[0]*e.halfDepth,e.start[1]+n[1]*e.halfDepth],[o[0]+n[0]*e.halfDepth,o[1]+n[1]*e.halfDepth],[o[0]-n[0]*e.halfDepth,o[1]-n[1]*e.halfDepth],[e.start[0]-n[0]*e.halfDepth,e.start[1]-n[1]*e.halfDepth]],t.coordinateScale);if(!a)continue;const r=ni(Dc(a),i);Array.isArray(r)&&0!==r.length&&(s=s?si(s,r):r)}return s}function Rc(e,t,i=2*(4*e.halfDepth+2*t.epsilon)){const s=function(e,t=yl){const i=new Set,s=Number.isFinite(t)&&t>=0?t:yl;for(let t=0;te-t)}(e);return s.length?Tc(e,t,i,s):null}function Ac(e,t,i,s,n){const o=function(e){let t=null;for(const i of e){const e=Dc(i);try{t=t?si(t,e):e}catch{}}return t}(function(e,t,i=!1){if(!e)return[];const s=[];for(const n of e.nodes)for(let o=0;o1e-9)||h>=Math.PI-1e-9)continue;const d=[-a.u[1],a.u[0]],p=[-r.u[1],r.u[0]],u=[n.point[0]+d[0]*a.halfDepth,n.point[1]+d[1]*a.halfDepth],_=[n.point[0]-p[0]*r.halfDepth,n.point[1]-p[1]*r.halfDepth],m=rc(u,a.u,_,r.u);if(!m)continue;const g=Math.hypot(m[0]-n.point[0],m[1]-n.point[1]);if(!Number.isFinite(g)||g<=n.limit)continue;const f=t?Math.sqrt(Math.max(0,n.limit*n.limit-a.halfDepth*a.halfDepth)):0,v=t?Math.sqrt(Math.max(0,n.limit*n.limit-r.halfDepth*r.halfDepth)):0,b=Fc([[u[0]+a.u[0]*f,u[1]+a.u[1]*f],[_[0]+r.u[0]*v,_[1]+r.u[1]*v],m],e.coordinateScale);if(b&&s.push(b),i){const t=m[0]-n.point[0],i=m[1]-n.point[1],o=Math.hypot(t,i),a=g-n.limit;if(o>e.epsilon&&a>e.epsilon){const r=Math.min(Math.max(8*e.epsilon,.05*n.halfDepth),.25*a),l=t/o,c=i/o,h=-c,d=l,p=Fc([[m[0]-l*r+h*r,m[1]-c*r+d*r],[m[0]-l*r-h*r,m[1]-c*r-d*r],[m[0]+l*r,m[1]+c*r]],e.coordinateScale);p&&s.push(p)}}}return s}({...t,nodes:[e]},i,s));return o&&n?oi(o,n):o}function zc(e,t,i,s){if(!e||!t.nodes.length)return e;let n=null;try{n=function(e){let t=null;for(const i of e.nodes){const s=Rc(i,e);s&&(t=t?si(t,s):s)}return t}(t)}catch{return e}let o=e;for(const e of t.nodes){const a=4*e.halfDepth+2*t.epsilon,r=2*a,l=[[e.point[0]-a,e.point[1]-a],[e.point[0]+a,e.point[1]-a],[e.point[0]+a,e.point[1]+a],[e.point[0]-a,e.point[1]+a]];try{let a=o;const c=Ac(e,t,!1,!0,n);c&&(a=oi(a,c));let h=Tc(e,t,r);const d=Ac(e,t,!0,!0,n);d&&(h=oi(h,d)),n&&(h=si(h,n));const p=.02*Math.min(...e.rays.map(e=>e.halfDepth));if(h=si(h,Dc([[e.point[0]-p,e.point[1]-p],[e.point[0]+p,e.point[1]-p],[e.point[0]+p,e.point[1]+p],[e.point[0]-p,e.point[1]+p]])),!h)continue;let u=ni(h,Dc(l));s?u=ni(u,s):i&&(u=ni(u,i));const _=Dc(l),m=oi(a,_),g=i?oi(ni(a,_),i):null,f=Ic(e,t,_);o=si(m,...g?[g]:[],...f?[f]:[],u)}catch{}}if(n)try{let e=n;s?e=ni(e,s):i&&(e=ni(e,i)),o=si(o,e)}catch{}return o}function Fc(e,t=1){if(!Array.isArray(e)||e.length<3)return null;const i=Number.isFinite(t)&&t>0?t:1,s=1e-12*Math.max(1,i),n=[];for(const t of e){if(!Array.isArray(t)||t.length<2)return null;const e=Number(t[0]),i=Number(t[1]);if(!Number.isFinite(e)||!Number.isFinite(i))return null;const o=Math.round(e/s)*s,a=Math.round(i/s)*s;if(!Number.isFinite(o)||!Number.isFinite(a))return null;n.push([Object.is(o,-0)?0:o,Object.is(a,-0)?0:a])}return Math.abs(nc(n))>s*s?n:null}function Nc(e,t,i,s){if(Sc(e[0],e[1],t[0],t[1],i[0],i[1])>s)return!1;const n=i[0]-t[0],o=i[1]-t[1],a=(e[0]-t[0])*n+(e[1]-t[1])*o,r=n*n+o*o,l=s*Math.sqrt(r);return a>=-l&&a<=r+l}function Ec(e,t,i){const s=[],n=[];for(let o=0;oi*i))continue;const d=i/Math.sqrt(h),p=[0,1];for(const e of t)for(const t of[e.a,e.b]){if(!Nc(t,a,r,i))continue;const e=((t[0]-a[0])*l+(t[1]-a[1])*c)/h;e>d&&e<1-d&&p.push(e)}p.sort((e,t)=>e-t);const u=p.filter((e,t)=>0===t||Math.abs(e-p[t-1])>d);for(let e=0;e=3&&n.length===s.length?{poly:s,offsets:n}:null}function Oc(e,t,i,s,n,o,a,r){const l=(e||[]).map(hi).filter(e=>!!e&&e.length>=3);if(!l.length)return null;let c=si(Dc(l[0]));for(let e=1;e"outer"===e.kind&&e.half>0),p=4*sc(s,a),u=r||gc(h,p,a);let _=null;for(const e of function(e){const t=[];for(const i of Array.isArray(e)?e:[])if(Array.isArray(i))for(const e of i){if(!Array.isArray(e)||e.length<4)continue;const i=e.slice(0,-1).map(e=>[e[0],e[1]]);i.length>=3&&t.push(i)}return t}(c)){const t=Ec(e,d,p);if(!t||!t.offsets.some(e=>e>0))continue;const i=Vc(t.poly,t.offsets,u),s=lc(t.poly,t.offsets,u);if(!i||!s)continue;const n=oi(Dc(i),Dc(s));_=_?si(_,n):n}return{centre:c,shell:_||[]}}function Hc(e,t,i,s,n,o,a=1){try{const r=Oc(e,t,i,s,n,o,a,bc(e,t,i,s,n,o,a));if(!r)return[];return r.shell?.length?si(r.centre,r.shell):r.centre}catch{return null}}function Lc(e){if(!e)return"";let t="";for(const i of e)if(Array.isArray(i))for(const e of i){if(!Array.isArray(e)||e.length<4)continue;const i=e.slice(0,e.length-1);i.length<3||(t+=(t?" ":"")+cc(i.map(e=>[e[0],e[1]])))}return t}const Bc=15;function jc(e,t,i){const s=e?.length||0;if(s<3||t?.length!==s)return!1;const n=e[(i-1+s)%s],o=e[i],a=e[(i+1)%s],r=[n[0]-o[0],n[1]-o[1]],l=[a[0]-o[0],a[1]-o[1]],c=Math.hypot(r[0],r[1]),h=Math.hypot(l[0],l[1]);if(!(c>1e-9&&h>1e-9))return!1;const d=Math.max(-1,Math.min(1,(r[0]*l[0]+r[1]*l[1])/(c*h))),p=Math.acos(d);if(!(p>1e-9))return!1;if(p>=Bc*Math.PI/180)return!1;const u=Math.max(Math.max(0,t[(i-1+s)%s]),Math.max(0,t[i]));if(!(u>0))return!1;const _=u/Math.tan(p/2);return Number.isFinite(_)&&_>0&&_e>0))continue;for(const e of l.offsets)e>0&&(p=Math.max(p,2*e));const c=Vc(l.poly,l.offsets,d),u=lc(l.poly,l.offsets,d);c&&h.push({outset:c,inset:u})}for(const e of l){const t=e.map(e=>e[0]),i=e.map(e=>e[1]);if(t.length){const s=Math.min(Math.max(...t)-Math.min(...t),Math.max(...i)-Math.min(...i)),n=Math.min(...e.map((t,i)=>{const s=e[(i+1)%e.length];return Math.hypot(s[0]-t[0],s[1]-t[1])}));p=Math.max(p,e.length>16?s:n)}}const u=function(e,t,i,s,n,o,a,r){if(!t?.length||!i?.length)return[];const l=4*sc(s,a),c=new Map;for(const r of wc(e,t,i,s,n,o,a))!r.open&&r.half>0&&!c.has(r.key)&&c.set(r.key,r);const h=[...c.values()];if(h.length<2)return[];const d=r||gc(h,l),p=[];for(const e of i)for(const t of[[e[0],e[1]],[e[2],e[3]]])p.some(e=>Math.hypot(e[0]-t[0],e[1]-t[1])<=l)||p.push(t);const u=[],_=(e,t)=>{let i=0,s=0;if(Math.hypot(e.a[0]-t[0],e.a[1]-t[1])<=l)i=e.b[0]-e.a[0],s=e.b[1]-e.a[1];else{if(!(Math.hypot(e.b[0]-t[0],e.b[1]-t[1])<=l))return null;i=e.a[0]-e.b[0],s=e.a[1]-e.b[1]}const n=Math.hypot(i,s);return n>l?[i/n,s/n]:null};for(const e of p){const t=h.map(t=>({iv:t,u:_(t,e)})).filter(e=>!!e.u);for(let i=0;i0?[e.slice(),p,m,_]:[e.slice(),_,m,p];else{if(!f)continue;{const t=[-n.u[1],n.u[0]],i=[-o.u[1],o.u[0]],s=a<0?1:-1,r=[e[0]+t[0]*n.iv.half*s,e[1]+t[1]*n.iv.half*s],l=[e[0]-i[0]*o.iv.half*s,e[1]-i[1]*o.iv.half*s];b=a>0?[e.slice(),r,l]:[e.slice(),l,r]}}Math.abs(nc(b))>l*l&&u.push(b)}}return u}(e,t,i,n,o,a,r,d),_=s.length?Kc(e,t,i,n,o,a,r):null;let m="exterior";try{const v=Oc(e,t,i,n,o,a,r,d);m="paper";const b=v?v.shell?.length?si(v.centre,v.shell):v.centre:[],y=e=>{const t=Dc(e.outset);if(!e.inset)return t;const i=Dc(e.inset);return oi(t,i)};m="room-rings";let w=null;for(const e of h)try{const t=y(e);w=w?si(w,t):t}catch{}if(m="edge-bodies",v)for(const s of Gc(e,t,i,n,o,a,r))try{const e=ni(Dc(s.quad),v.centre);w=w?si(w,e):e}catch{}m="junctions",w=function(e,t,i=1,s=si){let n=e;for(const e of t||[]){const t=Fc(e,i);if(t)try{const e=Dc(t);n=n?s(n,e):e}catch{}}return n}(w,u,r),m="facade-clip",w&&v&&(w=ni(w,v.centre)),m="exterior-shell";const k=[];let x=0;if(v?.shell?.length)if(w)try{const e=si(w,v.shell);if(!Pc(e))throw new Error("invalid shell union");w=e}catch{if(!Pc(w)||!Pc(v.shell))throw new Error("invalid shell");k.push({id:"exterior-shell",geom:v.shell}),x++}else w=v.shell;if(m="multi-wall-trim",w&&d.nodes.length){const e=e=>e.rays.some(e=>e.supports.some(e=>e.length<2*e.halfDepth)),t=d.nodes.filter(e);if(t.length){w=zc(w,{...d,nodes:t},v?.centre,b)}}if(m="junction-corners",d.nodes.length){const s=fc(d),l=function(e,t,i,s,n,o,a,r){try{const l=Oc(e,t,i,s,n,o,a,{epsilon:r.epsilon,coordinateScale:r.coordinateScale,nodes:[],index:new Map});return l?l.shell?.length?si(l.centre,l.shell):l.centre:null}catch{return null}}(e,t,i,n,o,a,r,d);for(const e of s.fans)try{let t=[Dc(e)];if(l&&(t=ni(t,l)),!t?.length)continue;w=w?si(w,t):t}catch{}g=w,f=Math.max(d.epsilon,1e-9)**2,w=Array.isArray(g)?g.map(e=>{if(!Array.isArray(e)||!e.length)return e;const[t,...i]=e;return Math.abs(nc(t||[]))<=f?null:[t,...i.filter(e=>Math.abs(nc(e||[]))>f)]}).filter(e=>!!e):g}const $=w||[],S=[...Pc($)?[{id:"room-primary",geom:$}]:[],...k.map((e,t)=>({id:`room-isolated-${t}`,geom:e.geom}))];m="openings";for(const e of s){if(!(e.length>0))continue;const t=Xc(_,e,!0);if(!t.negative&&!t.positive)continue;const i=e.angle*Math.PI/180,s=Math.cos(i),o=Math.sin(i),a=-o,l=s,c=e.length/2,h=1.25*Math.max(p,n*r),d=[[e.x-s*c-a*h,e.y-o*c-l*h],[e.x+s*c-a*h,e.y+o*c-l*h],[e.x+s*c+a*h,e.y+o*c+l*h],[e.x-s*c+a*h,e.y-o*c+l*h]];w&&(w=oi(w,Dc(d)));for(const e of k)e.geom=oi(e.geom,Dc(d))}m="extras";const M=[];let C=0;const D=c.mergeExtra||((e,t)=>e?si(e,t):t);for(let e=0;ee.length>=2&&Number.isFinite(e[0])&&Number.isFinite(e[1]))||Math.abs(Ti(t))<=1e-9){C++;continue}const i=[Dc(t)];try{const t=D(w,i,e);if(!Pc(t))throw new Error("invalid extra union");w=t}catch{C++,Pc(i)&&M.push({id:`extra-${e}`,geom:i})}}M.push(...k),M.sort((e,t)=>Lc(e.geom).localeCompare(Lc(t.geom)));const P=w||[];return{status:C||x?"degraded-extra":"ok",geom:P,components:[...Pc(P)?[{id:"primary",geom:P}]:[],...M.map((e,t)=>({...e,id:`isolated-${t}`}))],roomGeom:$,roomComponents:S,paperGeom:b,depthUnits:p,openingPadUnits:1.25*Math.max(p,n*r),openingIndex:_,multiWallNodes:d,degradedExtraCount:C+x}}catch{return c.onCoreFailure?.(m),{status:"failed-core",geom:[],components:[],roomGeom:[],paperGeom:[],roomComponents:[],depthUnits:p,openingIndex:null,multiWallNodes:d,degradedExtraCount:0}}var g,f}function Wc(e,t,i,s=[],n,o,a,r=1,l=[],c={}){if(!t?.length&&!l.length)return null;return Uc(qc(e,t,i,s,n,o,a,r,l,c))}function Uc(e){if("failed-core"===e.status||"not-applicable"===e.status)return null;const t=e.components.map(e=>({id:e.id,d:Lc(e.geom),fillRule:"evenodd"})).filter(e=>!!e.d),i=t[0]?.d||"",s=Lc(e.paperGeom);if(t.length){const n={status:e.status,d:i,paths:t,components:e.components,roomGeom:e.roomGeom,multiWallNodes:e.multiWallNodes,paperD:s,depthUnits:e.depthUnits,fillRule:"evenodd"};return Object.defineProperties(n,{roomComponents:{value:e.roomComponents||[],enumerable:!1},openingIndex:{value:e.openingIndex,enumerable:!1},openingPadUnits:{value:e.openingPadUnits,enumerable:!1}}),n}return null}function Gc(e,t,i,s,n,o,a=1){if(!t?.length)return[];const r=new Set,l=[];for(const c of e||[]){if(!c?.id)continue;const h=yc(e,c.id,t,i,s,n,o,a);if(!h)continue;const d=h.poly;for(let e=0;e0))continue;const u=Rl(t,i,s,a);if(r.has(u))continue;r.add(u);const _=Ml(p,n,o),[m,g]=oc(d,e),f=-m,v=-g,b=_/2,y=[[t[0]+f*b,t[1]+v*b],[i[0]+f*b,i[1]+v*b],[i[0]+m*b,i[1]+g*b],[t[0]+m*b,t[1]+g*b]];l.push({key:u,kind:c,cm:p,quad:y,a:[t[0],t[1]],b:[i[0],i[1]],depthUnits:_})}}return l}function Vc(e,t,i){const s=e?.length||0;if(s<3||t.length!==s)return null;if(t.every(e=>!(e>0)))return e.map(e=>[e[0],e[1]]);hc(e),t.slice().reverse();const n=[];for(let o=0;o0||l>0)){n.push([e[o][0],e[o][1]]);continue}if(r>0!=l>0){const t=e[o],i=r>0?[t[0]-c*r,t[1]-h*r]:[t[0],t[1]],s=l>0?[t[0]-d*l,t[1]-p*l]:[t[0],t[1]];n.push(i),Math.hypot(s[0]-i[0],s[1]-i[1])>1e-9&&n.push(s);continue}if(ac(w,k)){const t=e[o],i=[t[0]-c*r,t[1]-h*r],s=[t[0]-d*l,t[1]-p*l];n.push(i),Math.hypot(s[0]-i[0],s[1]-i[1])>1e-9&&n.push(s);continue}const S=rc(x,w,$,k),M=Math.max(r,l,1e-9),C=vc(i,e[o])?.limit??4*M;if(S){const i=Math.hypot(S[0]-e[o][0],S[1]-e[o][1]);if(Number.isFinite(i)&&i<=C){n.push(S);continue}if(jc(e,t,o)){n.push([e[o][0],e[o][1]]);continue}}r>0&&n.push([e[o][0]-c*r,e[o][1]-h*r]),l>0&&n.push([e[o][0]-d*l,e[o][1]-p*l])}return n.length>=3?n:null}function Kc(e,t,i,s,n,o,a=1){const r=[];for(const l of e||[]){if(!l?.id)continue;const c=yc(e,l.id,t,i,s,n,o,a);if(!c)continue;const h=Math.abs(Ti(c.poly));for(let e=0;e[Math.max(t,e.x0),Math.min(i,e.x1)]).filter(e=>e[1]-e[0]>s).sort((e,t)=>e[0]-t[0]||e[1]-t[1]);if(!n.length)return{coverage:0,full:!1};let o=n[0][0],a=n[0][1],r=0,l=o<=t+s;for(let e=1;e=i-s,{coverage:r,full:l}}function Jc(e,t){return Number(t.full)-Number(e.full)||e.faceDistance-t.faceDistance||e.area-t.area||e.roomId.localeCompare(t.roomId)}function Xc(e,t,i=!1){const s=Number(t?.x),n=Number(t?.y),o=Number(t?.angle),a=Number(t?.length);if(!([s,n,o,a].every(Number.isFinite)&&a>0))return{negative:null,positive:null};const r=o*Math.PI/180,l=Math.cos(r),c=Math.sin(r),h=-c,d=l,p=a/2,u=Math.max(1e-9,e.adjacencyEps),_=new Map;let m=0;for(const t of e.edges){if(i&&!(t.half>0))continue;if(!Bl(t.a,t.b,o))continue;const[e,a]=Tl(t.a,t.b);if(Math.abs((s-t.a[0])*a-(n-t.a[1])*e)>u)continue;const r=(t.a[0]-s)*l+(t.a[1]-n)*c,g=(t.b[0]-s)*l+(t.b[1]-n)*c,f=Math.max(-p,Math.min(r,g)),v=Math.min(p,Math.max(r,g));if(v-f<=u)continue;const b=t.inward[0]*h+t.inward[1]*d>=0?1:-1,y=((t.a[0]+t.b[0])/2-s)*h+((t.a[1]+t.b[1])/2-n)*d,w=Math.abs(y+b*t.half),k=`${b}|${t.roomId}`,x={x0:f,x1:v,half:t.half,cm:t.cm,key:t.key,axis:[e,a]},$=_.get(k);$?($.pieces.push(x),$.faceDistance=Math.min($.faceDistance,w)):_.set(k,{roomId:t.roomId,side:b,order:m++,pieces:[x],faceDistance:w,area:t.area,coverage:0,full:!1})}for(const e of _.values()){const t=Yc(e.pieces,-p,p,u);e.coverage=t.coverage,e.full=t.full}const g=e=>{const t=[..._.values()].filter(t=>t.side===e&&t.coverage>u);return t.sort(Jc),t[0]||null};return{negative:g(-1),positive:g(1)}}function Zc(e,t){const i=Xc(e,t),s=[i.negative,i.positive].filter(e=>!!e);if(!s.length)return{ox:0,oy:0,cm:0,side:-1};const n=i.negative&&i.positive?-1:s[0].side,o=t.flip_v?-n:n,a=(-1===o?i.negative:i.positive)||s[0],r=[...a.pieces].sort((e,t)=>(e.x0<=0&&e.x1>=0?0:Math.min(Math.abs(e.x0),Math.abs(e.x1)))-(t.x0<=0&&t.x1>=0?0:Math.min(Math.abs(t.x0),Math.abs(t.x1)))||t.x1-t.x0-(e.x1-e.x0)||e.key.localeCompare(t.key))[0];if(!(r.half>0&&r.cm>0))return{ox:0,oy:0,cm:0,side:o};const l=t.angle*Math.PI/180,c=-Math.sin(l),h=Math.cos(l);return{ox:c*o*r.half,oy:h*o*r.half,cm:r.cm,side:o}}function Qc(e,t){const i=1e-9,s=t.filter(e=>Number.isFinite(e.x0)&&Number.isFinite(e.x1)&&Number.isFinite(e.half)&&e.x1>e.x0&&e.half>0);if(!s.length)return"";const n=s.flatMap(e=>[e.x0,e.x1]).sort((e,t)=>e-t),o=[];for(const e of n){const t=o[o.length-1];(void 0===t||e>t+i)&&o.push(e)}const a=[];for(let e=0;e+1t+i))continue;const r=(t+n)/2,l=s.reduce((e,t)=>r>=t.x0-i&&r<=t.x1+i?Math.max(e,t.half):e,0);if(!(l>0))continue;const c=a[a.length-1];c&&t<=c.x1+i&&Math.abs(l-c.half)<=i?c.x1=n:a.push({x0:t,x1:n,half:l})}const r=[];for(const e of a){const t=r[r.length-1],s=t?.[t.length-1];s&&e.x0<=s.x1+i?(e.x0=s.x1,t.push(e)):r.push([e])}return r.map(t=>{const i=t[0],s=t[t.length-1],n=Math.min(.25*Math.min(...t.map(e=>e.half)),.75),o=-e*n,a=[];if(1===e){a.push(`M ${i.x0} ${o} L ${s.x1} ${o}`);for(let e=t.length-1;e>=0;e--){const i=t[e];a.push(`L ${i.x1} ${i.half} L ${i.x0} ${i.half}`)}}else{a.push(`M ${s.x1} ${o} L ${i.x0} ${o}`);for(const e of t)a.push(`L ${e.x0} ${-e.half} L ${e.x1} ${-e.half}`)}return a.push("Z"),a.join(" ")}).join(" ")}function eh(e,t,i,s){if(!s)return i;const n=e.angle*Math.PI/180,o=Math.cos(n),a=Math.sin(n),r=[],l=1e-9;for(const n of i){const[i,c]=n.axis,h=o*i+a*c;if(Math.abs(h)<=l)continue;const d=e.x*i+e.y*c,p=d+h*n.x0,u=d+h*n.x1,_=Math.min(p,u),m=Math.max(p,u),g=`${n.key}|${t}`,f=s.get(g)||[];let v=[[_,m]];for(const[e,t]of f){const i=[];for(const[s,n]of v)t<=s+l||e>=n-l?i.push([s,n]):(e>s+l&&i.push([s,Math.min(n,e)]),te[0]-t[0]||e[1]-t[1]),y=[];for(const e of b){const t=y[y.length-1];t&&e[0]<=t[1]+l?t[1]=Math.max(t[1],e[1]):y.push([e[0],e[1]])}s.set(g,y)}return r}function th(e,t){const i=new Map;return t.map(t=>function(e,t,i){const s=Xc(e,t,!0),n=s.negative,o=s.positive;if(!n&&!o)return null;let a;if(n&&o)a=[{candidate:n,side:-1},{candidate:o,side:1}];else{const e=n||o;a=[{candidate:e,side:-1},{candidate:e,side:1}]}const r=a.map(({candidate:e,side:s})=>({candidate:e,side:s,pieces:eh(t,s,e.pieces,i)})),l=r.map(({candidate:e,side:t,pieces:i})=>({side:t,roomId:e.roomId,d:Qc(t,i)})),c=r.flatMap(({pieces:e})=>e);if(!c.length)return null;const h=Math.max(...c.map(e=>e.half));return{faces:l,minY:-h,maxY:h,wallKey:[...new Set(c.map(e=>e.key))].sort().join("|")}}(e,t,i))}const ih=150,sh=1e-6;function nh(e){return Number.isFinite(e)?Math.max(1,Math.min(ih,e)):1}function oh(e){return((Number.isFinite(Number(e))?Number(e):0)%90+90)%90}const ah=e=>{const t=e.map(e=>[e[0],e[1]]);return!t.length||t[0][0]===t[t.length-1][0]&&t[0][1]===t[t.length-1][1]||t.push([...t[0]]),[t]},rh=(e,t)=>e[0]===t[0]&&e[1]===t[1];function lh(e,t=1e-6){const i=Number.isFinite(t)&&t>0?t:sh,s=[];for(const t of e||[]){if(!Array.isArray(t)||t.length<2)return null;const e=Number(t[0]),n=Number(t[1]);if(!Number.isFinite(e)||!Number.isFinite(n))return null;const o=Math.round(e/i)*i,a=Math.round(n/i)*i;if(!Number.isFinite(o)||!Number.isFinite(a))return null;const r=[Object.is(o,-0)?0:o,Object.is(a,-0)?0:a];s.length&&rh(s[s.length-1],r)||s.push(r)}return s.length>1&&rh(s[0],s[s.length-1])&&s.pop(),s.length<3||new Set(s.map(e=>`${e[0]},${e[1]}`)).size<3?null:Ti(s)>i*i?s:null}function ch(e){const t=[];for(const i of e||[])for(const e of i||[]){const i=(e||[]).filter(e=>Array.isArray(e)&&e.length>=2);i.length<4||t.push(`M ${i.slice(0,-1).map(e=>`${e[0]} ${e[1]}`).join(" L ")} Z`)}return t.join(" ")}function hh(e,t,i,s,n){return Kl({a:e,b:t,halfDepth:Ml(i,s,n)/2})}function dh(e,t,i){const s=Number(t)>0?Number(t):5,n=nh(e.cm)/s*i,o=e.center[0],a=e.center[1];if("circle"===e.shape){const e=n/2;return Array.from({length:96},(t,i)=>{const s=i/96*Math.PI*2;return[o+Math.cos(s)*e,a+Math.sin(s)*e]})}const r=n/2,l=oh(e.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l);return[[-r,-r],[r,-r],[r,r],[-r,r]].map(([e,t])=>[o+e*c-t*h,a+e*h+t*c])}function ph(e,t,i=1e-9){if(!t.length)return[e];let s=[ah(e)];try{for(const e of t){const t=e.b[0]-e.a[0],n=e.b[1]-e.a[1],o=Math.hypot(t,n);if(!(o>i))continue;const a=t/o,r=n/o,l=-r,c=a,h=1.25*Math.max(Number(e.depth)||0,4*i),d=Math.max(2*i,1e-9*o),p=[[e.a[0]-a*d-l*h,e.a[1]-r*d-c*h],[e.b[0]+a*d-l*h,e.b[1]+r*d-c*h],[e.b[0]+a*d+l*h,e.b[1]+r*d+c*h],[e.a[0]-a*d+l*h,e.a[1]-r*d+c*h]];s=oi(s,ah(p))}return kh(s)}catch{return[e]}}function uh(e,t){try{const i=oi([[...e.map(e=>[e[0],e[1]]),[e[0][0],e[0][1]]]],[[...t.map(e=>[e[0],e[1]]),[t[0][0],t[0][1]]]]);let s=null,n=0;for(const e of i||[]){const t=e?.[0]||[],i=Math.abs(Ti(t));t.length>=4&&i>n&&(n=i,s=t.slice(0,-1).map(e=>[e[0],e[1]]))}return s}catch{return null}}function _h(e,t,i,s=Math.max(2e-4*i,1e-9),n=[]){const o=[],a=[],r=new Map;for(const e of n){const t=r.get(e.hostId)||[];t.push(e),r.set(e.hostId,t)}const l=[],c=[],h=[];for(const s of e.room_drafts||[])for(let e=0;e+10))continue;const r={a:s.points[e],b:s.points[e+1],halfDepth:a},c=Kl(r);c&&(o.push(r),l.push(c))}const d=[];for(const s of e.partitions||[]){const e={a:s.a,b:s.b,halfDepth:Ml(s.cm,t,i)/2};if(!(e.halfDepth>0))continue;const n=Kl(e);n&&(a.push(e),c.push(n),d.push({id:s.id,body:n}))}const p=[...o,...a];for(const{segmentIndex:e,wedge:t}of Ql(p,s)){const i=e=o.length&&(d[i.at].body=s))}for(const e of d)h.push(...ph(e.body,r.get(e.id)||[],s));const u=(e.wall_columns||[]).map(e=>dh(e,t,i)),_=ec([...o,...a],s).flatMap(e=>ph(e,n,s));return{drafts:l,partitions:c,columns:u,patches:_,all:[...l,...h,..._,...u]}}function mh(e){try{const t=e.map(e=>lh(e)).filter(e=>!!e).map(e=>ah(e));return t.length?si(t[0],...t.slice(1)):null}catch{return null}}const gh=e=>`M ${e.map(e=>`${e[0]} ${e[1]}`).join(" L ")} Z`;function fh(e){const t=[];for(const i of e||[]){if(yh([i])<=1e-6)continue;const e=[];for(const t of i||[]){const i=(t||[]).filter(e=>Array.isArray(e)&&e.length>=2);i.length<4||e.push(gh(i.slice(0,-1)))}e.length&&t.push(e.join(" "))}return t}function vh(e,t,i={}){const s=mh(e.filter(e=>e.length>=3)),n=t.some(e=>!lh(e))?null:mh(t);if(!s)return[];if(n)try{return fh(ni(s,n))}catch{}return function(e,t,i){let s=null;for(let n=0;n=4&&t.push(e.slice(0,-1).map(e=>[e[0],e[1]]));return t}function kh(e){const t=[];for(const i of e||[]){const e=i?.[0];e?.length>=4&&t.push(e.slice(0,-1).map(e=>[e[0],e[1]]))}return t}function xh(e,t){let i=!1;for(let s=0,n=t.length-1;se[1]!=l>e[1]&&e[0]<(r-o)*(e[1]-a)/(l-a||1e-12)+o&&(i=!i)}return i}function $h(e,t,i){return function(e,t){for(const i of t||[]){const t=i?.[0];if(!t?.length||!xh(e,t))continue;let s=!1;for(let t=1;txh(e,t))}function Sh(e,t,i){if(Math.hypot(e.center[0]-t.center[0],e.center[1]-t.center[1])>i)return!1;if(Math.abs(nh(e.cm)-nh(t.cm))>1e-6)return!1;if(e.shape!==t.shape)return!0;if("circle"===e.shape||"circle"===t.shape)return!0;const s=Math.abs(oh(e.angle)-oh(t.angle));return Math.min(s,90-s)<=1e-6}const Mh=2.54;function Ch(e){const t="number"==typeof e?e:NaN;return!Number.isFinite(t)||t<=0||5===t?1:5/t}function Dh(e,t){return e*Ch(t)}function Ph(e){return e?Mh:1}function Th(e,t){if(!t)return String(e);const i=e/Mh;return String(Math.round(1e6*i)/1e6)}const Ih=1e3;function Rh(e,t){const i=Number(e),s=Number.isFinite(i)&&i>0?i:1,n=s>=1?t:t*s,o=s>=1?t/s:t;return{x:(t-n)/2,y:(t-o)/2,w:n,h:o}}const Ah=.01,zh=100;function Fh(e,t=1e3){const i=Rh(e?.plan_aspect,t),s=Number(e?.plan_scale),n=Number.isFinite(s)&&s>0?Math.min(zh,Math.max(Ah,s)):1,o=Number(e?.plan_scale_x),a=Number(e?.plan_scale_y),r=Number.isFinite(o)&&o>0?Math.min(zh,Math.max(Ah,o)):n,l=Number.isFinite(a)&&a>0?Math.min(zh,Math.max(Ah,a)):n,c=Number(e?.plan_x),h=Number(e?.plan_y),d=qr(e?.plan_angle);return{x:i.x+(Number.isFinite(c)?Gh(c):0)*t,y:i.y+(Number.isFinite(h)?Gh(h):0)*t,w:i.w*r,h:i.h*l,...d?{angle:d}:{}}}function Nh(e){if(null==e.x||null==e.y)return{x:e.x,y:e.y,w:e.w,h:e.h};const t=Number(e.w)||0,i=Number(e.h)||0;return{x:t<0?e.x+t:e.x,y:i<0?e.y+i:e.y,w:Math.abs(t),h:Math.abs(i)}}function Eh(e){return e&&Array.isArray(e.spaces)?e.spaces.map(e=>{const t=Ih,i=function(e){return Array.isArray(e)&&4===e.length&&e.every(e=>Number.isFinite(e))&&e[2]>1e-6&&e[3]>1e-6?e:[0,0,1,1]}(e.view_box);return{id:e.id,title:e.title,cellCm:Number.isFinite(Number(e.cell_cm))&&Number(e.cell_cm)>0?Number(e.cell_cm):5,vb:[i[0]*Ih,i[1]*t,i[2]*Ih,i[3]*t],bg:e.plan_url?{href:As(e.plan_url),...Fh(e,Ih)}:null,rooms:(e.rooms||[]).map(e=>{const i={...e,...Nh(e)};return{id:i.id,name:i.name,area:i.area??null,open_to:i.open_to||void 0,settings:i.settings||void 0,x:null!=i.x?i.x*Ih:void 0,y:null!=i.y?i.y*t:void 0,w:null!=i.w?i.w*Ih:void 0,h:null!=i.h?i.h*t:void 0,poly:i.poly?i.poly.map(e=>[e[0]*Ih,e[1]*t]):void 0,wall_ids:Array.isArray(i.wall_ids)?[...i.wall_ids]:void 0}}),wall_segments:(e.wall_segments||[]).map(e=>({...e,id:String(e.id),a:[Number(e.a?.[0])*Ih,Number(e.a?.[1])*t],b:[Number(e.b?.[0])*Ih,Number(e.b?.[1])*t],cm:Number(e.cm)})),room_drafts:(e.room_drafts||[]).map(e=>({id:e.id,points:(e.points||[]).map(e=>[e[0]*Ih,e[1]*t]),segments:(e.segments||[]).map(e=>({..."string"==typeof e.id&&e.id?{id:e.id}:{},cm:Number(e.cm)}))})),partitions:(e.partitions||[]).map(e=>({id:e.id,a:[e.a[0]*Ih,e.a[1]*t],b:[e.b[0]*Ih,e.b[1]*t],cm:Number(e.cm)})),wall_columns:(e.wall_columns||[]).map(e=>({id:e.id,shape:"circle"===e.shape?"circle":"square",center:[e.center[0]*Ih,e.center[1]*t],cm:Number(e.cm),..."circle"===e.shape?{}:{angle:oh(e.angle)}}))}}):[]}const Oh=5e3,Hh=Oh*Ih,Lh=240,Bh=Ih/Lh,jh=1/Lh;function qh(e){if(!Number.isFinite(e))return e;const t=Math.round(e*Lh/Ih)*Ih/Lh;return Math.abs(t-e)<=1e-9*Bh?e:t}function Wh(e){return{x:qh(e.x),y:qh(e.y)}}function Uh(e){return Number.isFinite(e)?Math.min(Hh,Math.max(-Hh,e)):0}function Gh(e){return Number.isFinite(e)?Math.min(Oh,Math.max(-Oh,e)):0}const Vh=1/3,Kh=200;function Yh(e){let t=1/0,i=1/0,s=-1/0,n=-1/0;for(const o of e){const e=Number(o[0]),a=Number(o[1]);Number.isFinite(e)&&Number.isFinite(a)&&(es&&(s=e),a>n&&(n=a))}return t>s?null:{minX:t,minY:i,maxX:s,maxY:n}}function Jh(e){return e.poly&&e.poly.length?Yh(e.poly):null==e.x||null==e.y?null:Yh([[e.x,e.y],[e.x+(e.w||0),e.y+(e.h||0)]])}function Xh(e,t){const i=[];for(const t of e.rooms||[]){const e=Jh(t);e&&i.push(e)}if(e.bg){const t=Yh(Yr(e.bg));t&&i.push(t)}for(const e of t||[])if(Array.isArray(e)){const t=Yh([e]);t&&i.push(t)}else i.push(e);return i}const Zh=e=>{if(!e.length)return 0;const t=[...e].sort((e,t)=>e-t),i=t.length>>1;return t.length%2?t[i]:(t[i-1]+t[i])/2},Qh=e=>{let t=1/0,i=1/0,s=-1/0,n=-1/0;for(const o of e)o.minXs&&(s=o.maxX),o.maxY>n&&(n=o.maxY);return t>s||i>n?null:{x:t,y:i,w:s-t,h:n-i}};function ed(e,t){let{x:i,y:s,w:n,h:o}=e;n<30&&(i=i+n/2-100,n=Kh),o<30&&(s=s+o/2-100,o=Kh);const a=Math.max(n,o)*t;return{x:i-a,y:s-a,w:n+2*a,h:o+2*a}}function td(e,t={}){const i=t.pad??.05,s=t.k??10,n=t.minSpread??50,o=e.filter(e=>Number.isFinite(e.minX)&&Number.isFinite(e.minY)&&Number.isFinite(e.maxX)&&Number.isFinite(e.maxY)&&Math.abs(e.minX)<=Hh&&Math.abs(e.maxX)<=Hh&&Math.abs(e.minY)<=Hh&&Math.abs(e.maxY)<=Hh);if(!o.length)return{core:null,all:null,outliers:0};const a=Qh(o);if(o.length<4){const e=ed(a,i);return{core:e,all:e,outliers:0}}const r=o.map(e=>(e.minX+e.maxX)/2),l=o.map(e=>(e.minY+e.maxY)/2),c=Zh(r),h=Zh(l),d=o.map((e,t)=>Math.max(Math.abs(r[t]-c),Math.abs(l[t]-h))),p=Math.max(((e,t)=>e.length?e[Math.min(e.length-1,Math.max(0,Math.round(t*(e.length-1))))]:0)([...d].sort((e,t)=>e-t),.75),n),u=d.map(e=>e>s*p),_=u.filter(Boolean).length,m=_&&_<=o.length*Vh?o.filter((e,t)=>!u[t]):o;return{core:ed(Qh(m)||a,i),all:ed(a,i),outliers:m===o?0:_}}function id(e,t,i=.05){const s=td(Xh(e,t),{pad:i});if(s.core)return s.core;const n=e.vb&&4===e.vb.length&&e.vb[2]>0&&e.vb[3]>0?e.vb:[0,0,Ih,Ih];return{x:n[0],y:n[1],w:n[2],h:n[3]}}function sd(e){const t=id(e);return{x:t.x+t.w/2,y:t.y+t.h/2}}function nd(e){const t=[];for(const i of e.rooms||[]){const e=Jh(i);e&&t.push(e)}const i=td(t,{pad:0}).core,s=Ih*Ch(e.cellCm??5);return i?Math.max(s,Math.min(Hh,Math.max(i.w,i.h))):s}function od(e,t,i,s=1){const n=Number(i),o=Number.isFinite(s)&&s>0?s:1;return!Number.isFinite(n)||n<=0?e*o:e*nd(t)*o/n}const ad=[1,2,5,10,20,50,100,200,500,1e3];function rd(e){if(e.poly&&e.poly.length){const t=e.poly.map(e=>e[0]),i=e.poly.map(e=>e[1]),s=Math.min(...t),n=Math.min(...i);return{x:s,y:n,w:Math.max(...t)-s,h:Math.max(...i)-n}}return{x:e.x??0,y:e.y??0,w:e.w??0,h:e.h??0}}function ld(e,t,i,s){const n=i["rl_"+(e.id||"")];if(n&&n.s===t)return{x:n.x*Ih,y:n.y*Ih};const o=function(e){if(e.poly){const t=e.poly.length;return[e.poly.reduce((e,t)=>e+t[0],0)/t,e.poly.reduce((e,t)=>e+t[1],0)/t]}return[e.x+e.w/2,e.y+.1*Math.min(e.w,e.h)]}(e);return Wh({x:o[0],y:o[1]})}const cd=["furniture","appliance","sanitary","other"],hd=[{id:"sofa",group:"furniture",w:220,h:90,g:[["r",0,0,1,1],["l",.09,.26,.91,.26],["l",.09,.26,.09,1],["l",.91,.26,.91,1],["l",.5,.26,.5,1]]},{id:"armchair",group:"furniture",w:90,h:85,g:[["r",0,0,1,1],["l",.14,.28,.86,.28],["l",.14,.28,.14,1],["l",.86,.28,.86,1]]},{id:"coffee_table",group:"furniture",w:110,h:60,g:[["r",0,0,1,1],["r",.08,.14,.84,.72]]},{id:"table_dining",group:"furniture",w:140,h:80,g:[["r",0,0,1,1],["r",.06,.11,.88,.78]]},{id:"table_round",group:"furniture",w:120,h:120,g:[["e",.5,.5,.5,.5],["e",.5,.5,.41,.41]]},{id:"chair",group:"furniture",w:45,h:45,g:[["r",0,0,1,.18],["r",.06,.18,.88,.8]]},{id:"desk",group:"furniture",w:120,h:60,g:[["r",0,0,1,1],["r",.63,.07,.31,.86],["l",.63,.5,.94,.5]]},{id:"bed_double",group:"furniture",w:160,h:200,g:[["r",0,0,1,1],["r",0,0,1,.07],["r",.06,.1,.4,.15],["r",.54,.1,.4,.15],["l",0,.33,1,.33]]},{id:"bed_single",group:"furniture",w:90,h:200,g:[["r",0,0,1,1],["r",0,0,1,.07],["r",.15,.1,.7,.15],["l",0,.33,1,.33]]},{id:"nightstand",group:"furniture",w:45,h:40,g:[["r",0,0,1,1],["r",.12,.14,.76,.33],["r",.12,.53,.76,.33]]},{id:"wardrobe",group:"furniture",w:100,h:60,g:[["r",0,0,1,1],["l",0,.72,1,.72],["l",.5,.72,.5,1]]},{id:"bookshelf",group:"furniture",w:80,h:30,g:[["r",0,0,1,1],["l",.34,0,.34,1],["l",.67,0,.67,1]]},{id:"fridge",group:"appliance",w:60,h:65,g:[["r",0,0,1,1],["l",0,.36,1,.36],["l",.83,.44,.83,.64]]},{id:"stove",group:"appliance",w:60,h:60,g:[["r",0,0,1,1],["e",.29,.31,.15,.15],["e",.71,.31,.15,.15],["e",.29,.71,.15,.15],["e",.71,.71,.15,.15]]},{id:"dishwasher",group:"appliance",w:60,h:60,g:[["r",0,0,1,1],["r",.1,.12,.8,.76],["e",.5,.5,.27,.27],["e",.5,.5,.13,.13]]},{id:"washer",group:"appliance",w:60,h:60,g:[["r",0,0,1,1],["l",.08,.17,.92,.17],["e",.5,.57,.3,.3],["e",.5,.57,.14,.14]]},{id:"dryer",group:"appliance",w:60,h:60,g:[["r",0,0,1,1],["l",.08,.17,.92,.17],["e",.5,.57,.3,.3],["p",.36,.5,.5,.64,.64,.5]]},{id:"tv",group:"appliance",w:120,h:30,g:[["r",0,0,1,.42],["l",.5,.42,.5,.72],["l",.3,.72,.7,.72]]},{id:"ac",group:"appliance",w:90,h:25,g:[["r",0,0,1,1],["l",.05,.55,.95,.55],["l",.05,.79,.95,.79]]},{id:"water_heater",group:"appliance",w:45,h:45,g:[["e",.5,.5,.5,.5],["e",.5,.5,.31,.31]]},{id:"toilet",group:"sanitary",w:40,h:70,g:[["r",.06,0,.88,.2],["e",.5,.58,.37,.35],["e",.5,.58,.22,.2]]},{id:"bathtub",group:"sanitary",w:170,h:75,g:[["r",0,0,1,1],["r",.05,.11,.77,.78],["e",.89,.5,.045,.1]]},{id:"shower",group:"sanitary",w:90,h:90,g:[["r",0,0,1,1],["l",0,0,1,1],["l",1,0,0,1],["e",.5,.5,.08,.08]]},{id:"sink",group:"sanitary",w:60,h:45,g:[["r",0,0,1,1],["e",.5,.6,.34,.3],["e",.5,.15,.07,.07]]},{id:"kitchen_sink",group:"sanitary",w:80,h:60,g:[["r",0,0,1,1],["r",.06,.24,.44,.64],["r",.54,.24,.4,.64],["e",.5,.12,.06,.06]]},{id:"bidet",group:"sanitary",w:40,h:55,g:[["e",.5,.5,.44,.5],["e",.5,.5,.26,.3]]},{id:"stairs",group:"other",w:100,h:280,g:[["r",0,0,1,1],["l",0,.111,1,.111],["l",0,.222,1,.222],["l",0,.333,1,.333],["l",0,.444,1,.444],["l",0,.556,1,.556],["l",0,.667,1,.667],["l",0,.778,1,.778],["l",0,.889,1,.889],["l",.5,.93,.5,.06],["p",.38,.16,.5,.06,.62,.16]]},{id:"fireplace",group:"other",w:120,h:40,g:[["r",0,0,1,1],["p",.22,1,.22,.42,.78,.42,.78,1]]},{id:"plant",group:"other",w:40,h:40,g:[["e",.5,.5,.22,.22],["l",.5,.28,.5,.02],["l",.5,.72,.5,.98],["l",.28,.5,.02,.5],["l",.72,.5,.98,.5],["l",.34,.34,.13,.13],["l",.66,.66,.87,.87],["l",.66,.34,.87,.13],["l",.34,.66,.13,.87]]},{id:"rug",group:"other",w:200,h:140,g:[["r",0,0,1,1],["r",.06,.09,.88,.82]]}],dd=new Map(hd.map(e=>[e.id,e]));function pd(e){return e&&dd.get(e)||null}function ud(e){return hd.filter(t=>t.group===e)}function _d(e,t,i=Bh,s=1e3){const n=Number(t)>0?Number(t):5;return(Number(e)||0)/n*i/s}const md=5e-4,gd=Oh;function fd(e){return Number.isFinite(e)?Math.max(md,Math.min(gd,e)):md}const vd=e=>{const t=Math.round(1e3*e)/1e3;return Object.is(t,-0)?"0":String(t)};function bd(e,t,i){const s=pd(e);if(!(s&&t>0&&i>0))return"";const n=e=>vd(e*t),o=e=>vd(e*i),a=[];for(const e of s.g)if("r"===e[0]){const[,t,i,s,r]=e;a.push(`M${n(t)} ${o(i)}H${n(t+s)}V${o(i+r)}H${n(t)}Z`)}else if("l"===e[0]){const[,t,i,s,r]=e;a.push(`M${n(t)} ${o(i)}L${n(s)} ${o(r)}`)}else if("e"===e[0]){const[,s,r,l,c]=e;a.push(`M${n(s-l)} ${o(r)}A${vd(l*t)} ${vd(c*i)} 0 0 1 ${n(s+l)} ${o(r)}A${vd(l*t)} ${vd(c*i)} 0 0 1 ${n(s-l)} ${o(r)}Z`)}else{const t=e.slice(1);if(t.length<4)continue;let i=`M${n(t[0])} ${o(t[1])}`;for(let e=2;e+1{let t=(e%360+360)%360;return t>180&&(t-=360),t};function wd(e,t,i,s,n,o=0){let a=null,r=n;for(const n of s){const[s,l,c,h]=n,d=c-s,p=h-l,u=d*d+p*p;if(!u)continue;const _=Math.sqrt(u);let m=((e-s)*d+(t-l)*p)/u;m=Math.max(0,Math.min(1,m));let g=s+m*d,f=l+m*p;const v=Math.hypot(e-g,t-f);if(!(v0){const e=Math.round(m*_/o)*o;g=s+e/_*d,f=l+e/_*p}a={cx:g+b*(i/2),cy:f+y*(i/2),angle:yd(180*Math.atan2(-b,y)/Math.PI),dist:v}}return a}const kd=1e-9,xd=e=>`${e[0].toFixed(6)},${e[1].toFixed(6)}`,$d=(e,t)=>Math.hypot(t[0]-e[0],t[1]-e[1]),Sd=(e,t)=>Math.atan2(t[1]-e[1],t[0]-e[0]),Md=e=>Array.isArray(e)&&e.length>=2&&e.every(e=>Number.isFinite(e)),Cd=e=>(e||[]).filter(e=>Md(e?.a)&&Md(e?.b)&&$d(e.a,e.b)>kd);function Dd(e,{minAngleDeg:t=15,maxValence:i=6}={}){const s=new Map;for(const t of Cd(e))for(const[e,i]of[[t.a,t.b],[t.b,t.a]]){const t=s.get(xd(e))||[];t.push(Sd(e,i)),s.set(xd(e),t)}const n=[];for(const[e,o]of s){if(o.length>i&&n.push({rule:"valence",subject:e,actual:o.length,limit:i}),o.length<2)continue;const s=[...o].sort((e,t)=>e-t);let a=1/0;for(let e=0;ekd&&i(180*Math.atan2(e.b[1]-e.a[1],e.b[0]-e.a[0])/Math.PI%180+180)%180,Td=e=>{const t=new Map;for(const i of e)for(const e of[i.a,i.b]){const s=t.get(xd(e));s?s.push(i):t.set(xd(e),[i])}return t};function Id(e,t,i){const s=Cd(t),n=i??Td(s),o=new Set([e]);let a=$d(e.a,e.b);const r=(e,t)=>{const i=(n.get(xd(t))||[]).find(t=>!o.has(t)&&((e,t,i=1)=>{const s=Math.abs(Pd(e)-Pd(t));return Math.min(s,180-s)<=i})(t,e)&&Number(t.cm||0)===Number(e.cm||0));i&&(o.add(i),a+=$d(i.a,i.b),r(i,xd(i.a)===xd(t)?i.b:i.a))};return r(e,e.a),r(e,e.b),a}function Rd(e,t,i,{minLengthCm:s=20}={}){const n=[],o=Cd(e),a=Td(o);for(const e of o){const r=Id(e,o,a)/i*(t||1),l=Math.max(s,Number(e.cm)>0?Number(e.cm):0);r{const s=i[0]-t[0],n=i[1]-t[1],o=s*s+n*n,a=o<=kd?0:Math.max(0,Math.min(1,((e[0]-t[0])*s+(e[1]-t[1])*n)/o));return Math.hypot(e[0]-(t[0]+s*a),e[1]-(t[1]+n*a))};function zd(e,t,i,{minDistanceCm:s=5}={}){const n=Cd(e),o=new Map;for(const e of n)o.set(xd(e.a),e.a),o.set(xd(e.b),e.b);const a=((e,t,i)=>e/(t||1)*i)(s,t,i),r=a>kd?a:1,l=(e,t)=>`${Math.floor(e/r)},${Math.floor(t/r)}`,c=new Map;for(const[e,t]of o){const i=l(t[0],t[1]),s=c.get(i);s?s.push([e,t]):c.set(i,[[e,t]])}const h=new Map;for(const e of n){const t=Math.min(e.a[0],e.b[0])-a,i=Math.max(e.a[0],e.b[0])+a,s=Math.min(e.a[1],e.b[1])-a,n=Math.max(e.a[1],e.b[1])+a;for(let o=Math.floor(t/r);o<=Math.floor(i/r);o++)for(let t=Math.floor(s/r);t<=Math.floor(n/r);t++){const i=`${o},${t}`,s=h.get(i);s?s.push(e):h.set(i,[e])}}const d=[];for(const[e,n]of o){const o=Math.floor(n[0]/r),l=Math.floor(n[1]/r);for(let r=-1;r<=1;r++)for(let h=-1;h<=1;h++)for(const[p,u]of c.get(`${o+r},${l+h}`)||[]){if(e>=p)continue;const o=$d(n,u);o{const s=o[(i+1)%o.length];return e+(t[0]*s[1]-s[0]*t[1])},0))/2,r=(i||1)/s,l=a*r*r;return l0?i:1;return{a:[e[0]/s,e[1]/s],b:[t[0]/s,t[1]/s]}}function Ed(e,t){const i=t>0?t:1;return[e.a[0]*i,e.a[1]*i,e.b[0]*i,e.b[1]*i]}function Od(e){return Array.isArray(e)&&e.length>=2&&Number.isFinite(Number(e[0]))&&Number.isFinite(Number(e[1]))}function Hd(e){if(!Array.isArray(e))return[];const t=[];for(const i of e){if(!i||"object"!=typeof i)continue;const e=i;if(!Od(e.a)||!Od(e.b))continue;const s=[Number(e.a[0]),Number(e.a[1])],n=[Number(e.b[0]),Number(e.b[1])];Math.hypot(n[0]-s[0],n[1]-s[1])<.001||t.push({a:s,b:n})}return t}function Ld(e,t,i,s,n=!0){const o=(e||[]).filter(e=>e?.id),a=Hd(t);if(a.length)return function(e,t,i,s){const n=Hd(e);if(!n.length)return[];const o=function(e,t){const i=[],s=(e||[]).filter(e=>e?.id);for(let e=0;e4*s||o>4*s)continue;const a=(e[0]-n)*p+(e[1]-l)*u,c=(e[2]-n)*p+(e[3]-l)*u,h=Math.max(0,Math.min(a,c)),m=Math.min(d,Math.max(a,c));if(m-he.lo-t.lo||e.hi-t.hi);const i=[];for(const e of t){const t=i[i.length-1];t&&e.lo<=t.hi+r?t.hi=Math.max(t.hi,e.hi):i.push({...e})}for(const t of i){const i=[n+p*t.lo,l+u*t.lo],s=[n+p*t.hi,l+u*t.hi];Math.hypot(s[0]-i[0],s[1]-i[1])i.pair===e&&Math.abs(i.ux*a-i.uy*o)<=1e-6&&Math.abs((t[0]-i.origin[0])*i.uy-(t[1]-i.origin[1])*i.ux)<=c&&Math.abs((t[2]-i.origin[0])*i.uy-(t[3]-i.origin[1])*i.ux)<=c);h||(h={pair:e,origin:[t[0],t[1]],ux:o,uy:a,ranges:[]},l.push(h));const d=(t[0]-h.origin[0])*h.ux+(t[1]-h.origin[1])*h.uy,p=(t[2]-h.origin[0])*h.ux+(t[3]-h.origin[1])*h.uy;h.ranges.push({lo:Math.min(d,p),hi:Math.max(d,p)})}const h=[];for(const e of l){e.ranges.sort((e,t)=>e.lo-t.lo||e.hi-t.hi);const t=[];for(const i of e.ranges){const e=t[t.length-1];e&&i.lo<=e.hi+r?e.hi=Math.max(e.hi,i.hi):t.push({...i})}for(const s of t){const t=[e.origin[0]+e.ux*s.lo,e.origin[1]+e.uy*s.lo],n=[e.origin[0]+e.ux*s.hi,e.origin[1]+e.uy*s.hi];Math.hypot(n[0]-t[0],n[1]-t[1])>=r&&h.push(Nd(t,n,i))}}return h}(a,e,i,s).map(e=>Ed(e,i));if(!n)return[];const r=[],l=(e,t)=>(e.open_to||[]).includes(t.id)||(t.open_to||[]).includes(e.id);for(let e=0;e{const t=qd.get(e);if(!t)return;const i=Date.now();if("hidden"===e.visibilityState){t.hiddenAt||(t.hiddenAt=i);const e={kind:"hidden",token:t.token,at:i,hiddenFor:0,long:!1};for(const i of[...t.subscribers])i(e);return}const s=t.hiddenAt?Math.max(0,i-t.hiddenAt):0;t.hiddenAt=0,t.token++;const n={kind:"visible",token:t.token,at:i,hiddenFor:s,long:s>=Bd};for(const e of[...t.subscribers])e(n)},onPageShow:t=>{const i=qd.get(e);if(!i)return;const s=Date.now();i.token++;const n={kind:"pageshow",token:i.token,at:s,hiddenFor:0,long:!!t.persisted,persisted:!!t.persisted};for(const e of[...i.subscribers])e(n)}},qd.set(e,i),e.addEventListener("visibilitychange",i.onVisibility),e.defaultView?.addEventListener("pageshow",i.onPageShow)}return i.subscribers.add(t),()=>{const i=qd.get(e);i&&(i.subscribers.delete(t),i.subscribers.size||(e.removeEventListener("visibilitychange",i.onVisibility),e.defaultView?.removeEventListener("pageshow",i.onPageShow),qd.delete(e)))}}const Ud={now:()=>Date.now(),setTimeout:(e,t)=>window.setTimeout(e,t),clearTimeout:e=>window.clearTimeout(e),requestAnimationFrame:e=>window.requestAnimationFrame(e),cancelAnimationFrame:e=>window.cancelAnimationFrame(e)};class Gd{constructor(e,t=Ud){this.onChange=e,this.clock=t,this._state="steady",this._token=0,this._frameFingerprint="",this._hasCompleteFrame=!1,this._overlayPhase="none",this._recoveryReason=null,this._overlayTimer=0,this._overlayRaf=0,this._overlayOpaqueAt=0,this._barrierRafs=new Set,this._trace=[],this._disposed=!1}get state(){return this._state}get token(){return this._token}get frameFingerprint(){return this._frameFingerprint}get hasCompleteFrame(){return this._hasCompleteFrame}get overlayPhase(){return this._overlayPhase}get overlayVisible(){return"none"!==this._overlayPhase}get overlayBlocksInteraction(){return this.overlayVisible}get recoveryReason(){return this._recoveryReason}get trace(){return this._trace.map(e=>({...e,...e.stage?{stage:[...e.stage]}:{}}))}note(e,t={}){this.record(e,void 0,t)}record(e,t,i={}){this._trace.push({at:this.clock.now(),token:this._token,event:e,state:this._state,...t?{reason:t}:{},...i}),this._trace.length>80&&this._trace.splice(0,this._trace.length-80)}changed(){this._disposed||this.onChange()}adoptCompleteFrame(e){e&&(this._hasCompleteFrame=!0,this._frameFingerprint=e,this._state="steady",this._recoveryReason=null,this.clearOverlay(),this.record("frame-adopted"),this.changed())}markCompleteFrame(e){e&&(this._hasCompleteFrame=!0,this._frameFingerprint=e,this._state="steady",this._recoveryReason=null,this.clearOverlay(),this.record("frame-complete"),this.changed())}refreshCompleteFrame(e){!this._disposed&&this._hasCompleteFrame&&"steady"===this._state&&e&&e!==this._frameFingerprint&&(this._frameFingerprint=e,this.record("frame-refreshed"))}visibility(e){return"hidden"===e.kind?(this.record("visibility-hidden"),this._token):e.long||"visible"!==e.kind&&"pageshow"!==e.kind?this.beginCandidate("pageshow"===e.kind?"pageshow":"long-resume"):(this.record("pageshow"===e.kind?"pageshow-noop":"visibility-visible-quick"),this._token)}beginCandidate(e,t="plan"){return this._token++,this._recoveryReason=t,this._hasCompleteFrame?(this._state="connection"===t?"offline-stale":"holding",this.clearOverlay()):this.overlayVisible?this._state="overlay-visible":(this._state="overlay-pending",this.scheduleOverlay(t)),this.record("candidate-start",e),this.changed(),this._token}connectionLost(){return this.beginCandidate("connection-lost","connection")}candidateReady(e){return!this._disposed&&e===this._token&&(this._state="candidate-ready",this.record("candidate-ready"),this.changed(),!0)}async commitAfterPaint(e,t){if(this._disposed||e!==this._token)return!1;let i=!0;const s=(async()=>{for(await t.updateComplete();i&&!this._disposed&&e===this._token;)if(t.stageValid()&&t.assetsReady()){if(await this.nextFrame(),e!==this._token||!t.stageValid()||!t.assetsReady())continue;if(await this.nextFrame(),e===this._token&&t.stageValid()&&t.assetsReady())return!0}else await this.nextFrame();return!1})();let n=0;const o=new Promise(e=>{n=this.clock.setTimeout(()=>e({ready:!1,timedOut:!0}),jd)}),a=await Promise.race([s.then(e=>({ready:e,timedOut:!1})),o]);return i=!1,this.clock.clearTimeout(n),!this._disposed&&e===this._token&&(!(!a.ready&&!a.timedOut)&&(a.ready?(this._hasCompleteFrame=!0,this._frameFingerprint=t.frameFingerprint(),this.record("paint-barrier"),this.finishOverlayAfterCommit(),!0):(this.record("paint-barrier-timeout"),this._hasCompleteFrame?(this._state="connection"===this._recoveryReason?"offline-stale":"steady",this.clearOverlay()):(this._state="recovery-error","opaque"!==this._overlayPhase&&this.showOverlayNow()),this.changed(),!1)))}retry(e=this._recoveryReason||"plan"){return this.beginCandidate("retry",e)}nextFrame(){return new Promise(e=>{const t=this.clock.requestAnimationFrame(()=>{this._barrierRafs.delete(t),e()});this._barrierRafs.add(t)})}scheduleOverlay(e){this.clearOverlayTimer(),this._recoveryReason=e,this._overlayTimer=this.clock.setTimeout(()=>{this._overlayTimer=0,this._hasCompleteFrame||"overlay-pending"!==this._state&&"candidate-ready"!==this._state||(this._state="overlay-visible",this._overlayPhase="entering",this.record("overlay-enter"),this.changed(),this._overlayRaf=this.clock.requestAnimationFrame(()=>{this._overlayRaf=0,"entering"===this._overlayPhase&&(this._overlayPhase="fading-in",this.changed(),this._overlayTimer=this.clock.setTimeout(()=>{this._overlayTimer=0,"fading-in"===this._overlayPhase&&(this._overlayPhase="opaque",this._overlayOpaqueAt=this.clock.now(),this.record("overlay-opaque"),this.changed())},150))}))},150)}showOverlayNow(){this.clearOverlayTimer(),this._overlayPhase="opaque",this._overlayOpaqueAt=this.clock.now(),this.record("overlay-error")}finishOverlayAfterCommit(){if(this.clearOverlayTimer(),"none"===this._overlayPhase||"entering"===this._overlayPhase||"fading-in"===this._overlayPhase)return this.clearOverlay(),this._state="steady",this._recoveryReason=null,this.record("candidate-committed"),void this.changed();const e=Math.max(0,this._overlayOpaqueAt+250-this.clock.now());this._overlayTimer=this.clock.setTimeout(()=>{this._overlayTimer=0,this._overlayPhase="leaving",this.record("overlay-leave"),this.changed(),this._overlayTimer=this.clock.setTimeout(()=>{this._overlayTimer=0,this._overlayPhase="none",this._state="steady",this._recoveryReason=null,this.record("candidate-committed"),this.changed()},180)},e)}clearOverlayTimer(){this._overlayTimer&&this.clock.clearTimeout(this._overlayTimer),this._overlayTimer=0}clearOverlay(){this.clearOverlayTimer(),this._overlayRaf&&this.clock.cancelAnimationFrame(this._overlayRaf),this._overlayRaf=0,this._overlayPhase="none",this._overlayOpaqueAt=0}dispose(){this._disposed=!0,this._token++,this.clearOverlay();for(const e of this._barrierRafs)this.clock.cancelAnimationFrame(e);this._barrierRafs.clear()}}function Vd(e){let t=2166136261,i=2654435769;const s=new WeakSet,n=e=>{for(let s=0;s>>0)+(i>>>2)}},o=e=>{if(null===e)return void n("n");const t=typeof e;if("string"===t)return void n(`s${e.length}:${e}`);if("number"===t)return void n(`d${Number.isNaN(e)?"NaN":String(e)}`);if("boolean"===t)return void n(e?"t":"f");if("undefined"===t)return void n("u");if("object"!==t)return void n(`${t}:${String(e)}`);const i=e;if(s.has(i))n("[cycle]");else{if(s.add(i),Array.isArray(e)){n("[");for(const t of e)o(t);return n("]"),void s.delete(i)}n("{");for(const t of Object.keys(e).sort())n(t),o(e[t]);n("}"),s.delete(i)}};return o(e),`${(t>>>0).toString(16).padStart(8,"0")}${(i>>>0).toString(16).padStart(8,"0")}`}function Kd(e){return Vd(e)}const Yd=new WeakMap;class Jd{constructor(e,t=()=>Date.now()){this.onUpdate=e,this.now=t,this.fallbackAuthority={},this.referenceOwner={},this.referenced=new Set,this.queued=new Set,this.disposed=!1,this.sharedUpdate=()=>{this.disposed||this.onUpdate()}}bind(e){const t=((e,t)=>{const i=e?.connection||e?.callWS||t;return!i||"object"!=typeof i&&"function"!=typeof i?t:i})(e,this.fallbackAuthority);return this.shared&&this.authority===t||(this.shared&&(this.shared.listeners.delete(this.sharedUpdate),this.shared.references.delete(this.referenceOwner)),this.authority=t,this.shared=(e=>{let t=Yd.get(e);return t||(t={signed:{},queued:new Set,inFlight:new Map,retry:new Map,listeners:new Set,settlers:new Set,references:new Map},Yd.set(e,t)),t})(t),this.disposed||(this.shared.listeners.add(this.sharedUpdate),this.shared.references.set(this.referenceOwner,new Set(this.referenced)))),this.shared}start(e,t){this.disposed=!1,this.referenced=new Set(t());const i=this.bind(e());i.listeners.add(this.sharedUpdate),i.references.set(this.referenceOwner,new Set(this.referenced)),this.stopTimer(),this.resignTimer=setInterval(()=>this.resign(e(),t()),288e5)}dispose(){if(this.disposed=!0,this.stopTimer(),clearTimeout(this.batchTimer),this.shared){this.shared.listeners.delete(this.sharedUpdate),this.shared.references.delete(this.referenceOwner);for(const e of this.queued)this.shared.queued.delete(e)}this.queued.clear()}invalidate(e){const t=this.bind(e);clearTimeout(this.batchTimer),this.queued.clear(),t.queued.clear(),t.inFlight.clear(),t.retry.clear(),t.signed={}}stopTimer(){void 0!==this.resignTimer&&clearInterval(this.resignTimer),this.resignTimer=void 0}display(e,t){const i=As(t);if(!i.startsWith("/api/houseplan/content/"))return i;const s=this.bind(e);this.referenced.add(i),s.references.set(this.referenceOwner,new Set(this.referenced));const n=s.signed[i],o=n?this.now()-n.at:1/0;return o{const t=[...this.queued];this.queued.clear();for(const e of t)i.queued.delete(e);this.sign(e,t)},30))}sign(e,t){if(!t.length||!e?.callWS)return;const i=this.bind(e);for(const s of function(e,t){const i=Math.max(1,Math.floor(t)),s=[];for(let t=0;t{const s=i.inFlight.get(e);return!(void 0!==s&&t-s<15e3)&&(i.inFlight.set(e,t),!0)});n.length&&e.callWS({type:"houseplan/content/sign",paths:n}).then(e=>{const t=this.now();let s=0,o=!1;for(const a of n){const n=e?.urls?.[a];if("string"==typeof n&&n){const e=i.signed[a];e?.loaded&&e.url!==n?(e.pending={url:n,at:t},this.preloadReplacement(i,a,n,t)):(i.signed[a]={url:n,at:t,loaded:e?.url===n&&!!e.loaded},o=!0),i.retry.delete(a),s++}else this.backOff(i,a)}if(s&&(this.trimShared(i),o))for(const e of[...i.listeners])e()}).catch(()=>{for(const e of n)this.backOff(i,e)}).finally(()=>{for(const e of n)i.inFlight.get(e)===t&&i.inFlight.delete(e);for(const e of[...i.settlers])e()})}}preloadReplacement(e,t,i,s){const n=()=>{const n=e.signed[t];if(n?.pending?.url===i){e.signed[t]={url:i,at:s,loaded:!0},e.retry.delete(t),this.trimShared(e);for(const t of[...e.listeners])t()}},o=()=>{const s=e.signed[t];s?.pending?.url===i&&(delete s.pending,this.backOff(e,t))};if("undefined"==typeof Image)return void n();const a=new Image;a.onload=()=>{("function"==typeof a.decode?a.decode():Promise.resolve()).then(n).catch(o)},a.onerror=o,a.src=i}preloadCurrentImage(e,t,i){if(i.loaded)return Promise.resolve(!0);if(i.preload)return i.preload;if("undefined"==typeof Image)return i.loaded=!0,Promise.resolve(!0);const s=i.url;return i.preload=new Promise(n=>{const o=new Image;let a=!1;const r=o=>{if(a)return;a=!0,clearTimeout(l);const r=e.signed[t];if(r===i&&delete r.preload,o&&r===i&&r.url===s){r.loaded=!0;for(const t of[...e.listeners])t();n(!0)}else o||r!==i||this.backOff(e,t),n(!1)};o.onload=()=>{("function"==typeof o.decode?o.decode():Promise.resolve()).then(()=>r(!0)).catch(()=>r(!1))},o.onerror=()=>r(!1);const l=setTimeout(()=>r(!1),jd);o.src=s}),i.preload}prepareImage(e,t){const i=As(t);if(!i.startsWith("/api/houseplan/content/"))return Promise.resolve(!0);const s=this.bind(e),n=()=>{const t=s.signed[i];if(t&&this.now()-t.atthis.now()&&!s.inFlight.has(i)&&!s.queued.has(i)?Promise.resolve(!1):null},o=n();return o?(a=o,new Promise(e=>{let t=!1;const i=i=>{t||(t=!0,clearTimeout(s),e(i))},s=setTimeout(()=>i(!1),jd);a.then(i).catch(()=>i(!1))})):new Promise(e=>{let t=!1,i=null;const o=i=>{t||(t=!0,clearTimeout(r),s.settlers.delete(a),e(i))},a=()=>{t||i||(i=n(),i&&i.then(o).catch(()=>o(!1)))},r=setTimeout(()=>o(!1),jd);s.settlers.add(a),a()});var a}backOff(e,t){const i=e.retry.get(t)?.delay||0,s=Math.min(6e4,i?2*i:2e3);e.retry.set(t,{notBefore:this.now()+s,delay:s})}trimShared(e){const t=Object.entries(e.signed);if(t.length<=512)return;const i=new Set(e.inFlight.keys());for(const t of e.references.values())for(const e of t)i.add(e);for(const[e,s]of t)(s.pending||s.preload)&&i.add(e);t.sort((e,t)=>t[1].at-e[1].at);const s=t.filter(([e])=>i.has(e));for(const e of t){if(s.length>=512)break;i.has(e[0])||s.push(e)}e.signed=Object.fromEntries(s)}resign(e,t){const i=this.bind(e);this.referenced=new Set(t),i.references.set(this.referenceOwner,new Set(this.referenced));const s=this.now(),n=new Set;for(const e of i.references.values())for(const t of e)n.add(t);const o={};for(const[e,t]of Object.entries(i.signed))n.has(e)&&s-t.at!!o[e]&&!o[e].pending))}markLoaded(e,t,i){const s=As(t);if(!s.startsWith("/api/houseplan/content/"))return;const n=this.bind(e).signed[s];if(n&&(!i||n.url===i)&&!n.loaded){n.loaded=!0;for(const t of[...this.bind(e).listeners])t()}}isReady(e,t){const i=As(t);if(!i.startsWith("/api/houseplan/content/"))return!0;const s=this.bind(e).signed[i];return!!s&&this.now()-s.ate.id===t)}function Zd(e="file"){const t="draw"===e;return{source:e,showBorders:t,showNames:t,displayTouched:!1}}function Qd(e,t){if(e.displayTouched)return{...e,source:t};const i="draw"===t;return{...e,source:t,showBorders:i,showNames:i}}function ep(e,t,i){return{...e,[t]:i,displayTouched:!0}}function tp(e,t,i){return[e[0]*t+e[1]*i+e[2],e[3]*t+e[4]*i+e[5]]}function ip(e,t){let i=0;for(const[s,n]of t){const t=tp(e,s[0],s[1]);i=Math.max(i,Math.hypot(t[0]-n[0],t[1]-n[1]))}return i}const sp=e=>{if(null==e||""===e)return null;const t=Number(e);return Number.isFinite(t)?t:null},np=e=>{const t=e,i=sp(Array.isArray(t)?t[0]:t?.x),s=sp(Array.isArray(t)?t[1]:t?.y);return null==i||null==s?null:[i,s]};function op(e){if(!Array.isArray(e))return null;const t=[];for(const i of e){const e=np(i);if(!e)return null;t.push(e)}if(t.length>1){const e=t[0],i=t[t.length-1];e[0]===i[0]&&e[1]===i[1]&&t.pop()}if(t.length<3)return null;let i=0,s=0,n=0;for(let e=0;ee+t[0],0)/t.length,t.reduce((e,t)=>e+t[1],0)/t.length]:[s/(3*i),n/(3*i)]}function ap(e){if(!Array.isArray(e))return null;const t=[];for(const i of e){const e=np(i);if(!e)return null;t.push(e)}if(t.length>1){const e=t[0],i=t[t.length-1];e[0]===i[0]&&e[1]===i[1]&&t.pop()}const i=op(t);if(!i)return null;const s=t.map(e=>e[0]),n=t.map(e=>e[1]);return{centroid:i,bbox:[Math.min(...s),Math.min(...n),Math.max(...s),Math.max(...n)]}}function rp(e){let t=e;if(t&&"object"==typeof t&&!Array.isArray(t)){const e=t;t=e.path??e.points??t}if(!Array.isArray(t)||!t.length)return[];let i=(t.some(e=>(e=>Array.isArray(e)?e.length>=2&&!Array.isArray(e[0])&&("object"!=typeof e[0]||null==e[0]):!!e&&"object"==typeof e&&("x"in e||"y"in e))(e))?[t]:t.filter(Array.isArray)).map(e=>e.map(np).filter(e=>null!=e)).filter(e=>e.length>=2);i.length>64&&(i=i.slice(-64));const s=i.reduce((e,t)=>e+t.length,0);if(s<=4e3)return i.map(e=>e.slice());const n=4e3-2*i.length,o=i.map(e=>e.length-2),a=o.reduce((e,t)=>e+t,0),r=o.map(e=>n*e/a),l=r.map(Math.floor);let c=n-l.reduce((e,t)=>e+t,0);const h=r.map((e,t)=>({index:t,fraction:e-Math.floor(e)})).sort((e,t)=>t.fraction-e.fraction||e.index-t.index);for(let e=0;efunction(e,t){if(t>=e.length)return e.slice();if(t<=2)return[e[0],e[e.length-1]];const i=[];for(let s=0;se.slice()),i=t[t.length-1];return i?(i.length>2?i.pop():t.pop(),t):[]}function hp(e){if(!e)return null;const t=e.vacuum_position||e.robot_position||null,i=t&&null!=sp(t.x)&&null!=sp(t.y)?{x:sp(t.x),y:sp(t.y),a:sp(t.a??t.angle??t.theta)}:null,s=rp(e.path?.path??e.path?.points??e.path),n=[],o=e.rooms,a=Array.isArray(o)?o.map((e,t)=>[String(e?.id??t),e]):o&&"object"==typeof o?Object.entries(o):[];for(const[e,t]of a){if(!t||"object"!=typeof t)continue;const i=String(t.name??t.label??"").trim(),s=[[sp(t.cx),sp(t.cy)],[sp(t.center?.x),sp(t.center?.y)],[sp(t.x),sp(t.y)]].find(([e,t])=>null!=e&&null!=t);let o=s?.[0]??null,a=s?.[1]??null;const r=ap(t.outline),l=sp(t.x0),c=sp(t.y0),h=sp(t.x1),d=sp(t.y1),p=(null!=l&&null!=c&&null!=h&&null!=d?[Math.min(l,h),Math.min(c,d),Math.max(l,h),Math.max(c,d)]:null)||r?.bbox||null;if(null!=o&&null!=a||!r||([o,a]=r.centroid),null!=o&&null!=a||!p||(o=(p[0]+p[2])/2,a=(p[1]+p[3])/2),i&&null!=o&&null!=a){const t={id:e,name:i,cx:o,cy:a};p&&([t.x0,t.y0,t.x1,t.y1]=p),n.push(t)}}const r=function(e){return String(e.map_name??e.current_map??e.map_index??e.selected_map??"default")}(e);return i||n.length||s.length?{pos:i,path:s,rooms:n,mapId:r}:null}function dp(e,t,i){if(!e||!t)return null;const s=t.attributes||{},n=e.split(".")[0]||"",o=null!=i?.platform?String(i.platform):null,a=!!np(s.vacuum_position??s.robot_position),r=!(!s.rooms||"object"!=typeof s.rooms),l=rp(s.path?.path??s.path?.points??s.path).length>0,c=[s.map_name,s.current_map,s.map_index,s.selected_map].some(e=>null!=e),h="xiaomi_cloud_map_extractor"===o,d=r||l||c;let p=null;if(a?p="compatible":h?p="known_xcme_incomplete":d?p="partial":"camera"===n&&(p="camera"),!p)return null;const u=a?"camera"===n?300:200:h?100:d?50:0;return{entityId:e,name:String(s.friendly_name||e),platform:o,category:p,hasPosition:a,hasRooms:r,hasPath:l,hasMapId:c,score:u}}const pp=e=>e.toLowerCase().replace(/[\s_\-.,]+/g,"");function up(e,t){const i=new Map(t.map(e=>[pp(e.name),e])),s=[],n=[];for(const t of e){const e=i.get(pp(t.name));e&&(s.push([[t.cx,t.cy],[e.cx,e.cy]]),n.push(t.name))}if(s.length<3)return null;const o=function(e){if(e.length<3)return null;let t=0,i=0,s=0,n=0,o=0,a=0,r=0,l=0,c=0,h=0,d=0,p=0;for(const[[u,_],[m,g]]of e){if(![u,_,m,g].every(Number.isFinite))return null;t+=u*u,i+=u*_,s+=u,n+=_*_,o+=_,a+=1,r+=u*m,l+=_*m,c+=m,h+=u*g,d+=_*g,p+=g}const u=[t,i,s,i,n,o,s,o,a],_=e=>{const[t,i,s,n,o,a,r,l,c]=u,h=t*(o*c-a*l)-i*(n*c-a*r)+s*(n*l-o*r);if(!Number.isFinite(h)||Math.abs(h)<1e-9)return null;const d=[(o*c-a*l)/h,(s*l-i*c)/h,(i*a-s*o)/h,(a*r-n*c)/h,(t*c-s*r)/h,(s*n-t*a)/h,(n*l-o*r)/h,(i*r-t*l)/h,(t*o-i*n)/h];return[d[0]*e[0]+d[1]*e[1]+d[2]*e[2],d[3]*e[0]+d[4]*e[1]+d[5]*e[2],d[6]*e[0]+d[7]*e[1]+d[8]*e[2]]},m=_([r,l,c]),g=_([h,d,p]);if(!m||!g)return null;const f=[m[0],m[1],m[2],g[0],g[1],g[2]];return f.every(Number.isFinite)?f:null}(s);return o?{matrix:o,matched:n,residual:ip(o,s)}:null}function _p(e,t,i){const s=e[e.length-1];if(s&&s[0]===t[0]&&s[1]===t[1])return e;if(e.push(t),e.length<=600)return e;let n=function(e,t){if(e.length<3)return e.slice();const i=new Uint8Array(e.length);i[0]=i[e.length-1]=1;const s=[[0,e.length-1]];for(;s.length;){const[n,o]=s.pop(),[a,r]=e[n],[l,c]=e[o],h=l-a,d=c-r,p=Math.hypot(h,d)||1e-9;let u=0,_=-1;for(let t=n+1;tu&&(u=i,_=t)}_>0&&u>t&&(i[_]=1,s.push([n,_],[_,o]))}const n=[];for(let t=0;t600&&(n=n.filter((e,t)=>t%2==0||t===n.length-1)),n}function mp(e){return"cleaning"===e||"returning"===e||"on"===e}const gp={0:[1,0],90:[0,1],180:[-1,0],270:[0,-1]};function fp(e){const[t,i]=gp[e.rot]||[1,0],s=e.mir?-1:1;return[e.s*t*s,-e.s*i,e.ox,e.s*i*s,e.s*t,e.oy]}function vp(e){const t=e[0]*e[4]-e[1]*e[3];if(!Number.isFinite(t)||Math.abs(t)<1e-12)return null;const i=t<0,s=Math.sqrt(Math.abs(t));let n=180*Math.atan2(-e[1],e[4])/Math.PI;return n=(90*Math.round(n/90)%360+360)%360,{ox:e[2],oy:e[5],s:s,rot:n,mir:i}}function bp(e,t,i,s){const[n,o]=tp(fp(t),i,s),a=fp({...e,ox:0,oy:0}),[r,l]=tp(a,i,s);return{...e,ox:n-r,oy:o-l}}function yp(e){const t=e?.trail_mode;return"never"===t||"cleaning"===t||"always"===t?t:!1===e?.trail?"never":"cleaning"}const wp=new Set(["group","template","derivative","min_max","threshold","integration","statistics","trend","utility_meter","tod","switch_as_x","schedule"]);function kp(e){const{hass:t,devices:i,markers:s,showEntities:n,currentBinding:o,currentDeviceId:a,labels:r}=e,l=wo(s),c=new Set(s.filter(e=>e.removed).map(e=>e.binding)),h=new Set;for(const e of i)e.id!==a&&("device"===e.bindingKind&&e.bindingRef&&h.add(`device:${e.bindingRef}`),"entity"===e.bindingKind&&e.bindingRef&&h.add(`entity:${e.bindingRef}`));const d=new Set;for(const e of i)"device"===e.bindingKind&&e.name&&d.add(`${e.name.trim()}|${e.area||""}`);const p=[];for(const e of Object.values(t?.devices||{})){if(!e||"service"===e.entry_type)continue;const t=`device:${e.id}`;if(h.has(t))continue;const i=String(e.name_by_user||e.name||e.id).trim();t!==o&&!c.has(t)&&d.has(`${i}|${e.area_id||""}`)||p.push({value:t,label:i,sub:(e.model||r.device)+("Group"===e.model?r.z2mGroup:""),kind:"device",ref:e.id,areaId:e.area_id||"",model:e.model||""})}for(const[e,i]of Object.entries(t?.entities||{})){const s=`entity:${e}`;if(h.has(s))continue;if(ko(t,e,l)&&!c.has(s))continue;const n=wp.has(i?.platform),o="group"===i?.platform;if(!n&&!o)continue;if(i?.hidden&&!c.has(s))continue;const a=t?.states?.[e];p.push({value:s,label:i?.name||a?.attributes?.friendly_name||e,sub:`${e.split(".")[0]} · ${o?r.group:r.helper}`,kind:"entity",ref:e,areaId:i?.area_id||i?.device_id&&t?.devices?.[i.device_id]?.area_id||"",model:"",parentDeviceId:i?.device_id||void 0})}if(n){const e=new Set(p.map(e=>e.value));for(const[i,s]of Object.entries(t?.entities||{})){const n=`entity:${i}`;if(h.has(n)||e.has(n)||s?.hidden&&!c.has(n))continue;const o=!!s?.device_id&&l.devices.has(s.device_id);if(ko(t,i,l)&&!c.has(n)&&!o)continue;const a=t?.states?.[i],d=s?.device_id?t?.devices?.[s.device_id]:null,u=d?String(d.name_by_user||d.name||""):"";p.push({value:n,label:s?.name||a?.attributes?.friendly_name||i,sub:`${i.split(".")[0]} · ${r.entity}${u?` · ${u}`:""}`,kind:"entity",ref:i,areaId:s?.area_id||d?.area_id||"",model:"",parentDeviceId:s?.device_id||void 0})}}return p.sort((e,t)=>e.label.localeCompare(t.label)||e.value.localeCompare(t.value))}const xp={kind:"active",enabledEntityIds:[],allEntityIds:[]};function $p(e){return e.startsWith("entity:")?"entity":"device"}function Sp(e){const t=e.binding.indexOf(":"),i=t>=0?e.binding.slice(t+1):"",s=Object.keys(e).filter(e=>!["id","binding","hidden"].includes(e));return e.id===`h${i}`&&0===s.length}const Mp={offStates:["off"],unknownUsesToggle:!0},Cp={light:Mp,switch:Mp,fan:Mp,humidifier:Mp,input_boolean:Mp,automation:Mp,remote:Mp,group:Mp,climate:{...Mp,featureMasks:{turn_on:256,turn_off:128,toggle:384}},media_player:{...Mp,featureMasks:{turn_on:128,turn_off:256,toggle:384}},siren:{...Mp,featureMasks:{turn_on:1,turn_off:2,toggle:3}},vacuum:{...Mp,featureMasks:{turn_on:1,turn_off:2,toggle:3}},water_heater:{...Mp,featureMasks:{turn_on:8,turn_off:8,toggle:8}},camera:{...Mp,featureMasks:{turn_on:1,turn_off:1,toggle:1}}};function Dp(e){return e.slice(0,e.indexOf("."))}function Pp(e,t,i){if(!e?.services||"object"!=typeof e.services)return!1;const s=e.services?.[t];return!!s&&Object.prototype.hasOwnProperty.call(s,i)}function Tp(e,t,i){return Pp(e,t,i)?{domain:t,service:i}:Pp(e,"homeassistant",i)?{domain:"homeassistant",service:i}:null}function Ip(e,t){return e?.entities?.[t]||null}function Rp(e,t,i){const s=e?.states?.[i],n=Ip(t,i);return s?.attributes?.friendly_name||n?.name||n?.original_name||i}function Ap(e,t){const i=Ip(e,t);if(null!=i?.disabled_by)return!0;const s=i?.device_id?e?.devices?.[i.device_id]:null;return null!=s?.disabled_by}function zp(e,t){const i=e?.attributes?.supported_features;if(null==i||""===i)return!1;const s=Number(i);return Number.isFinite(s)&&(s&t)===t}function Fp(e,t,i){const s=Dp(i);if("lock"===s||"alarm_control_panel"===s)return!0;if("cover"!==s)return!1;const n=e?.states?.[i],o=Ip(t,i),a=String(n?.attributes?.device_class||o?.device_class||o?.original_device_class||"");return Ui.has(a)}function Np(e,t,i,s,n){return{ref:i,entityId:s,name:s?Rp(e,t,s):null,reason:n}}function Ep(e,t,i,s,n,o=null){return{target:null,skipped:Np(e,t,i,s,n),semantics:o,nextEffect:null,command:null}}function Op(e,t,i,s,n=i){const o=Dp(i);if("cover"!==o&&"valve"!==o)return function(e,t,i,s,n=i){const o=Dp(i),a=e?.states?.[i];if(Ap(t,i))return Ep(e,t,n,i,"ha-disabled","power");if(Fp(e,t,i))return Ep(e,t,n,i,"secure");if(!a)return Ep(e,t,n,i,"missing","power");if("unavailable"===a.state)return Ep(e,t,n,i,"unavailable","power");const r=Cp[o];if(!r)return Ep(e,t,n,i,"unsupported");let l,c;if("unknown"===a.state||""===a.state){if(!r.unknownUsesToggle)return Ep(e,t,n,i,"unsupported","power");l="toggle",c="toggle"}else r.offStates.includes(String(a.state))?(l="turn-on",c="turn_on"):(l="turn-off",c="turn_off");const h=r.featureMasks?.[c];if(h&&!zp(a,h))return Ep(e,t,n,i,"unsupported","power");const d=Tp(e,o,c);return d?{target:{entityId:i,name:Rp(e,t,i),state:String(a.state||""),via:s},skipped:null,semantics:"power",nextEffect:l,command:{...d,data:{entity_id:i}}}:Ep(e,t,n,i,"unsupported","power")}(e,t,i,s,n);if(Ap(t,i))return Ep(e,t,n,i,"ha-disabled",o);if(Fp(e,t,i))return Ep(e,t,n,i,"secure",o);const a=e?.states?.[i];if(!a)return Ep(e,t,n,i,"missing",o);if("unavailable"===a.state)return Ep(e,t,n,i,"unavailable",o);const r=function(e,t,i){const s=String(i?.state||""),n="closed"===s?{service:`open_${t}`,effect:"open",feature:1}:"open"===s?{service:`close_${t}`,effect:"close",feature:2}:"opening"===s||"closing"===s?{service:`stop_${t}`,effect:"stop",feature:8}:null;return n&&zp(i,n.feature)&&Pp(e,t,n.service)?n:zp(i,3)&&Pp(e,t,"toggle")?{service:"toggle",effect:"toggle"}:null}(e,o,a);if(!r)return Ep(e,t,n,i,"unsupported",o);return{target:{entityId:i,name:Rp(e,t,i),state:String(a.state||""),via:s},skipped:null,semantics:o,nextEffect:r.effect,command:{domain:o,service:r.service,data:{entity_id:i}}}}function Hp(e){return"entity"===e.bindingKind?e.bindingRef&&Ms(e.bindingRef)?[e.bindingRef]:[]:"virtual"===e.bindingKind?[]:eo(e)}function Lp(e){const t=e.marker?.toggle_entity;return t&&Hp(e).includes(t)?t:null}function Bp(e,t,i){const s=function(e,t){if("entity"===e.bindingKind&&e.bindingRef){const t=e.marker?.light_entity?to(e):null;return[...new Set([t,e.bindingRef].filter(e=>!!e))]}const i=e.entities.length?e.entities:e.allEntities||[],s=e.marker?.light_entity||e.primary&&Ms(e.primary)?to(e):null;return[...new Set([s,...Yn(t,i)].filter(e=>!!e))]}(i,t);if(!s.length)return null;const n="entity"===i.bindingKind?"binding":"device-role";if("binding"===n)return Op(e,t,s[0],n);let o=null,a=null;for(const i of s){const s=Op(e,t,i,n);if(s.command)return s;const r=s.skipped?.reason;if("missing"===r||"unavailable"===r||"secure"===r)return s;"ha-disabled"!==r?o||=s:a||=s}return o||a}function jp(e){const t=e.skipped?.reason;return"missing"===t?"unavailable":t||"unsupported"}function qp(e,t){return{origin:e,kind:"none",semantics:null,targets:[],skippedTargets:[],noneReason:t,nextEffect:null,command:null}}function Wp(e,t){return"cover"===e||"toggle"===e?"toggle":"more-info"===e||"run"===e||"info"===e?e:(null==e||""===e)&&"light"===t?"toggle":"info"}function Up(e,t){return{origin:e,kind:"single",semantics:t.semantics,targets:t.target?[t.target]:[],skippedTargets:t.skipped?[t.skipped]:[],noneReason:t.command?null:jp(t),nextEffect:t.nextEffect,command:t.command}}function Gp(e,t,i,s=[]){const n=new Map,o=[],a=new Set,r=e=>{const t=`${e.ref}\n${e.entityId||""}\n${e.reason}`;a.has(t)||(a.add(t),o.push(e))};for(const e of s)r(e);for(const s of i){const i=Op(e,t,s.entityId,s.via,s.ref);i.target?n.set(s.entityId,n.get(s.entityId)||i.target):i.skipped&&r(i.skipped)}const l=[...n.values()];if(!l.length)return{origin:"explicit-toggle",kind:"group",semantics:"group-power",targets:l,skippedTargets:o,noneReason:o.length&&o.every(e=>"secure"===e.reason)?"secure":"configured-targets-missing",nextEffect:null,command:null};const c=l.some(e=>"on"===e.state)?"turn_off":"turn_on",h=Tp(e,"homeassistant",c);return h?{origin:"explicit-toggle",kind:"group",semantics:"group-power",targets:l,skippedTargets:o,noneReason:null,nextEffect:"turn_on"===c?"turn-on":"turn-off",command:{...h,data:{entity_id:l.map(e=>e.entityId)}}}:{origin:"explicit-toggle",kind:"group",semantics:"group-power",targets:[],skippedTargets:[...o,...l.map(i=>Np(e,t,i.entityId,i.entityId,"unsupported"))],noneReason:"unsupported",nextEffect:null,command:null}}function Vp(e){return String(e.marker?.id||e.id||"")}function Kp(e){const{hass:t,device:i}=e,s=e.registryHass||t,n=function(e){return"toggle"===e.tapAction?"explicit-toggle":"cover"===e.tapAction?"legacy-cover":!e.tapAction&&e.primary?.startsWith("light.")?"default-light":null}(i);if(!n)return null;const o=i.marker;if("explicit-toggle"===n&&bn(o)){const t=String(o?.id||i.id||""),s=ro(e.devices).get(t);if(s)return function(e,t){const i=e.registryHass||e.hass;return Gp(e.hass,i,t.driverEids.map(e=>({entityId:e,via:"control-marker-driver",ref:`marker:${t.markerId}`})))}(e,s);const a=yn(o,e.virtualLights);return{origin:n,kind:"single",semantics:"power",targets:[{entityId:"",name:i.name,state:a?"on":"off",via:"virtual-light"}],skippedTargets:[],noneReason:null,nextEffect:a?"turn-off":"turn-on",command:null,operation:{kind:"virtual-light",markerId:o.id}}}if("explicit-toggle"===n){if(Zn(i.marker?.binding,i.marker?.controls??i.controls,i.entities).length)return function(e,t=null){const{hass:i,device:s,devices:n}=e,o=e.registryHass||i,a=Zn(s.marker?.binding,s.marker?.controls??s.controls,s.entities),r=e.lightSources||ho(i,n,null,e.virtualLights),l=new Map;for(const e of r){if(!e.key.startsWith("marker:"))continue;const t=l.get(e.key)||[];t.push(e),l.set(e.key,t)}const c=ro(n),h=new Map;for(const e of n){const t=String(e.marker?.id||e.id||"");t&&h.set(t,e)}const d=[],p=[];t&&d.push({entityId:t,via:"entity"===s.bindingKind?"binding":"device-role",ref:t});for(const e of a){if(!e.startsWith("marker:")){Ms(e)?d.push({entityId:e,via:"control-entity",ref:e}):p.push(Np(i,o,e,e.includes(".")?e:null,"unsupported"));continue}const t=l.get(e)||[];if(!t.length){const t=h.get(e.slice(7));if("ha_disabled"===t?.bindingStatus?.kind){const s=t.bindingStatus.allEntityIds[0]||null;p.push({ref:e,entityId:s,name:t.name||(s?Rp(i,o,s):null),reason:"ha-disabled"})}else p.push(Np(i,o,e,null,"missing"));continue}const n=[...new Set(t.flatMap(e=>e.serviceEids))];if(n.length){for(const t of n)d.push({entityId:t,via:"control-entity",ref:e});continue}const a=t.some(e=>e.passive),r=e.slice(7),u=Vp(s),_=c.get(r)?.controllers.find(e=>Vp(e.device)===u);if(a&&_?.driverEids.length)for(const t of _.driverEids)d.push({entityId:t,via:"control-marker-driver",ref:e});else p.push(Np(i,o,e,null,a?"missing":"unsupported"))}return Gp(i,o,d,p)}(e,Lp(i))}if("legacy-cover"===n){const e=i.entities.find(e=>e.startsWith("cover."))||i.allEntities?.find(e=>e.startsWith("cover."));return e?Up(n,Op(t,s,e,"device-role")):qp(n,"no-actionable-entity")}const a=Lp(i),r=a?Op(t,s,a,"entity"===i.bindingKind?"binding":"device-role"):Bp(t,s,i);return r?Up(n,r):qp(n,i.virtual||"virtual"===i.bindingKind?"no-actionable-entity":"no-binding")}function Yp(e){if(!e)return[];const t=Array.isArray(e.data.entity_id)?e.data.entity_id:[e.data.entity_id];return[...new Set(t)].sort()}function Jp(e){return e?e.operation?e.operation:e.command?{kind:"ha-service",command:e.command}:null:null}const Xp=new Set(["missing","ha-disabled","unavailable"]);function Zp(e,t){const i=Jp(e),s=Jp(t);return!(!i||!s||i.kind!==s.kind)&&("virtual-light"===i.kind&&"virtual-light"===s.kind?i.markerId===s.markerId:"ha-service"===i.kind&&"ha-service"===s.kind&&function(e,t){const i=Yp(e),s=Yp(t);return i.length===s.length&&i.every((e,t)=>e===s[t])}(i.command,s.command))}const Qp=e=>!!e&&e.length>=2&&Number.isFinite(e[0])&&Number.isFinite(e[1]);function eu(e,t=5,i=5){return Ml(e.cm,t,i)/2}function tu(e,t,i=1,s=5,n=5,o=0){const a=e.host;if(!a||"partition"!==a.kind||"string"!=typeof a.id||!a.id)return{resolved:null,reason:"invalid-host"};const r=t.find(e=>e.id===a.id);if(!r)return{resolved:null,reason:"missing-partition"};if(!Qp(r.a)||!Qp(r.b)||!Number.isFinite(a.t)||a.t<0||a.t>1)return{resolved:null,reason:"invalid-position"};const l=r.b[0]-r.a[0],c=r.b[1]-r.a[1],h=Math.hypot(l,c),d=Number(e.length)*i;if(!(h>1e-9&&d>0&&Number.isFinite(d)))return{resolved:null,reason:"invalid-length"};const p=a.t*h;if(p-d/2h-o+1e-9)return{resolved:null,reason:"does-not-fit"};const u=l/h,_=c/h;let m=180*Math.atan2(c,l)/Math.PI;return m>=90?m-=180:m<-90&&(m+=180),{reason:null,resolved:{opening:e,host:a,partition:r,center:[r.a[0]+l*a.t,r.a[1]+c*a.t],angle:m,length:d,depth:Ml(r.cm,s,n),t:a.t,axis:{a:[r.a[0],r.a[1]],b:[r.b[0],r.b[1]],ux:u,uy:_,length:h}}}}function iu(e,t,i=1,s=5,n=5){return tu(e,t,i,s,n,0)}function su(e,t,i=1,s=5,n=5){const o=e.host,a="partition"===o?.kind?t.find(e=>e.id===o.id):void 0;if(!a)return iu(e,t,i,s,n);const r=tu(e,t,i,s,n,eu(a,s,n));return"does-not-fit"===r.reason?{resolved:null,reason:"does-not-fit-jamb"}:r}function nu(e,t){return!e||(e.length!==t.length||e.host?.kind!==t.host?.kind||e.host?.id!==t.host?.id||e.host?.t!==t.host?.t)}function ou(e){const{center:t,length:i,axis:s,host:n}=e,o=i/2;return{hostId:n.id,a:[t[0]-s.ux*o,t[1]-s.uy*o],b:[t[0]+s.ux*o,t[1]+s.uy*o],depth:e.depth}}function au(e,t=!1){const i=t?1:-1,s=e.depth/2;return{ox:-e.axis.uy*i*s,oy:e.axis.ux*i*s,cm:e.partition.cm,side:i}}function ru(e,t,i){return e.flatMap(e=>!Qp(e.a)||!Qp(e.b)||Math.hypot(e.b[0]-e.a[0],e.b[1]-e.a[1])<=1e-9?[]:[{roomId:"",a:[e.a[0],e.a[1]],b:[e.b[0],e.b[1]],key:`partition:${e.id}`,kind:"outer",cm:e.cm,open:!1,half:Ml(e.cm,t,i)/2,partitionHost:{kind:"partition",id:e.id}}])}function lu(e,t,i){const{center:s,length:n,axis:o}=e,a=n/2,r=t.flatMap(e=>{if(!e.kind||e.open)return[];const t=e.b[0]-e.a[0],n=e.b[1]-e.a[1],r=Math.hypot(t,n);if(!(r>i))return[];const l=t/r,c=n/r;if(Math.abs(o.ux*c-o.uy*l)>1e-6)return[];if(Math.abs((s[0]-e.a[0])*c-(s[1]-e.a[1])*l)>i)return[];const h=e=>(e[0]-s[0])*o.ux+(e[1]-s[1])*o.uy,d=h(e.a),p=h(e.b),u=Math.max(-a,Math.min(d,p)),_=Math.min(a,Math.max(d,p));return _>=u-i?[[u,_]]:[]});if(!r.length)return!1;r.sort((e,t)=>e[0]-t[0]||e[1]-t[1]);let l=-a;for(const[e,t]of r){if(e>l+i)return!1;if(l=Math.max(l,t),l>=a-i)return!0}return!1}function cu(e,t,i){return{...e,x:t.center[0]/i,y:t.center[1]/i,angle:t.angle}}const hu=.001;function du(e){return!!e&&e.length>=2&&Number.isFinite(e[0])&&Number.isFinite(e[1])}function pu(e){return`${e[0].toFixed(6)},${e[1].toFixed(6)}`}function uu(e,t,i){return Math.abs(e[0]-t[0])"room"!==e.kind&&i.some(i=>i!==e&&function(e,t,i){const s=e.b[0]-e.a[0],n=e.b[1]-e.a[1],o=Math.hypot(s,n);if(!(o>i))return!1;const a=s/o,r=n/o,l=t=>Math.abs((t[0]-e.a[0])*r-(t[1]-e.a[1])*a);if(l(t.a)>i||l(t.b)>i)return!1;const c=(t.a[0]-e.a[0])*a+(t.a[1]-e.a[1])*r,h=(t.b[0]-e.a[0])*a+(t.b[1]-e.a[1])*r;return Math.min(o,Math.max(c,h))-Math.max(0,Math.min(c,h))>i}(e,i,t))),n=s.map(e=>{const[t,i]=_u(e.a,e.b);return{a:t,b:i,key:`hidden|${gu(e,t,i)}`,sourceKind:e.kind,sourceId:e.id}}).sort((e,t)=>e.key.localeCompare(t.key)),o=s.flatMap(e=>[e.a,e.b].map((t,i)=>({point:[t[0],t[1]],key:`hidden|${mu(e)}|endpoint-${i}`,sourceKind:e.kind,sourceId:e.id}))).sort((e,t)=>e.key.localeCompare(t.key));return{segments:n,endpoints:o}}function yu(e){const t=e.epsilon??hu,i=e.roomCuts||[],s=new Map;for(const t of e.partitionCuts||[]){if("string"!=typeof t.hostId||!t.hostId||!du(t.a)||!du(t.b))continue;const e=s.get(t.hostId)||[];e.push([t.a[0],t.a[1],t.b[0],t.b[1]]),s.set(t.hostId,e)}const n=[];for(const[t,s]of pi(e.space.rooms).entries())s.length<4||n.push({a:[s[0],s[1]],b:[s[2],s[3]],kind:"room",id:`room-edge-${t}`,cuts:i});for(const t of e.space.room_drafts||[])if(t.id!==e.activeDraftId)for(let e=0;e+1vu(s,e,t)))continue;const e=pu(s);a.has(e)||a.set(e,{point:[s[0],s[1]],key:e})}}}return{segments:[...o.values()].sort((e,t)=>e.key.localeCompare(t.key)),endpoints:[...a.values()].sort((e,t)=>e.key.localeCompare(t.key))}}function wu(e,t,i){return t.some(t=>du(t)&&uu(e,t,i))}function ku(e,t,i){if(!i)return!0;const s=e-i.distance;return s<-1e-9||Math.abs(s)<=1e-9&&t.localeCompare(i.key)<0}function xu(e,t,i){const s=i.epsilon??hu,n=i.excludePoints||[],o=[...e.endpoints,...(i.extraEndpoints||[]).filter(e=>du(e.point)).map(e=>({point:[e.point[0],e.point[1]],key:e.key}))],a=new Map;for(const e of o){if(wu(e.point,n,s))continue;const t=pu(e.point),i=a.get(t);(!i||e.key.localeCompare(i.key)<0)&&a.set(t,e)}return[...a.values()].map(e=>({kind:"endpoint",point:[...e.point],key:e.key,distance:Math.hypot(t[0]-e.point[0],t[1]-e.point[1])})).filter(e=>e.distance<=i.tolerance).sort((e,t)=>e.distance-t.distance||e.key.localeCompare(t.key))}function $u(e,t){if(!e.length)return null;if(t>0){const i=new Set;for(let s=0;s1)return{kind:"ambiguous",candidate:null,conflicts:e.filter(e=>i.has(e.key)).map(e=>({point:[...e.point],key:e.key}))}}return{kind:"resolved",candidate:e[0],conflicts:[]}}function Su(e,t,i){const s=e.b[0]-e.a[0],n=e.b[1]-e.a[1],o=Math.hypot(s,n);if(!(o>0))return[...e.a];const a=s/o,r=n/o,l=Math.max(0,Math.min(o,(t[0]-e.a[0])*a+(t[1]-e.a[1])*r)),c=i>0?Math.max(0,Math.min(o,Math.round(l/i)*i)):l;return[e.a[0]+a*c,e.a[1]+r*c]}function Mu(e,t,i){if(!(du(t)&&du(i.anchor)&&i.tolerance>=0))return{kind:"none",candidate:null,conflicts:[]};const s=i.epsilon??hu,n=function(e,t){const i=t[0]-e[0],s=t[1]-e[1];if(!(Math.hypot(i,s)>Number.EPSILON))return null;const n=Math.round(Math.atan2(s,i)/(Math.PI/4))*(Math.PI/4);return[Math.cos(n),Math.sin(n)]}(i.anchor,t);if(!n)return{kind:"none",candidate:null,conflicts:[]};const o=xu(e,t,i).filter(e=>function(e,t,i,s){const n=e[0]-t[0],o=e[1]-t[1];return n*i[0]+o*i[1]>s&&Math.abs(n*i[1]-o*i[0])<=s*Math.max(Math.hypot(n,o),1)}(e.point,i.anchor,n,s)),a=$u(o,i.distinguishTolerance||0);if(a)return a;let r=null;for(const o of e.segments){const e=o.b[0]-o.a[0],a=o.b[1]-o.a[1],l=o.a[0]-i.anchor[0],c=o.a[1]-i.anchor[1],h=n[0]*a-n[1]*e;let d=null;if(Math.abs(h)<=s*Math.max(Math.hypot(e,a),1)){if(Math.abs(l*n[1]-c*n[0])>s*Math.max(Math.hypot(l,c),1))continue;const e=[o.a,o.b].map(e=>({point:e,along:(e[0]-i.anchor[0])*n[0]+(e[1]-i.anchor[1])*n[1]})).filter(e=>e.along>s).sort((e,t)=>e.along-t.along);e.length&&(d=[...e[0].point])}else{const t=(l*a-c*e)/h,o=(l*n[1]-c*n[0])/h;t>s&&o>=-s&&o<=1+s&&(d=[i.anchor[0]+n[0]*t,i.anchor[1]+n[1]*t])}if(!d)continue;const p=Math.hypot(t[0]-d[0],t[1]-d[1]);p>i.tolerance||wu(d,i.excludePoints||[],s)||!ku(p,o.key,r)||(r={kind:"line",point:d,key:o.key,distance:p,segment:o})}return r?{kind:"resolved",candidate:r,conflicts:[]}:{kind:"none",candidate:null,conflicts:[]}}const Cu=.001;function Du(e,t,i,s){const n=Number.isFinite(e)&&e>0?Math.floor(e):0,o=e=>"number"==typeof e&&Number.isFinite(e)&&e>=0?e:null,a=o(i),r=o(s)??0,l=[];let c=null;for(let e=0;e=2&&Number.isFinite(e[0])&&Number.isFinite(e[1])}function Iu(e,t,i,s){return e*s-t*i}function Ru(e){let t=0;for(let i=0;ie.localeCompare(t)),t[0]}function Eu(e,t,i){const s=[...e];for(let e=!0;e&&s.length>=3;){e=!1;for(let n=0;n=0){s.splice(n,1),e=!0;break}}}return s}function Ou(e,t){const i=e.map(e=>Math.max(0,Math.min(1,e))).sort((e,t)=>e-t),s=[];for(const e of i)(!s.length||Math.abs(e-s[s.length-1])>t)&&s.push(e);return s}function Hu(e,t){return[e.a[0]+(e.b[0]-e.a[0])*t,e.a[1]+(e.b[1]-e.a[1])*t]}function Lu(e,t,i){const s=function(e,t){const i=t.b[0]-t.a[0],s=t.b[1]-t.a[1],n=i*i+s*s;return n>0?((e[0]-t.a[0])*i+(e[1]-t.a[1])*s)/n:0}(e,t);if(s<-i||s>1+i)return null;const n=Hu(t,s);return Math.hypot(n[0]-e[0],n[1]-e[1])<=i?Math.max(0,Math.min(1,s)):null}function Bu(e,t,i,s,n){const o=e.b[0]-e.a[0],a=e.b[1]-e.a[1],r=t.b[0]-t.a[0],l=t.b[1]-t.a[1],c=t.a[0]-e.a[0],h=t.a[1]-e.a[1],d=Iu(o,a,r,l),p=Math.max(Math.hypot(o,a),Math.hypot(r,l),1);if(Math.abs(d)>n*p){const e=Iu(c,h,r,l)/d,t=Iu(c,h,o,a)/d;return void(e>=-n&&e<=1+n&&t>=-n&&t<=1+n&&(i.push(Math.max(0,Math.min(1,e))),s.push(Math.max(0,Math.min(1,t)))))}if(!(Math.abs(Iu(c,h,o,a))>n*p)){for(const s of[t.a,t.b]){const t=Lu(s,e,n);null!=t&&i.push(t)}for(const i of[e.a,e.b]){const e=Lu(i,t,n);null!=e&&s.push(e)}}}function ju(e){let t=e+1|0;return t^=t<<13,t^=t>>>17,t^=t<<5,t>>>0}function qu(e,t){return e.minY-t.minY||e.index-t.index}function Wu(e){return e.subtreeMaxY=Math.max(e.item.maxY,e.left?.subtreeMaxY??-1/0,e.right?.subtreeMaxY??-1/0),e}function Uu(e){const t=e.right;return e.right=t.left,t.left=Wu(e),Wu(t)}function Gu(e){const t=e.left;return e.left=t.right,t.right=Wu(e),Wu(t)}function Vu(e,t){return e?(qu(t,e.item)<0?(e.left=Vu(e.left,t),e.left.priority0)e.right=Ku(e.right,t);else{if(!e.left)return e.right;if(!e.right)return e.left;e.left.priority=t&&Yu(e.left,t,i,s),e.item.minY<=i&&e.item.maxY>=t&&s.push(e.item),e.item.minY<=i&&Yu(e.right,t,i,s))}function Ju(e,t=.001){const i=Number.isFinite(t)&&t>0?t:Cu,s=e.filter(e=>Tu(e.a)&&Tu(e.b)&&"string"==typeof e.key&&e.key.length>0&&Math.hypot(e.b[0]-e.a[0],e.b[1]-e.a[1])>i),n=s.map(()=>[0,1]);!function(e,t,i){const s=e.map((e,t)=>({index:t,minX:Math.min(e.a[0],e.b[0])-i,maxX:Math.max(e.a[0],e.b[0])+i,minY:Math.min(e.a[1],e.b[1])-i,maxY:Math.max(e.a[1],e.b[1])+i})),n=[...s].sort((e,t)=>e.minX-t.minX||e.minY-t.minY||e.index-t.index),o=[...s].sort((e,t)=>e.maxX-t.maxX||e.index-t.index),a=new Set;let r=null,l=0;for(const s of n){for(;le.index-t.index);for(const o of n)Bu(e[o.index],e[s.index],t[o.index],t[s.index],i);a.add(s.index),r=Vu(r,s)}}(s,n,i);const o=new Map;for(let e=0;e({key:e,a:t.a,b:t.b,sourceKeys:[...t.sourceKeys].sort((e,t)=>e.localeCompare(t))})).sort((e,t)=>e.key.localeCompare(t.key))}function Xu(e,t=.001){const i=Number.isFinite(t)&&t>0?t:Cu,s=Ju(e,i),n=new Map,o=new Map,a=new Map(s.map(e=>[e.key,e]));for(const e of s){const t=zu(e.a,i),s=zu(e.b,i);n.set(t,e.a),n.set(s,e.b),o.has(t)||o.set(t,new Set),o.has(s)||o.set(s,new Set),o.get(t).add(s),o.get(s).add(t)}const r=new Map;for(const[e,t]of o){const i=n.get(e);r.set(e,[...t].sort((e,t)=>{const s=n.get(e),o=n.get(t);return Math.atan2(s[1]-i[1],s[0]-i[0])-Math.atan2(o[1]-i[1],o[0]-i[0])||e.localeCompare(t)}))}const l=new Set,c=new Map,h=(e,t)=>`${e}>${t}`;for(const e of s){const t=[zu(e.a,i),zu(e.b,i)];for(const[e,o]of[t,[t[1],t[0]]]){if(l.has(h(e,o)))continue;const t=[],d=[];let p=e,u=o,_=!1;for(let i=0;i<=2*s.length+2;i++){const i=h(p,u);if(l.has(i)){_=p===e&&u===o;break}l.add(i),t.push(p),d.push(Fu(p,u));const s=r.get(u)||[],n=s.indexOf(p);if(n<0||!s.length)break;if(p=u,u=s[(n-1+s.length)%s.length],p===e&&u===o){_=!0;break}}if(!_||new Set(t).size<3||new Set(t).size!==t.length)continue;const m=t.map(e=>n.get(e)),g=Ru(m);if(!(g>i*i))continue;const f=Nu(Eu(t,n,i)),v=new Set;for(const e of d)for(const t of a.get(e)?.sourceKeys||[])v.add(t);const b={ring:m,key:f,area:g,atomKeys:[...d],sourceKeys:[...v].sort((e,t)=>e.localeCompare(t))};c.has(f)||c.set(f,b)}}return{atoms:s,faces:[...c.values()].sort((e,t)=>e.area-t.area||e.key.localeCompare(t.key))}}function Zu(e,t,i=.001){if(!Tu(t))return null;const s=e.faces.filter(e=>{if(e.ring.some((s,n)=>function(e,t,i,s){const n=i[0]-t[0],o=i[1]-t[1],a=n*n+o*o;if(!(a>0))return Math.hypot(e[0]-t[0],e[1]-t[1])<=s;const r=((e[0]-t[0])*n+(e[1]-t[1])*o)/a;return!(r<0||r>1)&&Math.hypot(t[0]+n*r-e[0],t[1]+o*r-e[1])<=s}(t,s,e.ring[(n+1)%e.ring.length],i)))return!1;let s=!1;for(let i=0,n=e.ring.length-1;it[1]!=a[1]>t[1]&&t[0]<(a[0]-o[0])*(t[1]-o[1])/(a[1]-o[1])+o[0]&&(s=!s)}return s});return[...s].sort((e,t)=>e.area-t.area||e.key.localeCompare(t.key))[0]||null}function Qu(e,t,i){const s=i[0]-t[0],n=i[1]-t[1],o=s*s+n*n,a=o>0?Math.max(0,Math.min(1,((e[0]-t[0])*s+(e[1]-t[1])*n)/o)):0;return Math.hypot(t[0]+s*a-e[0],t[1]+n*a-e[1])}function e_(e,t,i=.5){let s=Math.abs(e-t)%180;return s>90&&(s=180-s),s<=i}function t_(e,t,i,s){const n=e.filter(e=>"outer"===e.kind&&!e.open&&e.cm>0).map(e=>({interval:e,reusePartitionId:t.find(t=>function(e,t,i){return!(Math.abs(e.cm-t.cm)>1e-6)&&(e_(180*Math.atan2(e.b[1]-e.a[1],e.b[0]-e.a[0])/Math.PI,180*Math.atan2(t.b[1]-t.a[1],t.b[0]-t.a[0])/Math.PI,.001)&&Qu(t.a,e.a,e.b)<=i&&Qu(t.b,e.a,e.b)<=i)}(t,e,s))?.id||null})),o=new Map;for(const t of i){if("partition"===t.host?.kind)continue;const i=e.map(e=>{const i=180*Math.atan2(e.b[1]-e.a[1],e.b[0]-e.a[0])/Math.PI;return{interval:e,distance:e_(t.angle,i)?Qu([t.x,t.y],e.a,e.b):1/0}}).filter(e=>e.distance<=s).sort((e,t)=>e.distance-t.distance||e.interval.key.localeCompare(t.interval.key)),n=i[0]?.interval;"outer"===n?.kind&&!n.open&&n.cm>0&&o.set(t.id,n.key)}return{materialize:n,openingIntervals:o,removeOpeningIds:[...o.keys()].sort((e,t)=>e.localeCompare(t))}}function i_(e,t){const i=t.b[0]-t.a[0],s=t.b[1]-t.a[1],n=i*i+s*s;return n>0?Math.max(0,Math.min(1,((e[0]-t.a[0])*i+(e[1]-t.a[1])*s)/n)):0}const s_=e=>!e.key.startsWith("static:room|");function n_(e,t){const i=t.b[0]-t.a[0],s=t.b[1]-t.a[1],n=i*i+s*s;if(!(n>0))return null;const o=((e[0]-t.a[0])*i+(e[1]-t.a[1])*s)/n,a=[t.a[0]+i*o,t.a[1]+s*o];return{point:a,t:o,distance:Math.hypot(a[0]-e[0],a[1]-e[1])}}function o_(e,t){return t>0?Math.hypot(e[0]-Math.round(e[0]/t)*t,e[1]-Math.round(e[1]/t)*t):0}function a_(e,t,i){if(s_(e.source)!==s_(t.source))return s_(e.source)?e:t;const s=o_(e.point,i),n=o_(t.point,i);return Math.abs(s-n)>1e-9?s>n?e:t:function(e,t){return e[0]-t[0]||e[1]-t[1]}(e.point,t.point)>0?e:t}function r_(e){return[e.from[0],e.from[1],e.to[0],e.to[1],e.targetKind].map(e=>"number"==typeof e?e.toFixed(6):e).join("|")}function l_(e,t){return e.map(e=>e.key!==t.sourceKey?e:{...e,[t.endpoint]:[...t.to]})}function c_(e,t){const i=t.epsilon??.001;if(!(t.maxDistance>=0)||!t.point&&!t.requiredSourceKey)return{kind:"none"};const s=new Map,n=e.flatMap(e=>[{source:e,endpoint:"a",point:e.a},{source:e,endpoint:"b",point:e.b}]);for(let e=0;ei)||l>t.maxDistance)continue;const c=a_(a,r,t.gridStep||0),h=c===a?r:a;if(!s_(c.source))continue;const d={sourceKey:c.source.key,endpoint:c.endpoint,from:[c.point[0],c.point[1]],to:[h.point[0],h.point[1]],targetSourceKey:h.source.key,targetKind:"endpoint",distance:l};s.set(r_(d),d)}for(const o of n)if(s_(o.source))for(const n of e){if(n===o.source)continue;const e=n_(o.point,n);if(!e||e.t<=i||e.t>=1-i||!(e.distance>i)||e.distance>t.maxDistance)continue;const a={sourceKey:o.source.key,endpoint:o.endpoint,from:[o.point[0],o.point[1]],to:e.point,targetSourceKey:n.key,targetKind:"line",distance:e.distance};s.set(r_(a),a)}const o=[];for(const n of s.values()){const s=Xu(l_(e,n),i),a=t.point?Zu(s,t.point,i):s.faces.find(e=>!t.requiredSourceKey||e.sourceKeys.includes(t.requiredSourceKey))||null;a&&o.push({proposal:n,face:a})}if(o.sort((e,t)=>e.proposal.distance-t.proposal.distance||r_(e.proposal).localeCompare(r_(t.proposal))),!o.length)return{kind:"none"};return new Set(o.map(e=>`${r_(e.proposal)}|${e.face.key}`)).size>1?{kind:"ambiguous",proposals:o.map(e=>e.proposal)}:{kind:"repair",...o[0]}}const h_=1e-5;function d_(e){const t=e.map(()=>[]);for(let i=0;i=1-1e-9||p<=1e-9||p>=1-1e-9||(t[i].push(d),t[a].push(p))}}const i=[];for(let s=0;se-t),1];for(let e=1;e{const i=t[2]-t[0],s=t[3]-t[1],n=i*i+s*s;if(!(n>0))return Math.hypot(e[0]-t[0],e[1]-t[1]);const o=Math.max(0,Math.min(1,((e[0]-t[0])*i+(e[1]-t[1])*s)/n));return Math.hypot(e[0]-(t[0]+o*i),e[1]-(t[1]+o*s))},__=(e,t,i,s)=>{const n=s[2]-s[0],o=s[3]-s[1],a=t*o-i*n;if(Math.abs(a)<1e-12)return 1/0;const r=s[0]-e[0],l=s[1]-e[1],c=(r*o-l*n)/a;if(!(c>1e-9))return 1/0;const h=(r*i-l*t)/a;return h<-1e-9||h>1+1e-9?1/0:c};function m_(e,t,i,s=96){if(!(t>0&&Number.isFinite(e[0])&&Number.isFinite(e[1])))return[];const n=[];for(const s of i){if(!s||s.length<4)continue;if(![s[0],s[1],s[2],s[3]].every(Number.isFinite))continue;const i=u_(e,s);if(i<1e-7)return[];i>t||n.push(s)}const o=[],a=Math.max(12,Math.round(s));for(let e=0;ee-t);const l=[];let c=Number.NEGATIVE_INFINITY;for(const i of o){if(i-c<1e-9)continue;c=i;const s=Math.cos(i),o=Math.sin(i);let a=t;for(const t of n){const i=__(e,s,o,t);i=3?l:[]}class g_ extends he{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return pa(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},t=this._floorToken;return t?e.floor=t:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],t=this._lang,i=[{value:"",label:ua(t,"editor.floor_none")}],s=this._floorToken;s&&i.push({value:s,label:ua(t,"editor.floor_index",{index:String(this._config?.floor)})});const n="string"==typeof this._config?.floor?this._config.floor:"";n&&!e.some(e=>e.value===n)&&i.push({value:n,label:n}),i.push(...e);const o="string"==typeof this._config?.default_floor?this._config.default_floor:"",a=[...e];return o&&!e.some(e=>e.value===o)&&a.unshift({value:o,label:o}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:i}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:a}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:da(ua(t,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return G;this._loadSpaces();const e=this._lang,t={title:ua(e,"editor.title"),floor:ua(e,"editor.floor"),default_floor:ua(e,"editor.default_floor"),language:ua(e,"editor.language"),icon_size:ua(e,"editor.icon_size"),show_temperature:ua(e,"editor.show_temperature"),live_states:ua(e,"editor.live_states"),show_signal:ua(e,"editor.show_signal"),kiosk:ua(e,"editor.kiosk"),cycle:ua(e,"editor.cycle")},i=this._schema,s=function(e,t,i){if(!i||null===t)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||t.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),n=e=>q``}const Br={color:"#607d8b",opacity:1,widthCm:3.6,fill:!1,fillColor:"#607d8b",fillOpacity:.25},jr=(e,t=1)=>{const i=Number(e);return Number.isFinite(i)?Math.min(1,Math.max(0,i)):t},qr=e=>{let t=Number(e);return Number.isFinite(t)?(t=(t%360+360)%360,t>180?t-360:t):0};function Wr(e,t,i){return qr(t)?[e.x,e.y]:i([e.x,e.y])}const Ur=(e,t,i)=>{const s=Number(e),n=Number.isFinite(t)&&t>0?t:5;return Number.isFinite(s)&&s>0?s/n*i:0},Gr=(e,t,i)=>{const s=Number(e),n=Number.isFinite(t)&&t>0?t:5;return Number.isFinite(s)&&i>0?s/i*n:0},Vr=(e,t,i,s=Br.widthCm)=>{const n=Number(e?.width_cm);if(Number.isFinite(n)&&n>0)return n;const o=Number(e?.width);return Number.isFinite(o)&&o>0?Gr(o,t,i):s};function Kr(e,t){return{color:e.color,opacity:jr(e.opacity),width_cm:Math.max(.1,Math.min(100,Number(e.widthCm)||.1)),...t?{fill:e.fill,fill_color:e.fillColor,fill_opacity:jr(e.fillOpacity,.25)}:{}}}function Yr(e){const t=e.x+e.w/2,i=e.y+e.h/2,s=qr(e.angle)*Math.PI/180,n=Math.cos(s),o=Math.sin(s),a=(e,s)=>{const a=e-t,r=s-i;return[t+a*n-r*o,i+a*o+r*n]};return[a(e.x,e.y),a(e.x+e.w,e.y),a(e.x+e.w,e.y+e.h),a(e.x,e.y+e.h)]}function Jr(e){const t=Yr(e),i=(e,t)=>[(e[0]+t[0])/2,(e[1]+t[1])/2],s=[(t[0][0]+t[2][0])/2,(t[0][1]+t[2][1])/2];return{points:[...t,i(t[0],t[1]),i(t[1],t[2]),i(t[2],t[3]),i(t[3],t[0]),s],segments:t.map((e,i)=>({a:e,b:t[(i+1)%4]}))}}function Xr(e,t,i,s,n,o,a,r){const l=qr(e.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l),d=-Math.sin(l),p=Math.cos(l),u=e.x+e.w/2,_=e.y+e.h/2,m=t>0?-e.w/2:e.w/2,g=i>0?-e.h/2:e.h/2,f=u+m*c+g*d,v=_+m*h+g*p,b=s-f,y=n-v;let w=(b*c+y*h)*(t>0?1:-1),k=(b*d+y*p)*(i>0?1:-1),x=null;if(o){const t=w/Math.max(e.w,r),i=k/Math.max(e.h,r);x=Math.max(r/Math.max(e.w,r),r/Math.max(e.h,r),t,i),w=e.w*x,k=e.h*x}if(a>0)if(o){const t=Math.max(1,Math.round(e.w/a)),i=Math.max(1,Math.round(e.h/a));if(Math.abs(e.w-t*a)<1e-6&&Math.abs(e.h-i*a)<1e-6){const s=(e,t)=>{let i=Math.abs(e),s=Math.abs(t);for(;s;)[i,s]=[s,i%s];return Math.max(1,i)},n=1/s(t,i),o=Math.max(r/e.w,r/e.h),a=Math.max(o,Math.round((x??1)/n)*n);w=e.w*a,k=e.h*a}else{e.w>=e.h?(w=Math.round(w/a)*a,k=w*(e.h/Math.max(e.w,r))):(k=Math.round(k/a)*a,w=k*(e.w/Math.max(e.h,r)))}}else w=Math.round(w/a)*a,k=Math.round(k/a)*a;w=Math.max(r,w),k=Math.max(r,k);return{x:f+(t>0?w/2:-w/2)*c+(i>0?k/2:-k/2)*d-w/2,y:v+(t>0?w/2:-w/2)*h+(i>0?k/2:-k/2)*p-k/2,w:w,h:k,angle:qr(e.angle)||void 0}}const Zr=(e,t,i)=>{const s=i[0]-t[0],n=i[1]-t[1],o=s*s+n*n;if(o<1e-12)return[...t];const a=Math.max(0,Math.min(1,((e[0]-t[0])*s+(e[1]-t[1])*n)/o));return[t[0]+s*a,t[1]+n*a]};const Qr=10**9,el=240,tl=1e-4;function il(e){if(Array.isArray(e))return e.map(e=>il(e));if(null!==e&&"object"==typeof e){const t={};for(const[i,s]of Object.entries(e))t[i]=il(s);return t}return e}function sl(e){if("number"!=typeof e||!Number.isFinite(e))return e;const t=(e<0||Object.is(e,-0)?-1:1)*(Math.floor(Math.abs(e)*Qr+.5)/Qr);return 0===t?0:t}function nl(e){if("number"!=typeof e||!Number.isFinite(e))return e;const t=e*el,i=Math.round(t);if(Math.abs(t-i)null!==al(e)):[]}function ll(e,t){for(const i of t)Object.prototype.hasOwnProperty.call(e,i)&&(e[i]=sl(e[i]))}function cl(e,t){for(const i of t)Object.prototype.hasOwnProperty.call(e,i)&&(e[i]=nl(e[i]))}function hl(e){if(Array.isArray(e))for(let t=0;t{for(const n of s){const s=t[n];"number"==typeof s&&Number.isFinite(s)&&i(s,e)}},s=t=>{if(Array.isArray(t))for(let s=0;s{if(Array.isArray(e))for(const t of e)s(t)};for(const i of rl(e.rooms))t(i,["x","y","w","h"]),n(i.poly);for(const t of rl(e.walls))s(t.a),s(t.b);for(const t of rl(e.wall_segments))s(t.a),s(t.b);for(const i of rl(e.openings))t(i,["x","y"]);for(const i of rl(e.decor))"line"===i.kind?t(i,["x1","y1","x2","y2"]):"rect"===i.kind||"ellipse"===i.kind||"furniture"===i.kind?t(i,["x","y","w","h"]):"text"===i.kind&&t(i,["x","y"]);for(const t of rl(e.room_drafts))n(t.points);for(const t of rl(e.partitions))s(t.a),s(t.b);for(const t of rl(e.wall_columns))s(t.center);for(const t of rl(e.open_spans))s(t.a),s(t.b)}const n=al(t);if(!n)return;const o=new Map;if(s)for(const e of rl(s.spaces))null!=e.id&&o.set(String(e.id),e);for(const e of Object.values(n)){const t=al(e);if(!t)continue;const s=null!=t.s&&o.get(String(t.s))||null;for(const e of["x","y"]){const n=t[e];"number"==typeof n&&Number.isFinite(n)&&i(n,s)}}}function ul(e){const t=Number(e?.cell_cm);return t>0?t:5}function _l(e,t={}){const i={canonicalized:0,far:0,maxShift:0,maxShiftCm:0,spaces:[],bySpace:new Map},s=rl(al(e)?.spaces);let n=5;for(const e of s)n=Math.max(n,ul(e));pl(e,t,(e,t)=>{const s=e*el,o=Math.abs(s-Math.round(s)),a=nl(e),r=o>0&&o=tl))return;const l=r&&"number"==typeof a?Math.abs(a-e):0;if(r?i.canonicalized++:i.far++,i.maxShift=Math.max(i.maxShift,l),i.maxShiftCm=Math.max(i.maxShiftCm,l*el*(t?ul(t):n)),!t?.id||!r)return;const c=String(t.id);let h=i.bySpace.get(c);h||(h={spaceId:c,space:String(t.title||c),canonicalized:0,far:0,maxShift:0,maxShiftCm:0},i.bySpace.set(c,h)),h.canonicalized++,h.maxShift=Math.max(h.maxShift,l),h.maxShiftCm=Math.max(h.maxShiftCm,l*el*ul(t))}),i.bySpace.size&&pl(e,t,(e,t)=>{if(!t?.id)return;const s=i.bySpace.get(String(t.id));if(!s)return;const n=e*el;Math.abs(n-Math.round(n))>=tl&&s.far++}),i.spaces=[...i.bySpace.values()];const{bySpace:o,...a}=i;return a}function ml(e){return function(e){const t=e,i=al(t);i&&cl(i,["x","y"]);return t}(il(e))}function gl(e){const t=e,i=al(t);if(!i)return t;for(const e of Object.values(i)){const t=al(e);t&&cl(t,["x","y"])}return t}function fl(e){const t=e,i=al(t);if(!i)return t;for(const e of rl(i.spaces)){ll(e,["plan_x","plan_y","plan_scale","plan_scale_x","plan_scale_y","plan_angle"]);for(const t of rl(e.rooms))cl(t,["x","y","w","h"]),dl(t.poly);for(const t of rl(e.walls))hl(t.a),hl(t.b);for(const t of rl(e.wall_segments))hl(t.a),hl(t.b);for(const t of rl(e.openings)){cl(t,["x","y"]),ll(t,["angle","length"]);const e=al(t.host);e&&ll(e,["t"])}for(const t of rl(e.decor))"line"===t.kind?cl(t,["x1","y1","x2","y2"]):"rect"===t.kind||"ellipse"===t.kind||"furniture"===t.kind?(cl(t,["x","y","w","h"]),ll(t,["angle"])):"text"===t.kind&&(cl(t,["x","y"]),ll(t,["scale","angle"]));for(const t of rl(e.room_drafts))dl(t.points);for(const t of rl(e.partitions))hl(t.a),hl(t.b);for(const t of rl(e.wall_columns))hl(t.center),"square"===t.shape&&ll(t,["angle"]);for(const t of rl(e.open_spans))hl(t.a),hl(t.b)}for(const e of rl(i.markers))ll(e,["angle"]);return t}const vl=15;function bl(e,t,i,s,n){const o=i[0]-e[0],a=i[1]-e[1],r=Math.hypot(o,a);if(!(r>0)||r<=n+1e-9)return null;const l=o/r,c=a/r,h=t=>{const s=(t[0]-e[0])*l+(t[1]-e[1])*c,o=(n-s)/(r-s);return!Number.isFinite(o)||o<-1e-9||o>1+1e-9?null:[t[0]+(i[0]-t[0])*o,t[1]+(i[1]-t[1])*o]},d=h(t),p=h(s);return d&&p?[[e[0],e[1]],t,d,p,s]:null}const yl=Math.sin(.25*Math.PI/180);function wl(e,t){return Number.isFinite(e)&&e>0&&Number.isFinite(t)&&t>0&&e*t<3}function kl(e){const t=Number(e)>0?Number(e):5;if(5===t)return 8;const i=5/t*8;return Math.min(80,Math.max(.5,i))}function xl(e,t){return Number.isFinite(e)&&e>0&&Number.isFinite(t)&&t>0&&e*t<2}function $l(e){return Number.isFinite(e)?Math.max(1,Math.min(100,e)):1}function Sl(e,t){return!Number.isFinite(e)||e<0?"":String(t?Math.round(e/2.54*100)/100:Math.round(100*e)/100)}function Ml(e,t,i){if(!Number.isFinite(e)||e<=0)return 0;const s=Number(t)>0?Number(t):5;return $l(e)/s*i}function Cl(e,t){return t>0&&Number.isFinite(e)?Math.round(e/t)*t:e}function Dl(e){return Math.max(1e-6*Math.abs(e),1e-9)}function Pl(e,t){if(!(t>0&&Number.isFinite(e)))return e;const i=Cl(e,t);return Math.abs(i-e)<=Dl(t)?i:e}function Tl(e,t){let i=t[0]-e[0],s=t[1]-e[1];const n=Math.hypot(i,s);return n<1e-12?[1,0]:(i/=n,s/=n,(i<-1e-12||Math.abs(i)<=1e-12&&s<0)&&(i=-i,s=-s),[i,s])}function Il(e,t,i){const s=[Pl(e[0],i),Pl(e[1],i)],n=[Pl(t[0],i),Pl(t[1],i)],o=Cl((s[0]+n[0])/2,i),a=Cl((s[1]+n[1])/2,i),[r,l]=Tl(s,n);let c=Math.atan2(l,r);c<0&&(c+=Math.PI);const h=Math.round(1800*c)/1800,d=i>0&&i<.01?6:i<1?4:2;return`${o.toFixed(d)},${a.toFixed(d)}@${h.toFixed(4)}`}function Rl(e,t,i,s){return 1===s?Il(e,t,i):Il([e[0]/s,e[1]/s],[t[0]/s,t[1]/s],i)}function Al(e,t){if(!Array.isArray(e.a)||!Array.isArray(e.b)||e.a.length<2||e.b.length<2)return null;const i=[Number(e.a[0]),Number(e.a[1]),Number(e.b[0]),Number(e.b[1])];if(!i.every(Number.isFinite))return null;const s=t>0?t:1;return[[i[0]*s,i[1]*s],[i[2]*s,i[3]*s]]}function zl(e,t,i,s,n){const o=n>0?n:1;return{key:Rl(e,t,s,o),cm:$l(i),a:[e[0]/o,e[1]/o],b:[t[0]/o,t[1]/o]}}function Fl(e,t){const i=t>0?t:1,s=[];for(const t of e){const e=t.key.lastIndexOf("@");if(e<0)continue;const[n,o]=t.key.slice(0,e).split(",").map(Number),a=Number(t.key.slice(e+1));[n,o,a].every(Number.isFinite)&&s.push({w:t,x:n*i,y:o*i,ang:a})}return s}function Nl(e,t){const[i,s]=Tl(e,t);let n=Math.atan2(s,i);return n<0&&(n+=Math.PI),n}function El(e,t){let i=Math.abs(e-t);return i>Math.PI/2&&(i=Math.PI-i),i<.02}function Ol(e,t,i,s,n=1){if(!e?.length)return null;const o=Rl(t,i,s,n),a=e.find(e=>e.key===o);if(a)return a;const r=n>0?n:1,l=Dl(s)*r,c=(e,t)=>Math.abs(e[0]-t[0])<=l&&Math.abs(e[1]-t[1])<=l;for(const s of e){const e=Al(s,r);if(e&&(c(e[0],t)&&c(e[1],i)||c(e[0],i)&&c(e[1],t)))return s}const h=(t[0]+i[0])/2,d=(t[1]+i[1])/2,p=Nl(t,i),u=Math.max(.5*s,1e-9)*r;for(const t of Fl(e,r))if(El(t.ang,p)&&Math.hypot(t.x-h,t.y-d)<=u)return t.w;return null}function Hl(e,t,i,s,n=1){const o=Ol(e,t,i,s,n);if(o&&o.cm>0)return $l(o.cm);const a=function(e,t,i,s,n){if(!e?.length)return null;const o=n>0?n:1,a=Math.hypot(i[0]-t[0],i[1]-t[1]);if(a<1e-12)return null;const r=Nl(t,i),l=Math.max(.5*s,1e-9)*o;let c=null;for(const s of e){if(!(s.cm>0))continue;const e=Al(s,o);if(!e)continue;const n=Math.hypot(e[1][0]-e[0][0],e[1][1]-e[0][1]);if(n<1e-12||!El(Nl(e[0],e[1]),r))continue;if(Sc(t[0],t[1],e[0][0],e[0][1],e[1][0],e[1][1])>l||Sc(i[0],i[1],e[0][0],e[0][1],e[1][0],e[1][1])>l)continue;if(n+l(o.has(e.key)||(e=>{const t=Al(e,s);if(!t)return!1;const[i,o]=t,r=o[0]-i[0],c=o[1]-i[1],h=Math.hypot(r,c);if(h<=l)return!1;if(!a.some(e=>{const t=[e[0],e[1]],s=[e[2],e[3]];return El(Nl(i,o),Nl(t,s))&&Sc(i[0],i[1],t[0],t[1],s[0],s[1])<=l&&Sc(o[0],o[1],t[0],t[1],s[0],s[1])<=l}))return!1;const d=(n||[]).some(e=>{const t=[e[0],e[1]],s=[e[2],e[3]];if(!El(Nl(i,o),Nl(t,s)))return!1;const n=e=>Math.abs((e[0]-i[0])*c-(e[1]-i[1])*r)/h;if(n(t)>l||n(s)>l)return!1;const a=h*h,d=((t[0]-i[0])*r+(t[1]-i[1])*c)/a,p=((s[0]-i[0])*r+(s[1]-i[1])*c)/a;return Math.min(1,Math.max(d,p))-Math.max(0,Math.min(d,p))>l/h});return!d})(e))&&e.cm>=1&&e.cm<=100)}function Bl(e,t,i,s=8){const[n,o]=Tl(e,t);let a=Math.atan2(o,n);a<0&&(a+=Math.PI);let r=i*Math.PI/180%Math.PI;r<0&&(r+=Math.PI);let l=Math.abs(a-r);return l>Math.PI/2&&(l=Math.PI-l),l<=s*Math.PI/180}function jl(e,t,i,s,n=1,o="affine",a){if(!e?.length)return[];if(t.length!==i.length)return"fixed-topology"===o&&a?.(),e.slice();const r=n>0?n:1,l=Math.max(.5*s,1e-9)*r,c=Math.max(s*r*1e-6,1e-9),h=[],d=new Map,p=new Map,u=(e,t,i)=>{const s=e.get(t)||new Set;s.add(i),e.set(t,s)};for(let e=0;e[e[0]+(t[0]-e[0])*i,e[1]+(t[1]-e[1])*i],m=(e,t)=>Math.hypot(e[0]-t[0],e[1]-t[1])<=c,g=(e,t)=>{if("fixed-topology"===o){const i=t.na[0]-t.oa[0],s=t.na[1]-t.oa[1],n=t.nb[0]-t.ob[0],o=t.nb[1]-t.ob[1];return Math.hypot(i-n,s-o)<=c?[e[0]+i,e[1]+s]:m(e,t.oa)?[...t.na]:m(e,t.ob)?[...t.nb]:[...e]}const i=Math.max(0,Math.min(1,((e[0]-t.oa[0])*t.dx+(e[1]-t.oa[1])*t.dy)/t.len2));return _(t.na,t.nb,i)},f=(e,t)=>{const[i,s]=Tl(e,t);return(t[0]-e[0])*i+(t[1]-e[1])*s>=0?[[...e],[...t]]:[[...t],[...e]]},v=[],b=[],y=(e,t,i)=>{if(Math.hypot(t[0]-e[0],t[1]-e[1])<=c)return;const[n,o]=f(e,t),a=$l(i);if(b.some(e=>e.entry.cm===a&&m(e.span[0],n)&&m(e.span[1],o)))return;const l=zl(n,o,a,s,r);v.push(l),b.push({entry:l,span:[n,o]})};for(const t of e){const e=Al(t,r);if(e){const[i,s]=f(e[0],e[1]),n=s[0]-i[0],a=s[1]-i[1],r=n*n+a*a,d=Math.sqrt(r);if(d<=c){v.push({...t,cm:$l(t.cm)});continue}const p=[];for(const e of h){if(!El(Nl(i,s),Nl(e.oa,e.ob)))continue;const t=e=>Math.abs((e[0]-i[0])*a-(e[1]-i[1])*n)/d;if(t(e.oa)>l||t(e.ob)>l)continue;const o=((e.oa[0]-i[0])*n+(e.oa[1]-i[1])*a)/r,h=((e.ob[0]-i[0])*n+(e.ob[1]-i[1])*a)/r,u=Math.max(0,Math.min(o,h)),_=Math.min(1,Math.max(o,h));(_-u)*d>c&&p.push({lo:u,hi:_,move:e})}if(!p.length){"fixed-topology"===o?v.push({...t}):y(i,s,t.cm);continue}const u=[0,1,...p.flatMap(({lo:e,hi:t})=>[e,t])].sort((e,t)=>e-t).filter((e,t,i)=>0===t||Math.abs(e-i[t-1])*d>c),b=[];for(let e=0;e+1r>=e.lo-1e-12&&r<=e.hi+1e-12);if(!l.length){b.push([o,a]);continue}const h=[g(o,l[0].move),g(a,l[0].move)],f=l.slice(1).some(e=>{const t=g(o,e.move),i=g(a,e.move);return!m(h[0],t)||!m(h[1],i)});b.push(f?[o,a]:h)}const w=(e,t)=>{const i=e[1][0]-e[0][0],s=e[1][1]-e[0][1],n=t[1][0]-t[0][0],o=t[1][1]-t[0][1],a=Math.hypot(i,s);return!(a<=c||i*n+s*o<=0)&&Math.abs(i*o-s*n)/a<=c},k=[];for(const e of b){const t=k[k.length-1];t&&m(t[1],e[0])&&w(t,e)?t[1]=e[1]:k.push([[...e[0]],[...e[1]]])}if("fixed-topology"===o&&1===k.length){const[i,s]=k[0];if(m(i,e[0])&&m(s,e[1])||m(i,e[1])&&m(s,e[0])){v.push({...t});continue}}for(const[e,i]of k)y(e,i,t.cm);continue}if("fixed-topology"===o){const e=p.get(t.key);if(1===e?.size){const i=[...e][0];v.push(i===t.key?{...t}:{...t,key:i});continue}const i=[Fl([t],r)[0]];1!==r&&i.push(Fl([t],1)[0]);const s=i.filter(Boolean).some(e=>h.some(t=>{if(!El(e.ang,Nl(t.oa,t.ob)))return!1;const i=((e.x-t.oa[0])*t.dx+(e.y-t.oa[1])*t.dy)/t.len2;return i>=-1e-6&&i<=1.000001&&Sc(e.x,e.y,t.oa[0],t.oa[1],t.ob[0],t.ob[1])<=l}));((e?.size||0)>1||s)&&a?.(),v.push({...t});continue}let i="";const n=d.get(t.key);if(1===n?.size&&(i=[...n][0]),!i){const e=Fl([t],r)[0];if(e){const t=new Set;for(const i of h){if(!El(e.ang,Nl(i.oa,i.ob)))continue;const n=((e.x-i.oa[0])*i.dx+(e.y-i.oa[1])*i.dy)/i.len2;if(n<-1e-6||n>1.000001)continue;if(Sc(e.x,e.y,i.oa[0],i.oa[1],i.ob[0],i.ob[1])>l)continue;const o=g([e.x,e.y],i),[a,c]=Tl(i.na,i.nb),h=Math.max(s*r,1e-6);t.add(Rl([o[0]-a*h,o[1]-c*h],[o[0]+a*h,o[1]+c*h],s,r))}1===t.size&&(i=[...t][0])}}v.push({...t,key:i||t.key,cm:$l(t.cm)})}return v}function ql(e,t,i,s,n=1,o="affine"){return jl(e,t,i,s,n,o)}function Wl(e,t,i,s=1,n=[]){const o=s>0?s:1,a=Math.abs(i),r=Math.max(a*o*tl,1e-9),l=(n||[]).flatMap(e=>{const t=Al(e,o);return t?t.map(e=>[...e]):[]}),c=e=>e.every((e,t)=>(e=>{if(!(a>0))return!0;const t=e/o/a;return Math.abs(t-Math.round(t))Math.abs(e-i[t])<=r)),h=[],d=e=>JSON.stringify([e.key,e.cm,e.a,e.b]);for(const i of e||[]){const e=Al(i,o);if(!e)continue;const[s,n]=e;if(![s[0],s[1],n[0],n[1]].every(Number.isFinite)||!c(s)||!c(n)){h.push(d(i));continue}const a=n[0]-s[0],l=n[1]-s[1],p=Math.hypot(a,l);if(p<=r){h.push(d(i));continue}const u=a/p,_=l/p,m=[];for(const e of t){const[t,i]=e;if(![t?.[0],t?.[1],i?.[0],i?.[1]].every(Number.isFinite))continue;const n=e=>Math.abs((e[0]-s[0])*_-(e[1]-s[1])*u);if(n(t)>r||n(i)>r)continue;const o=(t[0]-s[0])*u+(t[1]-s[1])*_,a=(i[0]-s[0])*u+(i[1]-s[1])*_,l=Math.max(0,Math.min(o,a)),c=Math.min(p,Math.max(o,a));c-l>r&&m.push([l,c])}m.sort((e,t)=>e[0]-t[0]||e[1]-t[1]);let g=0;for(const[e,t]of m){if(e>g+r)break;if(g=Math.max(g,t),g>=p-r)break}ge.key!==a);return null==s||s<1?r:[...r,zl(t,i,s,n,o)]}function Gl(e,t,i,s,n,o=[],a=1){let r=e?e.slice():[];for(const e of function(e,t,i,s,n=1,o=[]){const a=dc(e,t,i,s,n,o);if(!a)return[];const r=[];for(let e=0;e0||(c=Ul(c,t,i,s,n,a)))}return c}function Kl(e){const{a:t,b:i,halfDepth:s}=e;if(!Array.isArray(t)||!Array.isArray(i)||t.length<2||i.length<2||![t[0],t[1],i[0],i[1]].every(Number.isFinite))return null;const n=i[0]-t[0],o=i[1]-t[1],a=Math.hypot(n,o);if(!(a>1e-9&&s>0&&Number.isFinite(s)))return null;const r=-o/a*s,l=n/a*s;return[[t[0]+r,t[1]+l],[i[0]+r,i[1]+l],[i[0]-r,i[1]-l],[t[0]-r,t[1]-l]]}function Yl(e,t,i){return Math.hypot(e[0]-t[0],e[1]-t[1])<=i}function Jl(e,t,i){const s=t.b[0]-t.a[0],n=t.b[1]-t.a[1],o=s*s+n*n;if(!(o>i*i))return!1;const a=((e[0]-t.a[0])*s+(e[1]-t.a[1])*n)/o;if(!(a>0&&a<1))return!1;const r=[t.a[0]+s*a,t.a[1]+n*a];return Math.hypot(e[0]-r[0],e[1]-r[1])<=i}function Xl(e,t,i,s){const n=Math.hypot(t,i);if(!(n>1e-9&&s>0))return;const o=[t/n,i/n],a=e.find(e=>Math.abs(e.u[0]*o[1]-e.u[1]*o[0])<1e-9&&e.u[0]*o[0]+e.u[1]*o[1]>1-1e-9);a?a.halfDepth=Math.max(a.halfDepth,s):e.push({u:o,halfDepth:s})}function Zl(e,t){try{const i=oi(Dc(e),Dc(t));let s=null,n=0;for(const e of i||[]){const t=e?.[0]||[],i=Math.abs(nc(t));t.length>=4&&i>n&&(n=i,s=t.slice(0,-1).map(e=>[e[0],e[1]]))}return s}catch{return null}}function Ql(e,t=1e-6){const i=(e||[]).map((e,t)=>({segment:e,index:t})).filter(({segment:e})=>e&&Array.isArray(e.a)&&Array.isArray(e.b)&&e.a.length>=2&&e.b.length>=2&&e.a.every(Number.isFinite)&&e.b.every(Number.isFinite)&&Number.isFinite(e.halfDepth)&&e.halfDepth>0&&Math.hypot(e.b[0]-e.a[0],e.b[1]-e.a[1])>1e-9);if(i.length<2)return[];const s=Math.max(Number.isFinite(t)?t:0,1e-9),n=i.flatMap(({segment:e})=>[e.a,e.b]).map(e=>[e[0],e[1]]).sort((e,t)=>e[0]-t[0]||e[1]-t[1]),o=[];for(const e of n)o.some(t=>Yl(t,e,s))||o.push(e);const a=[];for(const e of o){const t=[];let n=!1;for(const{segment:o,index:a}of i){const i=Math.hypot(o.b[0]-o.a[0],o.b[1]-o.a[1]);Yl(e,o.a,s)?t.push({u:[(o.b[0]-o.a[0])/i,(o.b[1]-o.a[1])/i],halfDepth:o.halfDepth,length:i,index:a}):Yl(e,o.b,s)?t.push({u:[(o.a[0]-o.b[0])/i,(o.a[1]-o.b[1])/i],halfDepth:o.halfDepth,length:i,index:a}):Jl(e,o,s)&&(n=!0)}if(n||2!==t.length)continue;const[o,r]=t,l=o.u[0]*r.u[1]-o.u[1]*r.u[0];if(Math.abs(l)<1e-9)continue;const c=l<0?1:-1,h=[-o.u[1],o.u[0]],d=[-r.u[1],r.u[0]],p=[e[0]+h[0]*o.halfDepth*c,e[1]+h[1]*o.halfDepth*c],u=[e[0]-d[0]*r.halfDepth*c,e[1]-d[1]*r.halfDepth*c];if(!rc(p,o.u,u,r.u))continue;const _=[[o,r,u,[d[0]*-c,d[1]*-c]],[r,o,p,[h[0]*c,h[1]*c]]];for(const[t,,i,n]of _){const o=Math.min(2*t.halfDepth,t.length),r=[-t.u[1]*t.halfDepth,t.u[0]*t.halfDepth],l=[[e[0]+r[0],e[1]+r[1]],[e[0]+t.u[0]*o+r[0],e[1]+t.u[1]*o+r[1]],[e[0]+t.u[0]*o-r[0],e[1]+t.u[1]*o-r[1]],[e[0]-r[0],e[1]-r[1]]],c=e=>(e[0]-i[0])*n[0]+(e[1]-i[1])*n[1],h=[];for(let e=0;e=-1e-12&&h.push(t),s>1e-12&&n<-1e-12||s<-1e-12&&n>1e-12){const e=s/(s-n);h.push([t[0]+(i[0]-t[0])*e,t[1]+(i[1]-t[1])*e])}}h.length>=3&&Math.abs(nc(h))>s*s&&a.push({segmentIndex:t.index,wedge:h})}}return a}function ec(e,t=1e-6){const i=(e||[]).filter(e=>e&&Array.isArray(e.a)&&Array.isArray(e.b)&&e.a.length>=2&&e.b.length>=2&&e.a.every(Number.isFinite)&&e.b.every(Number.isFinite)&&Number.isFinite(e.halfDepth)&&e.halfDepth>0&&Math.hypot(e.b[0]-e.a[0],e.b[1]-e.a[1])>1e-9);if(i.length<2)return[];const s=Math.max(Number.isFinite(t)?t:0,1e-9),n=i.flatMap(e=>[e.a,e.b]).map(e=>[e[0],e[1]]).sort((e,t)=>e[0]-t[0]||e[1]-t[1]),o=[];for(const e of n)o.some(t=>Yl(t,e,s))||o.push(e);const a=[],r=i.map((e,t)=>({roomId:"",a:[e.a[0],e.a[1]],b:[e.b[0],e.b[1]],key:`join-${t}`,kind:"outer",cm:0,open:!1,half:e.halfDepth})),l=gc(r,s);for(const e of fc(l).fans)a.push(e);const c=e=>!!vc(l,e);for(const e of o){if(c(e))continue;const t=[];for(const n of i)Yl(e,n.a,s)?Xl(t,n.b[0]-n.a[0],n.b[1]-n.a[1],n.halfDepth):Yl(e,n.b,s)?Xl(t,n.a[0]-n.b[0],n.a[1]-n.b[1],n.halfDepth):Jl(e,n,s)&&(Xl(t,n.a[0]-e[0],n.a[1]-e[1],n.halfDepth),Xl(t,n.b[0]-e[0],n.b[1]-e[1],n.halfDepth));if(!(t.length<2)){t.sort((e,t)=>Math.atan2(e.u[1],e.u[0])-Math.atan2(t.u[1],t.u[0])||e.halfDepth-t.halfDepth);for(let i=0;is*s&&a.push(m)}}}return a}function tc(e,t,i,s){if(!(t>0)||!e||e.length<2)return"";if(i&&e.length>=3){let i=e;const n=e[e.length-1];if(e.length>=4&&Math.hypot(e[0][0]-n[0],e[0][1]-n[1])<1e-9&&(i=e.slice(0,-1)),i.length>=3){const e=i.map((e,i)=>s?.[i]||t),n=Vc(i,e),o=lc(i,e);if(n&&o)return`${cc(n)} ${cc(hc(o))}`}}const n=[];for(let i=0;i=1e-9&&r>0&&n.push({a:o,b:a,halfDepth:r})}const o=n.map(Kl);for(const{segmentIndex:e,wedge:t}of Ql(n)){const i=o[e];if(!i)continue;const s=Zl(i,t);s&&(o[e]=s)}const a=[...o.filter(e=>!!e),...ec(n)],r=function(e){let t=null;try{for(const i of e){if(i.length<3)continue;const e=Dc(i);t=t?si(t,e):[e]}return t}catch{return null}}(a);return r?Lc(r):a.map(e=>cc(e)).join(" ")}function ic(e,t,i,s,n=1){if(!i.length)return!1;const o=sc(s,n),a=(e[0]+t[0])/2,r=(e[1]+t[1])/2,[l,c]=Tl(e,t);for(const e of i){const[t,i]=Tl([e[0],e[1]],[e[2],e[3]]);if(!(Math.abs(l*i-c*t)>.05)&&Sc(a,r,e[0],e[1],e[2],e[3])<=o)return!0}return!1}function sc(e,t){return Math.max(e*(t>0?t:1)*.04,1e-9)}function nc(e){let t=0;for(let i=0;ie[1]!=l>e[1]&&e[0]<(r-o)*(e[1]-a)/(l-a+0)+o&&(i=!i)}return i}([c[0]+.001*r,c[1]+.001*l],e)||(r=-r,l=-l),[r,l]}function ac(e,t){const i=e[0]*t[1]-e[1]*t[0],s=e[0]*t[0]+e[1]*t[1];return Math.abs(i)<1e-9&&s>0}function rc(e,t,i,s){const n=t[0]*s[1]-t[1]*s[0];if(Math.abs(n)<1e-12)return null;const o=[i[0]-e[0],i[1]-e[1]],a=(o[0]*s[1]-o[1]*s[0])/n;return[e[0]+a*t[0],e[1]+a*t[1]]}function lc(e,t,i){const s=e?.length||0;if(s<3||t.length!==s)return null;if(t.every(e=>!(e>0)))return e.map(e=>[e[0],e[1]]);const n=[];for(let o=0;o0||p>0)){n.push([e[o][0],e[o][1]]);continue}if(d>0!=p>0){const t=e[o],i=d>0?[t[0]+u*d,t[1]+_*d]:[t[0],t[1]],s=p>0?[t[0]+m*p,t[1]+g*p]:[t[0],t[1]];n.push(i),Math.hypot(s[0]-i[0],s[1]-i[1])>1e-9&&n.push(s);continue}if(ac(w,k)){const t=e[o],i=[t[0]+u*d,t[1]+_*d],s=[t[0]+m*p,t[1]+g*p];n.push(i),Math.hypot(s[0]-i[0],s[1]-i[1])>1e-9&&n.push(s);continue}const S=rc(x,w,$,k),M=Math.max(d,p,1e-9),C=vc(i,e[o])?.limit??4*M;if(S){const i=Math.hypot(S[0]-e[o][0],S[1]-e[o][1]);if(Number.isFinite(i)&&i<=C){n.push(S);continue}if(jc(e,t,o)){n.push(S);continue}}d>0&&n.push([e[o][0]+u*d,e[o][1]+_*d]),p>0&&n.push([e[o][0]+m*p,e[o][1]+g*p]),d>0||p>0||n.push([e[o][0],e[o][1]])}return n.length>=3?n:null}function cc(e,t=!0){if(!e.length)return"";let i=`M ${e[0][0]} ${e[0][1]}`;for(let t=1;te?.id===t),r=hi(a);if(!r||r.length<3)return null;const l=sc(s,n),c=[];for(const i of e||[]){if(!i||i.id===t)continue;const e=hi(i);if(e)for(const t of Cs(r,e,l))c.push([t[0],t[1]],[t[2],t[3]])}for(const e of i||[])c.push([e[0],e[1]],[e[2],e[3]]);for(const e of o||[]){const t=Al(e,n);t&&c.push(t[0],t[1])}const h=[],d=[];for(let e=0;el)continue;const a=((e[0]-t[0])*(i[0]-t[0])+(e[1]-t[1])*(i[1]-t[1]))/(s*s);a<=n||a>=1-n||(o.some(e=>Math.abs(e-a)*s<=2*l)||o.push(a))}o.sort((e,t)=>e-t);for(const s of o)h.push([t[0]+(i[0]-t[0])*s,t[1]+(i[1]-t[1])*s]),d.push(e)}return{poly:h,parent:d,orig:r}}function pc(e,t,i,s){const n=t.poly.length,o=new Array(n).fill(0);if(!e?.length)return o;const a=new Set,r=[];for(let l=0;l0?(o[l]=$l(c.cm),a.add(c.key)):r.push(l)}if(!r.length)return o;const l=s>0?s:1,c=Math.max(.5*i,1e-9)*l,h=Fl(e,l).filter(e=>e.w.cm>0);for(let e=r.length-1;e>=0;e--){const i=r[e],s=t.poly[i],a=t.poly[(i+1)%n],d=Nl(s,a);let p=null;for(const e of h){const t=Al(e.w,l);if(!t||!El(Nl(t[0],t[1]),d))continue;if(Sc(s[0],s[1],t[0][0],t[0][1],t[1][0],t[1][1])>c||Sc(a[0],a[1],t[0][0],t[0][1],t[1][0],t[1][1])>c)continue;const i=Math.hypot(a[0]-s[0],a[1]-s[1]),n=Math.hypot(t[1][0]-t[0][0],t[1][1]-t[0][1]),o=Math.max(0,n-i);(!p||oc||Sc(n[0],n[1],t[0][0],t[0][1],t[1][0],t[1][1])>c)continue;i=!0,o=Math.max(0,Math.hypot(t[1][0]-t[0][0],t[1][1]-t[0][1])-_)}else{if(Sc(e.x,e.y,s[0],s[1],n[0],n[1])>c)continue;o=Math.hypot(e.x-d,e.y-p)}(!u||i&&!u.exact||i===u.exact&&o0?i:1,o=(e||[]).filter(e=>e&&!e.open&&null!==e.kind&&Number.isFinite(e.half)&&e.half>0&&Array.isArray(e.a)&&Array.isArray(e.b)&&e.a.length>=2&&e.b.length>=2&&[e.a[0],e.a[1],e.b[0],e.b[1]].every(Number.isFinite)&&Math.hypot(e.b[0]-e.a[0],e.b[1]-e.a[1])>s).sort((e,t)=>e.key.localeCompare(t.key)||e.a[0]-t.a[0]||e.a[1]-t.a[1]||e.b[0]-t.b[0]||e.b[1]-t.b[1]||e.half-t.half),a=new Map;for(const e of o){const t=a.get(e.key);t?e.half>t.half&&a.set(e.key,{...t,half:e.half}):a.set(e.key,e)}const r=[...a.values()].flatMap(e=>[{point:[e.a[0],e.a[1]],other:e.b,halfDepth:e.half,kind:e.kind,key:e.key},{point:[e.b[0],e.b[1]],other:e.a,halfDepth:e.half,kind:e.kind,key:e.key}]).sort((e,t)=>e.point[0]-t.point[0]||e.point[1]-t.point[1]||e.other[0]-t.other[0]||e.other[1]-t.other[1]||e.halfDepth-t.halfDepth),l=new Map;for(const e of r){const[t,i]=uc(e.point,s),n=_c(t,i),o=l.get(n)||[];o.push(e),l.set(n,o)}const c=[],h=new Map;for(const e of r){const t=mc(h,e.point,s).filter(t=>Math.hypot(t.point[0]-e.point[0],t.point[1]-e.point[1])<=s).sort((t,i)=>Math.hypot(t.point[0]-e.point[0],t.point[1]-e.point[1])-Math.hypot(i.point[0]-e.point[0],i.point[1]-e.point[1])||t.point[0]-i.point[0]||t.point[1]-i.point[1]);let i=t[0];if(!i){i={point:[...e.point],rays:[]},c.push(i);const[t,n]=uc(i.point,s),o=_c(t,n),a=h.get(o)||[];a.push(i),h.set(o,a)}const n=e.other[0]-e.point[0],o=e.other[1]-e.point[1],a=Math.hypot(n,o);if(!(a>s))continue;const r=[n/a,o/a];let l=Math.atan2(r[1],r[0]);l<0&&(l+=2*Math.PI),i.rays.push({u:r,halfDepth:e.halfDepth,length:a,angle:l})}const d=[],p=e=>{const t=1e-9*Math.max(1,n),i=e.filter(e=>Number.isFinite(e.halfDepth)&&e.halfDepth>0&&Number.isFinite(e.length)&&e.length>s);return i.filter((e,s)=>!i.some((i,n)=>n!==s&&i.halfDepth>=e.halfDepth-t&&i.length>=e.length-t&&(i.halfDepth>e.halfDepth+t||i.length>e.length+t||ne.length-t.length||e.halfDepth-t.halfDepth).map(e=>({...e}))};for(const e of c){const t=e.rays.sort((e,t)=>e.angle-t.angle||e.length-t.length||e.halfDepth-t.halfDepth),i=[];for(const e of t){const t=i[i.length-1];t&&Math.abs(e.angle-t.angle)<=1e-9?t.supports.push({halfDepth:e.halfDepth,length:e.length}):i.push({u:[...e.u],angle:e.angle,supports:[{halfDepth:e.halfDepth,length:e.length}]})}if(i.length>1&&2*Math.PI-i[i.length-1].angle+i[0].angle<=1e-9){const e=i.pop();i[0].supports.push(...e.supports)}if(i.length<3)continue;const n=i.map(t=>{const i=p(t.supports),n=new Set,o=[];for(const a of i){const i=[e.point[0]+t.u[0]*a.length,e.point[1]+t.u[1]*a.length];for(const t of mc(l,i,s)){if("shared"!==t.kind||Math.hypot(t.point[0]-i[0],t.point[1]-i[1])>s)continue;const a=t.other[0]-t.point[0],r=t.other[1]-t.point[1],l=Math.hypot(a,r);if(!(l>s))continue;const c=a/l,h=r/l;if(Math.hypot(t.other[0]-e.point[0],t.other[1]-e.point[1])<=s)continue;const d=`${t.key}|${t.point[0]}|${t.point[1]}|${t.other[0]}|${t.other[1]}|${t.halfDepth}`;n.has(d)||(n.add(d),o.push({start:[t.point[0],t.point[1]],u:[c,h],length:l,halfDepth:t.halfDepth}))}}return o.sort((e,t)=>e.start[0]-t.start[0]||e.start[1]-t.start[1]||e.u[0]-t.u[0]||e.u[1]-t.u[1]||e.length-t.length||e.halfDepth-t.halfDepth),{u:[...t.u],halfDepth:Math.max(...i.map(e=>e.halfDepth)),length:Math.max(...i.map(e=>e.length)),supports:i,continuations:o}}).filter(e=>Number.isFinite(e.halfDepth)&&e.halfDepth>0&&Number.isFinite(e.length)&&e.length>s);if(n.length<3)continue;const o=Math.max(...n.map(e=>e.halfDepth));o>0&&Number.isFinite(o)&&d.push({point:[...e.point],rays:n,halfDepth:o,limit:1.25*o})}d.sort((e,t)=>e.point[0]-t.point[0]||e.point[1]-t.point[1]);const u=new Map;for(const e of d){const[t,i]=uc(e.point,s),n=_c(t,i),o=u.get(n)||[];o.push(e),u.set(n,o)}return{epsilon:s,coordinateScale:n,nodes:d,index:u}}function fc(e){const t={fans:[],supports:[]};if(!e?.nodes?.length)return t;const i=Math.max(e.epsilon,1e-9)**2;for(const s of e.nodes){const e=s.rays.filter(e=>Number.isFinite(e.halfDepth)&&e.halfDepth>0).map(e=>({...e,thickLength:Math.max(...e.supports.filter(t=>t.halfDepth>=e.halfDepth-1e-12).map(e=>e.length),0),angle:(()=>{const t=Math.atan2(e.u[1],e.u[0]);return t<0?t+2*Math.PI:t})()})).sort((e,t)=>e.angle-t.angle);if(e.length<2)continue;const n=s.point;for(const i of e)for(const e of i.supports){if(!(e.halfDepth>0&&e.length>0))continue;const s=-i.u[1]*e.halfDepth,o=i.u[0]*e.halfDepth,a=[n[0]+i.u[0]*e.length,n[1]+i.u[1]*e.length];t.supports.push([[n[0]+s,n[1]+o],[a[0]+s,a[1]+o],[a[0]-s,a[1]-o],[n[0]-s,n[1]-o]])}for(let s=0;s{const e=a.angle-o.angle;return e>0?e:e+2*Math.PI})();if(r<1e-9)continue;const l=r>Math.PI+1e-9,c=4*Math.max(o.halfDepth,a.halfDepth),h=[n[0]-o.u[1]*o.halfDepth,n[1]+o.u[0]*o.halfDepth],d=[n[0]+a.u[1]*a.halfDepth,n[1]-a.u[0]*a.halfDepth],p=o.u[0]*a.u[1]-o.u[1]*a.u[0],u=e=>{let t=Math.atan2(e[1]-n[1],e[0]-n[0])-o.angle;for(;t<0;)t+=2*Math.PI;return t<=r+1e-9};let _=null;if(Math.abs(p)>1e-9){const e=((d[0]-h[0])*a.u[1]-(d[1]-h[1])*a.u[0])/p,t=((d[0]-h[0])*o.u[1]-(d[1]-h[1])*o.u[0])/p,i=[h[0]+o.u[0]*e,h[1]+o.u[1]*e];(l?e<=1e-9&&t<=1e-9:e>1e-9&&e<=o.thickLength&&t<=a.thickLength)&&Math.hypot(i[0]-n[0],i[1]-n[1])<=c&&u(i)&&(_=i)}const m=e=>{Math.abs(nc(e))>i&&t.fans.push(e)};if(_){const e=1.5*Math.max(o.halfDepth,a.halfDepth);m(bl([n[0],n[1]],h,_,d,e)??[[n[0],n[1]],h,_,d]);continue}if(l){m([[n[0],n[1]],h,d]);continue}const g=(e,t)=>Math.min(t,Math.sqrt(Math.max(c**2-e**2,0)),2*Math.max(o.halfDepth,a.halfDepth)),f=[h[0]+o.u[0]*g(o.halfDepth,o.thickLength),h[1]+o.u[1]*g(o.halfDepth,o.thickLength)],v=[d[0]+a.u[0]*g(a.halfDepth,a.thickLength),d[1]+a.u[1]*g(a.halfDepth,a.thickLength)];m([[n[0],n[1]],h,f,v,d])}}return t}function vc(e,t){return!e||!Array.isArray(t)||t.length<2||!t.slice(0,2).every(Number.isFinite)?null:mc(e.index,t,e.epsilon).filter(i=>Math.hypot(i.point[0]-t[0],i.point[1]-t[1])<=e.epsilon).sort((e,i)=>Math.hypot(e.point[0]-t[0],e.point[1]-t[1])-Math.hypot(i.point[0]-t[0],i.point[1]-t[1])||e.point[0]-i.point[0]||e.point[1]-i.point[1])[0]||null}function bc(e,t,i,s,n,o,a){return gc(wc(e,t,i,s,n,o,a),4*sc(s,a),a)}function yc(e,t,i,s,n,o,a,r=1){const l=dc(e,t,s,n,r,i);if(!l)return null;const c=function(e,t,i){const s=(e||[]).find(e=>e?.id===t),n=hi(s);if(!n)return[];const o=[];for(const s of e||[]){if(!s||s.id===t)continue;const e=hi(s);if(e)for(const t of Cs(n,e,i))o.push(t)}return o}(e,t,sc(n,r)),h=function(e,t,i,s,n){const o=sc(s,n),a=[];for(let r=0;rSc(h,d,e[0],e[1],e[2],e[3])<=o);a.push(p?"shared":"outer")}return a}(l.poly,c,s,n,r),d=pc(i,l,n,r),p=d.map((e,t)=>h[t]&&e>0?Ml(e,o,a)/2:0);return{...l,kinds:h,cms:d,offsets:p}}function wc(e,t,i,s,n,o,a=1){const r=[];for(const l of e||[]){if(!l?.id)continue;const c=yc(e,l.id,t,i,s,n,o,a);if(c)for(let e=0;e0&&(r=Ul(r,e.a,e.b,e.cm,s,a));return r}function xc(e,t,i,s,n,o,a=1){if(!t?.length)return[];const r=wc(e,t,i,s,n,o,a),l=new Map;for(const e of r){if(e.open||!e.kind||!e.roomId)continue;const t=l.get(e.key)||new Set;t.add(e.roomId),l.set(e.key,t)}const c=e=>{const t=[...l.get(e)||[]].sort();return 1!==t.length&&2!==t.length?`ambiguous:${e}`:`${1===t.length?"outer":"shared"}:${t.join("|")}`},h=[],d=new Set;for(const e of r)!e.open&&e.cm>0&&!d.has(e.key)&&(d.add(e.key),h.push({...e,ownerSignature:c(e.key)}));const p=[];for(const r of e||[]){if(!r?.id)continue;const l=yc(e,r.id,t,i,s,n,o,a);if(l)for(let e=0;e0)||null===l.kinds[i]){e++;continue}const o=c(Rl(l.poly[i],l.poly[(i+1)%l.poly.length],s,a));let r=e;for(;r+10&&p.push({a:[d[0],d[1]],b:[u[0],u[1]],key:Rl(d,u,s,a),cm:n,len:_,ownerSignature:o}),e=r+1}}}p.sort((e,t)=>t.len-e.len||e.key.localeCompare(t.key));const u=[],_=new Set,m=new Set,g=4*sc(s,a);for(const e of p){const t=h.filter(t=>!m.has(t.key)&&t.cm===e.cm&&t.ownerSignature===e.ownerSignature&&El(Nl(t.a,t.b),Nl(e.a,e.b))&&Sc(t.a[0],t.a[1],e.a[0],e.a[1],e.b[0],e.b[1])<=g&&Sc(t.b[0],t.b[1],e.a[0],e.a[1],e.b[0],e.b[1])<=g);if(t.length){for(const e of t)m.add(e.key);_.has(e.key)||(_.add(e.key),u.push(zl(e.a,e.b,e.cm,s,a)))}}for(const e of h)m.has(e.key)||_.has(e.key)||(_.add(e.key),u.push(zl(e.a,e.b,e.cm,s,a)));return u}function $c(e,t,i,s,n,o,a,r=1){const l=sc(n,r),c=(s[0]+s[2])/2,h=(s[1]+s[3])/2,d=Nl([s[0],s[1]],[s[2],s[3]]);let p=null;for(const s of wc(e,t,i,n,o,a,r)){if(!El(Nl(s.a,s.b),d))continue;const e=Sc(c,h,s.a[0],s.a[1],s.b[0],s.b[1]);e>4*l||(!p||e[e[0],e[1]]),o=Math.abs(nc(n));n.length>=3&&o>i&&(t=n,i=o)}return t}function Cc(e,t,i,s,n,o,a,r=1,l,c){const h=(e||[]).find(e=>e?.id===t),d=hi(h);if(!d||d.length<3)return null;if(!i?.length)return d.map(e=>[e[0],e[1]]);const p=yc(e,t,i,s,n,o,a,r);if(!p||!p.offsets.some(e=>e>0))return d.map(e=>[e[0],e[1]]);const u=c||bc(e,i,s,n,o,a,r),_=lc(p.poly,p.offsets,u);if(!_)return d.map(e=>[e[0],e[1]]);if(!u.nodes.length)return _;const m=void 0===l?qc(e,i,s,[],n,o,a,r):null,g=l??("ok"===m?.status||"degraded-extra"===m?.status?m.roomGeom:void 0);if(g)try{const e=Mc(oi(Dc(p.poly),g));if(e)return e}catch{}return function(e,t){try{return Mc(ni(Dc(e),Dc(t)))}catch{return null}}(_,p.poly)||d.map(e=>[e[0],e[1]])}function Dc(e){const t=e.map(e=>[e[0],e[1]]);return t.push([e[0][0],e[0][1]]),[t]}function Pc(e){return!(!Array.isArray(e)||!e.length)&&e.every(e=>Array.isArray(e)&&e.length&&e.every(e=>Array.isArray(e)&&e.length>=4&&e.every(e=>Array.isArray(e)&&e.length>=2&&Number.isFinite(e[0])&&Number.isFinite(e[1])))&&Math.abs(Ti(e[0]))>1e-9)}function Tc(e,t,i,s){const n=s?new Set(s):null;let o=null;for(let s=0;st.epsilon))continue;const l=Fc([[e.point[0]+r[0]*s.halfDepth,e.point[1]+r[1]*s.halfDepth],[e.point[0]+a.u[0]*n+r[0]*s.halfDepth,e.point[1]+a.u[1]*n+r[1]*s.halfDepth],[e.point[0]+a.u[0]*n-r[0]*s.halfDepth,e.point[1]+a.u[1]*n-r[1]*s.halfDepth],[e.point[0]-r[0]*s.halfDepth,e.point[1]-r[1]*s.halfDepth]],t.coordinateScale);if(!l)continue;const c=Dc(l);o=o?si(o,c):c}}return o}function Ic(e,t,i){let s=null;for(const n of e.rays)for(const e of n.continuations){const n=[-e.u[1],e.u[0]],o=[e.start[0]+e.u[0]*e.length,e.start[1]+e.u[1]*e.length],a=Fc([[e.start[0]+n[0]*e.halfDepth,e.start[1]+n[1]*e.halfDepth],[o[0]+n[0]*e.halfDepth,o[1]+n[1]*e.halfDepth],[o[0]-n[0]*e.halfDepth,o[1]-n[1]*e.halfDepth],[e.start[0]-n[0]*e.halfDepth,e.start[1]-n[1]*e.halfDepth]],t.coordinateScale);if(!a)continue;const r=ni(Dc(a),i);Array.isArray(r)&&0!==r.length&&(s=s?si(s,r):r)}return s}function Rc(e,t,i=2*(4*e.halfDepth+2*t.epsilon)){const s=function(e,t=yl){const i=new Set,s=Number.isFinite(t)&&t>=0?t:yl;for(let t=0;te-t)}(e);return s.length?Tc(e,t,i,s):null}function Ac(e,t,i,s,n){const o=function(e){let t=null;for(const i of e){const e=Dc(i);try{t=t?si(t,e):e}catch{}}return t}(function(e,t,i=!1){if(!e)return[];const s=[];for(const n of e.nodes)for(let o=0;o1e-9)||h>=Math.PI-1e-9)continue;const d=[-a.u[1],a.u[0]],p=[-r.u[1],r.u[0]],u=[n.point[0]+d[0]*a.halfDepth,n.point[1]+d[1]*a.halfDepth],_=[n.point[0]-p[0]*r.halfDepth,n.point[1]-p[1]*r.halfDepth],m=rc(u,a.u,_,r.u);if(!m)continue;const g=Math.hypot(m[0]-n.point[0],m[1]-n.point[1]);if(!Number.isFinite(g)||g<=n.limit)continue;const f=t?Math.sqrt(Math.max(0,n.limit*n.limit-a.halfDepth*a.halfDepth)):0,v=t?Math.sqrt(Math.max(0,n.limit*n.limit-r.halfDepth*r.halfDepth)):0,b=Fc([[u[0]+a.u[0]*f,u[1]+a.u[1]*f],[_[0]+r.u[0]*v,_[1]+r.u[1]*v],m],e.coordinateScale);if(b&&s.push(b),i){const t=m[0]-n.point[0],i=m[1]-n.point[1],o=Math.hypot(t,i),a=g-n.limit;if(o>e.epsilon&&a>e.epsilon){const r=Math.min(Math.max(8*e.epsilon,.05*n.halfDepth),.25*a),l=t/o,c=i/o,h=-c,d=l,p=Fc([[m[0]-l*r+h*r,m[1]-c*r+d*r],[m[0]-l*r-h*r,m[1]-c*r-d*r],[m[0]+l*r,m[1]+c*r]],e.coordinateScale);p&&s.push(p)}}}return s}({...t,nodes:[e]},i,s));return o&&n?oi(o,n):o}function zc(e,t,i,s){if(!e||!t.nodes.length)return e;let n=null;try{n=function(e){let t=null;for(const i of e.nodes){const s=Rc(i,e);s&&(t=t?si(t,s):s)}return t}(t)}catch{return e}let o=e;for(const e of t.nodes){const a=4*e.halfDepth+2*t.epsilon,r=2*a,l=[[e.point[0]-a,e.point[1]-a],[e.point[0]+a,e.point[1]-a],[e.point[0]+a,e.point[1]+a],[e.point[0]-a,e.point[1]+a]];try{let a=o;const c=Ac(e,t,!1,!0,n);c&&(a=oi(a,c));let h=Tc(e,t,r);const d=Ac(e,t,!0,!0,n);d&&(h=oi(h,d)),n&&(h=si(h,n));const p=.02*Math.min(...e.rays.map(e=>e.halfDepth));if(h=si(h,Dc([[e.point[0]-p,e.point[1]-p],[e.point[0]+p,e.point[1]-p],[e.point[0]+p,e.point[1]+p],[e.point[0]-p,e.point[1]+p]])),!h)continue;let u=ni(h,Dc(l));s?u=ni(u,s):i&&(u=ni(u,i));const _=Dc(l),m=oi(a,_),g=i?oi(ni(a,_),i):null,f=Ic(e,t,_);o=si(m,...g?[g]:[],...f?[f]:[],u)}catch{}}if(n)try{let e=n;s?e=ni(e,s):i&&(e=ni(e,i)),o=si(o,e)}catch{}return o}function Fc(e,t=1){if(!Array.isArray(e)||e.length<3)return null;const i=Number.isFinite(t)&&t>0?t:1,s=1e-12*Math.max(1,i),n=[];for(const t of e){if(!Array.isArray(t)||t.length<2)return null;const e=Number(t[0]),i=Number(t[1]);if(!Number.isFinite(e)||!Number.isFinite(i))return null;const o=Math.round(e/s)*s,a=Math.round(i/s)*s;if(!Number.isFinite(o)||!Number.isFinite(a))return null;n.push([Object.is(o,-0)?0:o,Object.is(a,-0)?0:a])}return Math.abs(nc(n))>s*s?n:null}function Nc(e,t,i,s){if(Sc(e[0],e[1],t[0],t[1],i[0],i[1])>s)return!1;const n=i[0]-t[0],o=i[1]-t[1],a=(e[0]-t[0])*n+(e[1]-t[1])*o,r=n*n+o*o,l=s*Math.sqrt(r);return a>=-l&&a<=r+l}function Ec(e,t,i){const s=[],n=[];for(let o=0;oi*i))continue;const d=i/Math.sqrt(h),p=[0,1];for(const e of t)for(const t of[e.a,e.b]){if(!Nc(t,a,r,i))continue;const e=((t[0]-a[0])*l+(t[1]-a[1])*c)/h;e>d&&e<1-d&&p.push(e)}p.sort((e,t)=>e-t);const u=p.filter((e,t)=>0===t||Math.abs(e-p[t-1])>d);for(let e=0;e=3&&n.length===s.length?{poly:s,offsets:n}:null}function Oc(e,t,i,s,n,o,a,r){const l=(e||[]).map(hi).filter(e=>!!e&&e.length>=3);if(!l.length)return null;let c=si(Dc(l[0]));for(let e=1;e"outer"===e.kind&&e.half>0),p=4*sc(s,a),u=r||gc(h,p,a);let _=null;for(const e of function(e){const t=[];for(const i of Array.isArray(e)?e:[])if(Array.isArray(i))for(const e of i){if(!Array.isArray(e)||e.length<4)continue;const i=e.slice(0,-1).map(e=>[e[0],e[1]]);i.length>=3&&t.push(i)}return t}(c)){const t=Ec(e,d,p);if(!t||!t.offsets.some(e=>e>0))continue;const i=Vc(t.poly,t.offsets,u),s=lc(t.poly,t.offsets,u);if(!i||!s)continue;const n=oi(Dc(i),Dc(s));_=_?si(_,n):n}return{centre:c,shell:_||[]}}function Hc(e,t,i,s,n,o,a=1){try{const r=Oc(e,t,i,s,n,o,a,bc(e,t,i,s,n,o,a));if(!r)return[];return r.shell?.length?si(r.centre,r.shell):r.centre}catch{return null}}function Lc(e){if(!e)return"";let t="";for(const i of e)if(Array.isArray(i))for(const e of i){if(!Array.isArray(e)||e.length<4)continue;const i=e.slice(0,e.length-1);i.length<3||(t+=(t?" ":"")+cc(i.map(e=>[e[0],e[1]])))}return t}const Bc=15;function jc(e,t,i){const s=e?.length||0;if(s<3||t?.length!==s)return!1;const n=e[(i-1+s)%s],o=e[i],a=e[(i+1)%s],r=[n[0]-o[0],n[1]-o[1]],l=[a[0]-o[0],a[1]-o[1]],c=Math.hypot(r[0],r[1]),h=Math.hypot(l[0],l[1]);if(!(c>1e-9&&h>1e-9))return!1;const d=Math.max(-1,Math.min(1,(r[0]*l[0]+r[1]*l[1])/(c*h))),p=Math.acos(d);if(!(p>1e-9))return!1;if(p>=Bc*Math.PI/180)return!1;const u=Math.max(Math.max(0,t[(i-1+s)%s]),Math.max(0,t[i]));if(!(u>0))return!1;const _=u/Math.tan(p/2);return Number.isFinite(_)&&_>0&&_e>0))continue;for(const e of l.offsets)e>0&&(p=Math.max(p,2*e));const c=Vc(l.poly,l.offsets,d),u=lc(l.poly,l.offsets,d);c&&h.push({outset:c,inset:u})}for(const e of l){const t=e.map(e=>e[0]),i=e.map(e=>e[1]);if(t.length){const s=Math.min(Math.max(...t)-Math.min(...t),Math.max(...i)-Math.min(...i)),n=Math.min(...e.map((t,i)=>{const s=e[(i+1)%e.length];return Math.hypot(s[0]-t[0],s[1]-t[1])}));p=Math.max(p,e.length>16?s:n)}}const u=function(e,t,i,s,n,o,a,r){if(!t?.length||!i?.length)return[];const l=4*sc(s,a),c=new Map;for(const r of wc(e,t,i,s,n,o,a))!r.open&&r.half>0&&!c.has(r.key)&&c.set(r.key,r);const h=[...c.values()];if(h.length<2)return[];const d=r||gc(h,l),p=[];for(const e of i)for(const t of[[e[0],e[1]],[e[2],e[3]]])p.some(e=>Math.hypot(e[0]-t[0],e[1]-t[1])<=l)||p.push(t);const u=[],_=(e,t)=>{let i=0,s=0;if(Math.hypot(e.a[0]-t[0],e.a[1]-t[1])<=l)i=e.b[0]-e.a[0],s=e.b[1]-e.a[1];else{if(!(Math.hypot(e.b[0]-t[0],e.b[1]-t[1])<=l))return null;i=e.a[0]-e.b[0],s=e.a[1]-e.b[1]}const n=Math.hypot(i,s);return n>l?[i/n,s/n]:null};for(const e of p){const t=h.map(t=>({iv:t,u:_(t,e)})).filter(e=>!!e.u);for(let i=0;i0?[e.slice(),p,m,_]:[e.slice(),_,m,p];else{if(!f)continue;{const t=[-n.u[1],n.u[0]],i=[-o.u[1],o.u[0]],s=a<0?1:-1,r=[e[0]+t[0]*n.iv.half*s,e[1]+t[1]*n.iv.half*s],l=[e[0]-i[0]*o.iv.half*s,e[1]-i[1]*o.iv.half*s];b=a>0?[e.slice(),r,l]:[e.slice(),l,r]}}Math.abs(nc(b))>l*l&&u.push(b)}}return u}(e,t,i,n,o,a,r,d),_=s.length?Kc(e,t,i,n,o,a,r):null;let m="exterior";try{const v=Oc(e,t,i,n,o,a,r,d);m="paper";const b=v?v.shell?.length?si(v.centre,v.shell):v.centre:[],y=e=>{const t=Dc(e.outset);if(!e.inset)return t;const i=Dc(e.inset);return oi(t,i)};m="room-rings";let w=null;for(const e of h)try{const t=y(e);w=w?si(w,t):t}catch{}if(m="edge-bodies",v)for(const s of Gc(e,t,i,n,o,a,r))try{const e=ni(Dc(s.quad),v.centre);w=w?si(w,e):e}catch{}m="junctions",w=function(e,t,i=1,s=si){let n=e;for(const e of t||[]){const t=Fc(e,i);if(t)try{const e=Dc(t);n=n?s(n,e):e}catch{}}return n}(w,u,r),m="facade-clip",w&&v&&(w=ni(w,v.centre)),m="exterior-shell";const k=[];let x=0;if(v?.shell?.length)if(w)try{const e=si(w,v.shell);if(!Pc(e))throw new Error("invalid shell union");w=e}catch{if(!Pc(w)||!Pc(v.shell))throw new Error("invalid shell");k.push({id:"exterior-shell",geom:v.shell}),x++}else w=v.shell;if(m="multi-wall-trim",w&&d.nodes.length){const e=e=>e.rays.some(e=>e.supports.some(e=>e.length<2*e.halfDepth)),t=d.nodes.filter(e);if(t.length){w=zc(w,{...d,nodes:t},v?.centre,b)}}if(m="junction-corners",d.nodes.length){const s=fc(d),l=function(e,t,i,s,n,o,a,r){try{const l=Oc(e,t,i,s,n,o,a,{epsilon:r.epsilon,coordinateScale:r.coordinateScale,nodes:[],index:new Map});return l?l.shell?.length?si(l.centre,l.shell):l.centre:null}catch{return null}}(e,t,i,n,o,a,r,d);for(const e of s.fans)try{let t=[Dc(e)];if(l&&(t=ni(t,l)),!t?.length)continue;w=w?si(w,t):t}catch{}g=w,f=Math.max(d.epsilon,1e-9)**2,w=Array.isArray(g)?g.map(e=>{if(!Array.isArray(e)||!e.length)return e;const[t,...i]=e;return Math.abs(nc(t||[]))<=f?null:[t,...i.filter(e=>Math.abs(nc(e||[]))>f)]}).filter(e=>!!e):g}const $=w||[],S=[...Pc($)?[{id:"room-primary",geom:$}]:[],...k.map((e,t)=>({id:`room-isolated-${t}`,geom:e.geom}))];m="openings";for(const e of s){if(!(e.length>0))continue;const t=Xc(_,e,!0);if(!t.negative&&!t.positive)continue;const i=e.angle*Math.PI/180,s=Math.cos(i),o=Math.sin(i),a=-o,l=s,c=e.length/2,h=1.25*Math.max(p,n*r),d=[[e.x-s*c-a*h,e.y-o*c-l*h],[e.x+s*c-a*h,e.y+o*c-l*h],[e.x+s*c+a*h,e.y+o*c+l*h],[e.x-s*c+a*h,e.y-o*c+l*h]];w&&(w=oi(w,Dc(d)));for(const e of k)e.geom=oi(e.geom,Dc(d))}m="extras";const M=[];let C=0;const D=c.mergeExtra||((e,t)=>e?si(e,t):t);for(let e=0;ee.length>=2&&Number.isFinite(e[0])&&Number.isFinite(e[1]))||Math.abs(Ti(t))<=1e-9){C++;continue}const i=[Dc(t)];try{const t=D(w,i,e);if(!Pc(t))throw new Error("invalid extra union");w=t}catch{C++,Pc(i)&&M.push({id:`extra-${e}`,geom:i})}}M.push(...k),M.sort((e,t)=>Lc(e.geom).localeCompare(Lc(t.geom)));const P=w||[];return{status:C||x?"degraded-extra":"ok",geom:P,components:[...Pc(P)?[{id:"primary",geom:P}]:[],...M.map((e,t)=>({...e,id:`isolated-${t}`}))],roomGeom:$,roomComponents:S,paperGeom:b,depthUnits:p,openingPadUnits:1.25*Math.max(p,n*r),openingIndex:_,multiWallNodes:d,degradedExtraCount:C+x}}catch{return c.onCoreFailure?.(m),{status:"failed-core",geom:[],components:[],roomGeom:[],paperGeom:[],roomComponents:[],depthUnits:p,openingIndex:null,multiWallNodes:d,degradedExtraCount:0}}var g,f}function Wc(e,t,i,s=[],n,o,a,r=1,l=[],c={}){if(!t?.length&&!l.length)return null;return Uc(qc(e,t,i,s,n,o,a,r,l,c))}function Uc(e){if("failed-core"===e.status||"not-applicable"===e.status)return null;const t=e.components.map(e=>({id:e.id,d:Lc(e.geom),fillRule:"evenodd"})).filter(e=>!!e.d),i=t[0]?.d||"",s=Lc(e.paperGeom);if(t.length){const n={status:e.status,d:i,paths:t,components:e.components,roomGeom:e.roomGeom,multiWallNodes:e.multiWallNodes,paperD:s,depthUnits:e.depthUnits,fillRule:"evenodd"};return Object.defineProperties(n,{roomComponents:{value:e.roomComponents||[],enumerable:!1},openingIndex:{value:e.openingIndex,enumerable:!1},openingPadUnits:{value:e.openingPadUnits,enumerable:!1}}),n}return null}function Gc(e,t,i,s,n,o,a=1){if(!t?.length)return[];const r=new Set,l=[];for(const c of e||[]){if(!c?.id)continue;const h=yc(e,c.id,t,i,s,n,o,a);if(!h)continue;const d=h.poly;for(let e=0;e0))continue;const u=Rl(t,i,s,a);if(r.has(u))continue;r.add(u);const _=Ml(p,n,o),[m,g]=oc(d,e),f=-m,v=-g,b=_/2,y=[[t[0]+f*b,t[1]+v*b],[i[0]+f*b,i[1]+v*b],[i[0]+m*b,i[1]+g*b],[t[0]+m*b,t[1]+g*b]];l.push({key:u,kind:c,cm:p,quad:y,a:[t[0],t[1]],b:[i[0],i[1]],depthUnits:_})}}return l}function Vc(e,t,i){const s=e?.length||0;if(s<3||t.length!==s)return null;if(t.every(e=>!(e>0)))return e.map(e=>[e[0],e[1]]);hc(e),t.slice().reverse();const n=[];for(let o=0;o0||l>0)){n.push([e[o][0],e[o][1]]);continue}if(r>0!=l>0){const t=e[o],i=r>0?[t[0]-c*r,t[1]-h*r]:[t[0],t[1]],s=l>0?[t[0]-d*l,t[1]-p*l]:[t[0],t[1]];n.push(i),Math.hypot(s[0]-i[0],s[1]-i[1])>1e-9&&n.push(s);continue}if(ac(w,k)){const t=e[o],i=[t[0]-c*r,t[1]-h*r],s=[t[0]-d*l,t[1]-p*l];n.push(i),Math.hypot(s[0]-i[0],s[1]-i[1])>1e-9&&n.push(s);continue}const S=rc(x,w,$,k),M=Math.max(r,l,1e-9),C=vc(i,e[o])?.limit??4*M;if(S){const i=Math.hypot(S[0]-e[o][0],S[1]-e[o][1]);if(Number.isFinite(i)&&i<=C){n.push(S);continue}if(jc(e,t,o)){n.push([e[o][0],e[o][1]]);continue}}r>0&&n.push([e[o][0]-c*r,e[o][1]-h*r]),l>0&&n.push([e[o][0]-d*l,e[o][1]-p*l])}return n.length>=3?n:null}function Kc(e,t,i,s,n,o,a=1){const r=[];for(const l of e||[]){if(!l?.id)continue;const c=yc(e,l.id,t,i,s,n,o,a);if(!c)continue;const h=Math.abs(Ti(c.poly));for(let e=0;e[Math.max(t,e.x0),Math.min(i,e.x1)]).filter(e=>e[1]-e[0]>s).sort((e,t)=>e[0]-t[0]||e[1]-t[1]);if(!n.length)return{coverage:0,full:!1};let o=n[0][0],a=n[0][1],r=0,l=o<=t+s;for(let e=1;e=i-s,{coverage:r,full:l}}function Jc(e,t){return Number(t.full)-Number(e.full)||e.faceDistance-t.faceDistance||e.area-t.area||e.roomId.localeCompare(t.roomId)}function Xc(e,t,i=!1){const s=Number(t?.x),n=Number(t?.y),o=Number(t?.angle),a=Number(t?.length);if(!([s,n,o,a].every(Number.isFinite)&&a>0))return{negative:null,positive:null};const r=o*Math.PI/180,l=Math.cos(r),c=Math.sin(r),h=-c,d=l,p=a/2,u=Math.max(1e-9,e.adjacencyEps),_=new Map;let m=0;for(const t of e.edges){if(i&&!(t.half>0))continue;if(!Bl(t.a,t.b,o))continue;const[e,a]=Tl(t.a,t.b);if(Math.abs((s-t.a[0])*a-(n-t.a[1])*e)>u)continue;const r=(t.a[0]-s)*l+(t.a[1]-n)*c,g=(t.b[0]-s)*l+(t.b[1]-n)*c,f=Math.max(-p,Math.min(r,g)),v=Math.min(p,Math.max(r,g));if(v-f<=u)continue;const b=t.inward[0]*h+t.inward[1]*d>=0?1:-1,y=((t.a[0]+t.b[0])/2-s)*h+((t.a[1]+t.b[1])/2-n)*d,w=Math.abs(y+b*t.half),k=`${b}|${t.roomId}`,x={x0:f,x1:v,half:t.half,cm:t.cm,key:t.key,axis:[e,a]},$=_.get(k);$?($.pieces.push(x),$.faceDistance=Math.min($.faceDistance,w)):_.set(k,{roomId:t.roomId,side:b,order:m++,pieces:[x],faceDistance:w,area:t.area,coverage:0,full:!1})}for(const e of _.values()){const t=Yc(e.pieces,-p,p,u);e.coverage=t.coverage,e.full=t.full}const g=e=>{const t=[..._.values()].filter(t=>t.side===e&&t.coverage>u);return t.sort(Jc),t[0]||null};return{negative:g(-1),positive:g(1)}}function Zc(e,t){const i=Xc(e,t),s=[i.negative,i.positive].filter(e=>!!e);if(!s.length)return{ox:0,oy:0,cm:0,side:-1};const n=i.negative&&i.positive?-1:s[0].side,o=t.flip_v?-n:n,a=(-1===o?i.negative:i.positive)||s[0],r=[...a.pieces].sort((e,t)=>(e.x0<=0&&e.x1>=0?0:Math.min(Math.abs(e.x0),Math.abs(e.x1)))-(t.x0<=0&&t.x1>=0?0:Math.min(Math.abs(t.x0),Math.abs(t.x1)))||t.x1-t.x0-(e.x1-e.x0)||e.key.localeCompare(t.key))[0];if(!(r.half>0&&r.cm>0))return{ox:0,oy:0,cm:0,side:o};const l=t.angle*Math.PI/180,c=-Math.sin(l),h=Math.cos(l);return{ox:c*o*r.half,oy:h*o*r.half,cm:r.cm,side:o}}function Qc(e,t){const i=1e-9,s=t.filter(e=>Number.isFinite(e.x0)&&Number.isFinite(e.x1)&&Number.isFinite(e.half)&&e.x1>e.x0&&e.half>0);if(!s.length)return"";const n=s.flatMap(e=>[e.x0,e.x1]).sort((e,t)=>e-t),o=[];for(const e of n){const t=o[o.length-1];(void 0===t||e>t+i)&&o.push(e)}const a=[];for(let e=0;e+1t+i))continue;const r=(t+n)/2,l=s.reduce((e,t)=>r>=t.x0-i&&r<=t.x1+i?Math.max(e,t.half):e,0);if(!(l>0))continue;const c=a[a.length-1];c&&t<=c.x1+i&&Math.abs(l-c.half)<=i?c.x1=n:a.push({x0:t,x1:n,half:l})}const r=[];for(const e of a){const t=r[r.length-1],s=t?.[t.length-1];s&&e.x0<=s.x1+i?(e.x0=s.x1,t.push(e)):r.push([e])}return r.map(t=>{const i=t[0],s=t[t.length-1],n=Math.min(.25*Math.min(...t.map(e=>e.half)),.75),o=-e*n,a=[];if(1===e){a.push(`M ${i.x0} ${o} L ${s.x1} ${o}`);for(let e=t.length-1;e>=0;e--){const i=t[e];a.push(`L ${i.x1} ${i.half} L ${i.x0} ${i.half}`)}}else{a.push(`M ${s.x1} ${o} L ${i.x0} ${o}`);for(const e of t)a.push(`L ${e.x0} ${-e.half} L ${e.x1} ${-e.half}`)}return a.push("Z"),a.join(" ")}).join(" ")}function eh(e,t,i,s){if(!s)return i;const n=e.angle*Math.PI/180,o=Math.cos(n),a=Math.sin(n),r=[],l=1e-9;for(const n of i){const[i,c]=n.axis,h=o*i+a*c;if(Math.abs(h)<=l)continue;const d=e.x*i+e.y*c,p=d+h*n.x0,u=d+h*n.x1,_=Math.min(p,u),m=Math.max(p,u),g=`${n.key}|${t}`,f=s.get(g)||[];let v=[[_,m]];for(const[e,t]of f){const i=[];for(const[s,n]of v)t<=s+l||e>=n-l?i.push([s,n]):(e>s+l&&i.push([s,Math.min(n,e)]),te[0]-t[0]||e[1]-t[1]),y=[];for(const e of b){const t=y[y.length-1];t&&e[0]<=t[1]+l?t[1]=Math.max(t[1],e[1]):y.push([e[0],e[1]])}s.set(g,y)}return r}function th(e,t){const i=new Map;return t.map(t=>function(e,t,i){const s=Xc(e,t,!0),n=s.negative,o=s.positive;if(!n&&!o)return null;let a;if(n&&o)a=[{candidate:n,side:-1},{candidate:o,side:1}];else{const e=n||o;a=[{candidate:e,side:-1},{candidate:e,side:1}]}const r=a.map(({candidate:e,side:s})=>({candidate:e,side:s,pieces:eh(t,s,e.pieces,i)})),l=r.map(({candidate:e,side:t,pieces:i})=>({side:t,roomId:e.roomId,d:Qc(t,i)})),c=r.flatMap(({pieces:e})=>e);if(!c.length)return null;const h=Math.max(...c.map(e=>e.half));return{faces:l,minY:-h,maxY:h,wallKey:[...new Set(c.map(e=>e.key))].sort().join("|")}}(e,t,i))}const ih=150,sh=1e-6;function nh(e){return Number.isFinite(e)?Math.max(1,Math.min(ih,e)):1}function oh(e){return((Number.isFinite(Number(e))?Number(e):0)%90+90)%90}const ah=e=>{const t=e.map(e=>[e[0],e[1]]);return!t.length||t[0][0]===t[t.length-1][0]&&t[0][1]===t[t.length-1][1]||t.push([...t[0]]),[t]},rh=(e,t)=>e[0]===t[0]&&e[1]===t[1];function lh(e,t=1e-6){const i=Number.isFinite(t)&&t>0?t:sh,s=[];for(const t of e||[]){if(!Array.isArray(t)||t.length<2)return null;const e=Number(t[0]),n=Number(t[1]);if(!Number.isFinite(e)||!Number.isFinite(n))return null;const o=Math.round(e/i)*i,a=Math.round(n/i)*i;if(!Number.isFinite(o)||!Number.isFinite(a))return null;const r=[Object.is(o,-0)?0:o,Object.is(a,-0)?0:a];s.length&&rh(s[s.length-1],r)||s.push(r)}return s.length>1&&rh(s[0],s[s.length-1])&&s.pop(),s.length<3||new Set(s.map(e=>`${e[0]},${e[1]}`)).size<3?null:Ti(s)>i*i?s:null}function ch(e){const t=[];for(const i of e||[])for(const e of i||[]){const i=(e||[]).filter(e=>Array.isArray(e)&&e.length>=2);i.length<4||t.push(`M ${i.slice(0,-1).map(e=>`${e[0]} ${e[1]}`).join(" L ")} Z`)}return t.join(" ")}function hh(e,t,i,s,n){return Kl({a:e,b:t,halfDepth:Ml(i,s,n)/2})}function dh(e,t,i){const s=Number(t)>0?Number(t):5,n=nh(e.cm)/s*i,o=e.center[0],a=e.center[1];if("circle"===e.shape){const e=n/2;return Array.from({length:96},(t,i)=>{const s=i/96*Math.PI*2;return[o+Math.cos(s)*e,a+Math.sin(s)*e]})}const r=n/2,l=oh(e.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l);return[[-r,-r],[r,-r],[r,r],[-r,r]].map(([e,t])=>[o+e*c-t*h,a+e*h+t*c])}function ph(e,t,i=1e-9){if(!t.length)return[e];let s=[ah(e)];try{for(const e of t){const t=e.b[0]-e.a[0],n=e.b[1]-e.a[1],o=Math.hypot(t,n);if(!(o>i))continue;const a=t/o,r=n/o,l=-r,c=a,h=1.25*Math.max(Number(e.depth)||0,4*i),d=Math.max(2*i,1e-9*o),p=[[e.a[0]-a*d-l*h,e.a[1]-r*d-c*h],[e.b[0]+a*d-l*h,e.b[1]+r*d-c*h],[e.b[0]+a*d+l*h,e.b[1]+r*d+c*h],[e.a[0]-a*d+l*h,e.a[1]-r*d+c*h]];s=oi(s,ah(p))}return kh(s)}catch{return[e]}}function uh(e,t){try{const i=oi([[...e.map(e=>[e[0],e[1]]),[e[0][0],e[0][1]]]],[[...t.map(e=>[e[0],e[1]]),[t[0][0],t[0][1]]]]);let s=null,n=0;for(const e of i||[]){const t=e?.[0]||[],i=Math.abs(Ti(t));t.length>=4&&i>n&&(n=i,s=t.slice(0,-1).map(e=>[e[0],e[1]]))}return s}catch{return null}}function _h(e,t,i,s=Math.max(2e-4*i,1e-9),n=[]){const o=[],a=[],r=new Map;for(const e of n){const t=r.get(e.hostId)||[];t.push(e),r.set(e.hostId,t)}const l=[],c=[],h=[];for(const s of e.room_drafts||[])for(let e=0;e+10))continue;const r={a:s.points[e],b:s.points[e+1],halfDepth:a},c=Kl(r);c&&(o.push(r),l.push(c))}const d=[];for(const s of e.partitions||[]){const e={a:s.a,b:s.b,halfDepth:Ml(s.cm,t,i)/2};if(!(e.halfDepth>0))continue;const n=Kl(e);n&&(a.push(e),c.push(n),d.push({id:s.id,body:n}))}const p=[...o,...a];for(const{segmentIndex:e,wedge:t}of Ql(p,s)){const i=e=o.length&&(d[i.at].body=s))}for(const e of d)h.push(...ph(e.body,r.get(e.id)||[],s));const u=(e.wall_columns||[]).map(e=>dh(e,t,i)),_=ec([...o,...a],s).flatMap(e=>ph(e,n,s));return{drafts:l,partitions:c,columns:u,patches:_,all:[...l,...h,..._,...u]}}function mh(e){try{const t=e.map(e=>lh(e)).filter(e=>!!e).map(e=>ah(e));return t.length?si(t[0],...t.slice(1)):null}catch{return null}}const gh=e=>`M ${e.map(e=>`${e[0]} ${e[1]}`).join(" L ")} Z`;function fh(e){const t=[];for(const i of e||[]){if(yh([i])<=1e-6)continue;const e=[];for(const t of i||[]){const i=(t||[]).filter(e=>Array.isArray(e)&&e.length>=2);i.length<4||e.push(gh(i.slice(0,-1)))}e.length&&t.push(e.join(" "))}return t}function vh(e,t,i={}){const s=mh(e.filter(e=>e.length>=3)),n=t.some(e=>!lh(e))?null:mh(t);if(!s)return[];if(n)try{return fh(ni(s,n))}catch{}return function(e,t,i){let s=null;for(let n=0;n=4&&t.push(e.slice(0,-1).map(e=>[e[0],e[1]]));return t}function kh(e){const t=[];for(const i of e||[]){const e=i?.[0];e?.length>=4&&t.push(e.slice(0,-1).map(e=>[e[0],e[1]]))}return t}function xh(e,t){let i=!1;for(let s=0,n=t.length-1;se[1]!=l>e[1]&&e[0]<(r-o)*(e[1]-a)/(l-a||1e-12)+o&&(i=!i)}return i}function $h(e,t,i){return function(e,t){for(const i of t||[]){const t=i?.[0];if(!t?.length||!xh(e,t))continue;let s=!1;for(let t=1;txh(e,t))}function Sh(e,t,i){if(Math.hypot(e.center[0]-t.center[0],e.center[1]-t.center[1])>i)return!1;if(Math.abs(nh(e.cm)-nh(t.cm))>1e-6)return!1;if(e.shape!==t.shape)return!0;if("circle"===e.shape||"circle"===t.shape)return!0;const s=Math.abs(oh(e.angle)-oh(t.angle));return Math.min(s,90-s)<=1e-6}const Mh=2.54;function Ch(e){const t="number"==typeof e?e:NaN;return!Number.isFinite(t)||t<=0||5===t?1:5/t}function Dh(e,t){return e*Ch(t)}function Ph(e){return e?Mh:1}function Th(e,t){if(!t)return String(e);const i=e/Mh;return String(Math.round(1e6*i)/1e6)}const Ih=1e3;function Rh(e,t){const i=Number(e),s=Number.isFinite(i)&&i>0?i:1,n=s>=1?t:t*s,o=s>=1?t/s:t;return{x:(t-n)/2,y:(t-o)/2,w:n,h:o}}const Ah=.01,zh=100;function Fh(e,t=1e3){const i=Rh(e?.plan_aspect,t),s=Number(e?.plan_scale),n=Number.isFinite(s)&&s>0?Math.min(zh,Math.max(Ah,s)):1,o=Number(e?.plan_scale_x),a=Number(e?.plan_scale_y),r=Number.isFinite(o)&&o>0?Math.min(zh,Math.max(Ah,o)):n,l=Number.isFinite(a)&&a>0?Math.min(zh,Math.max(Ah,a)):n,c=Number(e?.plan_x),h=Number(e?.plan_y),d=qr(e?.plan_angle);return{x:i.x+(Number.isFinite(c)?Gh(c):0)*t,y:i.y+(Number.isFinite(h)?Gh(h):0)*t,w:i.w*r,h:i.h*l,...d?{angle:d}:{}}}function Nh(e){if(null==e.x||null==e.y)return{x:e.x,y:e.y,w:e.w,h:e.h};const t=Number(e.w)||0,i=Number(e.h)||0;return{x:t<0?e.x+t:e.x,y:i<0?e.y+i:e.y,w:Math.abs(t),h:Math.abs(i)}}function Eh(e){return e&&Array.isArray(e.spaces)?e.spaces.map(e=>{const t=Ih,i=function(e){return Array.isArray(e)&&4===e.length&&e.every(e=>Number.isFinite(e))&&e[2]>1e-6&&e[3]>1e-6?e:[0,0,1,1]}(e.view_box);return{id:e.id,title:e.title,cellCm:Number.isFinite(Number(e.cell_cm))&&Number(e.cell_cm)>0?Number(e.cell_cm):5,vb:[i[0]*Ih,i[1]*t,i[2]*Ih,i[3]*t],bg:e.plan_url?{href:As(e.plan_url),...Fh(e,Ih)}:null,rooms:(e.rooms||[]).map(e=>{const i={...e,...Nh(e)};return{id:i.id,name:i.name,area:i.area??null,open_to:i.open_to||void 0,settings:i.settings||void 0,x:null!=i.x?i.x*Ih:void 0,y:null!=i.y?i.y*t:void 0,w:null!=i.w?i.w*Ih:void 0,h:null!=i.h?i.h*t:void 0,poly:i.poly?i.poly.map(e=>[e[0]*Ih,e[1]*t]):void 0,wall_ids:Array.isArray(i.wall_ids)?[...i.wall_ids]:void 0}}),wall_segments:(e.wall_segments||[]).map(e=>({...e,id:String(e.id),a:[Number(e.a?.[0])*Ih,Number(e.a?.[1])*t],b:[Number(e.b?.[0])*Ih,Number(e.b?.[1])*t],cm:Number(e.cm)})),room_drafts:(e.room_drafts||[]).map(e=>({id:e.id,points:(e.points||[]).map(e=>[e[0]*Ih,e[1]*t]),segments:(e.segments||[]).map(e=>({..."string"==typeof e.id&&e.id?{id:e.id}:{},cm:Number(e.cm)}))})),partitions:(e.partitions||[]).map(e=>({id:e.id,a:[e.a[0]*Ih,e.a[1]*t],b:[e.b[0]*Ih,e.b[1]*t],cm:Number(e.cm)})),wall_columns:(e.wall_columns||[]).map(e=>({id:e.id,shape:"circle"===e.shape?"circle":"square",center:[e.center[0]*Ih,e.center[1]*t],cm:Number(e.cm),..."circle"===e.shape?{}:{angle:oh(e.angle)}}))}}):[]}const Oh=5e3,Hh=Oh*Ih,Lh=240,Bh=Ih/Lh,jh=1/Lh;function qh(e){if(!Number.isFinite(e))return e;const t=Math.round(e*Lh/Ih)*Ih/Lh;return Math.abs(t-e)<=1e-9*Bh?e:t}function Wh(e){return{x:qh(e.x),y:qh(e.y)}}function Uh(e){return Number.isFinite(e)?Math.min(Hh,Math.max(-Hh,e)):0}function Gh(e){return Number.isFinite(e)?Math.min(Oh,Math.max(-Oh,e)):0}const Vh=1/3,Kh=200;function Yh(e){let t=1/0,i=1/0,s=-1/0,n=-1/0;for(const o of e){const e=Number(o[0]),a=Number(o[1]);Number.isFinite(e)&&Number.isFinite(a)&&(es&&(s=e),a>n&&(n=a))}return t>s?null:{minX:t,minY:i,maxX:s,maxY:n}}function Jh(e){return e.poly&&e.poly.length?Yh(e.poly):null==e.x||null==e.y?null:Yh([[e.x,e.y],[e.x+(e.w||0),e.y+(e.h||0)]])}function Xh(e,t){const i=[];for(const t of e.rooms||[]){const e=Jh(t);e&&i.push(e)}if(e.bg){const t=Yh(Yr(e.bg));t&&i.push(t)}for(const e of t||[])if(Array.isArray(e)){const t=Yh([e]);t&&i.push(t)}else i.push(e);return i}const Zh=e=>{if(!e.length)return 0;const t=[...e].sort((e,t)=>e-t),i=t.length>>1;return t.length%2?t[i]:(t[i-1]+t[i])/2},Qh=e=>{let t=1/0,i=1/0,s=-1/0,n=-1/0;for(const o of e)o.minXs&&(s=o.maxX),o.maxY>n&&(n=o.maxY);return t>s||i>n?null:{x:t,y:i,w:s-t,h:n-i}};function ed(e,t){let{x:i,y:s,w:n,h:o}=e;n<30&&(i=i+n/2-100,n=Kh),o<30&&(s=s+o/2-100,o=Kh);const a=Math.max(n,o)*t;return{x:i-a,y:s-a,w:n+2*a,h:o+2*a}}function td(e,t={}){const i=t.pad??.05,s=t.k??10,n=t.minSpread??50,o=e.filter(e=>Number.isFinite(e.minX)&&Number.isFinite(e.minY)&&Number.isFinite(e.maxX)&&Number.isFinite(e.maxY)&&Math.abs(e.minX)<=Hh&&Math.abs(e.maxX)<=Hh&&Math.abs(e.minY)<=Hh&&Math.abs(e.maxY)<=Hh);if(!o.length)return{core:null,all:null,outliers:0};const a=Qh(o);if(o.length<4){const e=ed(a,i);return{core:e,all:e,outliers:0}}const r=o.map(e=>(e.minX+e.maxX)/2),l=o.map(e=>(e.minY+e.maxY)/2),c=Zh(r),h=Zh(l),d=o.map((e,t)=>Math.max(Math.abs(r[t]-c),Math.abs(l[t]-h))),p=Math.max(((e,t)=>e.length?e[Math.min(e.length-1,Math.max(0,Math.round(t*(e.length-1))))]:0)([...d].sort((e,t)=>e-t),.75),n),u=d.map(e=>e>s*p),_=u.filter(Boolean).length,m=_&&_<=o.length*Vh?o.filter((e,t)=>!u[t]):o;return{core:ed(Qh(m)||a,i),all:ed(a,i),outliers:m===o?0:_}}function id(e,t,i=.05){const s=td(Xh(e,t),{pad:i});if(s.core)return s.core;const n=e.vb&&4===e.vb.length&&e.vb[2]>0&&e.vb[3]>0?e.vb:[0,0,Ih,Ih];return{x:n[0],y:n[1],w:n[2],h:n[3]}}function sd(e){const t=id(e);return{x:t.x+t.w/2,y:t.y+t.h/2}}function nd(e){const t=[];for(const i of e.rooms||[]){const e=Jh(i);e&&t.push(e)}const i=td(t,{pad:0}).core,s=Ih*Ch(e.cellCm??5);return i?Math.max(s,Math.min(Hh,Math.max(i.w,i.h))):s}function od(e,t,i,s=1){const n=Number(i),o=Number.isFinite(s)&&s>0?s:1;return!Number.isFinite(n)||n<=0?e*o:e*nd(t)*o/n}const ad=[1,2,5,10,20,50,100,200,500,1e3];function rd(e){if(e.poly&&e.poly.length){const t=e.poly.map(e=>e[0]),i=e.poly.map(e=>e[1]),s=Math.min(...t),n=Math.min(...i);return{x:s,y:n,w:Math.max(...t)-s,h:Math.max(...i)-n}}return{x:e.x??0,y:e.y??0,w:e.w??0,h:e.h??0}}function ld(e,t,i,s){const n=i["rl_"+(e.id||"")];if(n&&n.s===t)return{x:n.x*Ih,y:n.y*Ih};const o=function(e){if(e.poly){const t=e.poly.length;return[e.poly.reduce((e,t)=>e+t[0],0)/t,e.poly.reduce((e,t)=>e+t[1],0)/t]}return[e.x+e.w/2,e.y+.1*Math.min(e.w,e.h)]}(e);return Wh({x:o[0],y:o[1]})}const cd=["furniture","appliance","sanitary","other"],hd=[{id:"sofa",group:"furniture",w:220,h:90,g:[["r",0,0,1,1],["l",.09,.26,.91,.26],["l",.09,.26,.09,1],["l",.91,.26,.91,1],["l",.5,.26,.5,1]]},{id:"armchair",group:"furniture",w:90,h:85,g:[["r",0,0,1,1],["l",.14,.28,.86,.28],["l",.14,.28,.14,1],["l",.86,.28,.86,1]]},{id:"coffee_table",group:"furniture",w:110,h:60,g:[["r",0,0,1,1],["r",.08,.14,.84,.72]]},{id:"table_dining",group:"furniture",w:140,h:80,g:[["r",0,0,1,1],["r",.06,.11,.88,.78]]},{id:"table_round",group:"furniture",w:120,h:120,g:[["e",.5,.5,.5,.5],["e",.5,.5,.41,.41]]},{id:"chair",group:"furniture",w:45,h:45,g:[["r",0,0,1,.18],["r",.06,.18,.88,.8]]},{id:"desk",group:"furniture",w:120,h:60,g:[["r",0,0,1,1],["r",.63,.07,.31,.86],["l",.63,.5,.94,.5]]},{id:"bed_double",group:"furniture",w:160,h:200,g:[["r",0,0,1,1],["r",0,0,1,.07],["r",.06,.1,.4,.15],["r",.54,.1,.4,.15],["l",0,.33,1,.33]]},{id:"bed_single",group:"furniture",w:90,h:200,g:[["r",0,0,1,1],["r",0,0,1,.07],["r",.15,.1,.7,.15],["l",0,.33,1,.33]]},{id:"nightstand",group:"furniture",w:45,h:40,g:[["r",0,0,1,1],["r",.12,.14,.76,.33],["r",.12,.53,.76,.33]]},{id:"wardrobe",group:"furniture",w:100,h:60,g:[["r",0,0,1,1],["l",0,.72,1,.72],["l",.5,.72,.5,1]]},{id:"bookshelf",group:"furniture",w:80,h:30,g:[["r",0,0,1,1],["l",.34,0,.34,1],["l",.67,0,.67,1]]},{id:"fridge",group:"appliance",w:60,h:65,g:[["r",0,0,1,1],["l",0,.36,1,.36],["l",.83,.44,.83,.64]]},{id:"stove",group:"appliance",w:60,h:60,g:[["r",0,0,1,1],["e",.29,.31,.15,.15],["e",.71,.31,.15,.15],["e",.29,.71,.15,.15],["e",.71,.71,.15,.15]]},{id:"dishwasher",group:"appliance",w:60,h:60,g:[["r",0,0,1,1],["r",.1,.12,.8,.76],["e",.5,.5,.27,.27],["e",.5,.5,.13,.13]]},{id:"washer",group:"appliance",w:60,h:60,g:[["r",0,0,1,1],["l",.08,.17,.92,.17],["e",.5,.57,.3,.3],["e",.5,.57,.14,.14]]},{id:"dryer",group:"appliance",w:60,h:60,g:[["r",0,0,1,1],["l",.08,.17,.92,.17],["e",.5,.57,.3,.3],["p",.36,.5,.5,.64,.64,.5]]},{id:"tv",group:"appliance",w:120,h:30,g:[["r",0,0,1,.42],["l",.5,.42,.5,.72],["l",.3,.72,.7,.72]]},{id:"ac",group:"appliance",w:90,h:25,g:[["r",0,0,1,1],["l",.05,.55,.95,.55],["l",.05,.79,.95,.79]]},{id:"water_heater",group:"appliance",w:45,h:45,g:[["e",.5,.5,.5,.5],["e",.5,.5,.31,.31]]},{id:"toilet",group:"sanitary",w:40,h:70,g:[["r",.06,0,.88,.2],["e",.5,.58,.37,.35],["e",.5,.58,.22,.2]]},{id:"bathtub",group:"sanitary",w:170,h:75,g:[["r",0,0,1,1],["r",.05,.11,.77,.78],["e",.89,.5,.045,.1]]},{id:"shower",group:"sanitary",w:90,h:90,g:[["r",0,0,1,1],["l",0,0,1,1],["l",1,0,0,1],["e",.5,.5,.08,.08]]},{id:"sink",group:"sanitary",w:60,h:45,g:[["r",0,0,1,1],["e",.5,.6,.34,.3],["e",.5,.15,.07,.07]]},{id:"kitchen_sink",group:"sanitary",w:80,h:60,g:[["r",0,0,1,1],["r",.06,.24,.44,.64],["r",.54,.24,.4,.64],["e",.5,.12,.06,.06]]},{id:"bidet",group:"sanitary",w:40,h:55,g:[["e",.5,.5,.44,.5],["e",.5,.5,.26,.3]]},{id:"stairs",group:"other",w:100,h:280,g:[["r",0,0,1,1],["l",0,.111,1,.111],["l",0,.222,1,.222],["l",0,.333,1,.333],["l",0,.444,1,.444],["l",0,.556,1,.556],["l",0,.667,1,.667],["l",0,.778,1,.778],["l",0,.889,1,.889],["l",.5,.93,.5,.06],["p",.38,.16,.5,.06,.62,.16]]},{id:"fireplace",group:"other",w:120,h:40,g:[["r",0,0,1,1],["p",.22,1,.22,.42,.78,.42,.78,1]]},{id:"plant",group:"other",w:40,h:40,g:[["e",.5,.5,.22,.22],["l",.5,.28,.5,.02],["l",.5,.72,.5,.98],["l",.28,.5,.02,.5],["l",.72,.5,.98,.5],["l",.34,.34,.13,.13],["l",.66,.66,.87,.87],["l",.66,.34,.87,.13],["l",.34,.66,.13,.87]]},{id:"rug",group:"other",w:200,h:140,g:[["r",0,0,1,1],["r",.06,.09,.88,.82]]}],dd=new Map(hd.map(e=>[e.id,e]));function pd(e){return e&&dd.get(e)||null}function ud(e){return hd.filter(t=>t.group===e)}function _d(e,t,i=Bh,s=1e3){const n=Number(t)>0?Number(t):5;return(Number(e)||0)/n*i/s}const md=5e-4,gd=Oh;function fd(e){return Number.isFinite(e)?Math.max(md,Math.min(gd,e)):md}const vd=e=>{const t=Math.round(1e3*e)/1e3;return Object.is(t,-0)?"0":String(t)};function bd(e,t,i){const s=pd(e);if(!(s&&t>0&&i>0))return"";const n=e=>vd(e*t),o=e=>vd(e*i),a=[];for(const e of s.g)if("r"===e[0]){const[,t,i,s,r]=e;a.push(`M${n(t)} ${o(i)}H${n(t+s)}V${o(i+r)}H${n(t)}Z`)}else if("l"===e[0]){const[,t,i,s,r]=e;a.push(`M${n(t)} ${o(i)}L${n(s)} ${o(r)}`)}else if("e"===e[0]){const[,s,r,l,c]=e;a.push(`M${n(s-l)} ${o(r)}A${vd(l*t)} ${vd(c*i)} 0 0 1 ${n(s+l)} ${o(r)}A${vd(l*t)} ${vd(c*i)} 0 0 1 ${n(s-l)} ${o(r)}Z`)}else{const t=e.slice(1);if(t.length<4)continue;let i=`M${n(t[0])} ${o(t[1])}`;for(let e=2;e+1{let t=(e%360+360)%360;return t>180&&(t-=360),t};function wd(e,t,i,s,n,o=0){let a=null,r=n;for(const n of s){const[s,l,c,h]=n,d=c-s,p=h-l,u=d*d+p*p;if(!u)continue;const _=Math.sqrt(u);let m=((e-s)*d+(t-l)*p)/u;m=Math.max(0,Math.min(1,m));let g=s+m*d,f=l+m*p;const v=Math.hypot(e-g,t-f);if(!(v0){const e=Math.round(m*_/o)*o;g=s+e/_*d,f=l+e/_*p}a={cx:g+b*(i/2),cy:f+y*(i/2),angle:yd(180*Math.atan2(-b,y)/Math.PI),dist:v}}return a}const kd=1e-9,xd=1e7,$d=e=>{const t=Math.sign(e)*Math.floor(Math.abs(e)*xd+.5)/xd;return Object.is(t,-0)?0:t},Sd=e=>`${$d(e[0])},${$d(e[1])}`,Md=(e,t)=>Math.hypot(t[0]-e[0],t[1]-e[1]),Cd=(e,t)=>Math.atan2(t[1]-e[1],t[0]-e[0]),Dd=e=>Array.isArray(e)&&e.length>=2&&e.every(e=>Number.isFinite(e)),Pd=e=>(e||[]).filter(e=>Dd(e?.a)&&Dd(e?.b)&&Md(e.a,e.b)>kd);function Td(e,{minAngleDeg:t=15,maxValence:i=6}={}){const s=new Map;for(const t of Pd(e))for(const[e,i]of[[t.a,t.b],[t.b,t.a]]){const t=s.get(Sd(e))||[];t.push(Cd(e,i)),s.set(Sd(e),t)}const n=[];for(const[e,o]of s){if(o.length>i&&n.push({rule:"valence",subject:e,actual:o.length,limit:i}),o.length<2)continue;const s=[...o].sort((e,t)=>e-t);let a=1/0;for(let e=0;e(180*Math.atan2(e.b[1]-e.a[1],e.b[0]-e.a[0])/Math.PI%180+180)%180,Rd=(e,t,i=1)=>{const s=Math.abs(Id(e)-Id(t));return Math.min(s,180-s)<=i},Ad=e=>{const t=new Map;for(const i of e)for(const e of[i.a,i.b]){const s=t.get(Sd(e));s?s.push(i):t.set(Sd(e),[i])}return t};function zd(e,t,i){const s=Pd(t),n=i??Ad(s),o=new Set([e]);let a=Md(e.a,e.b);const r=[e.a,e.b];for(;r.length;){const t=r.pop();for(const i of n.get(Sd(t))||[])o.has(i)||Rd(i,e)&&Number(i.cm||0)===Number(e.cm||0)&&(o.add(i),a+=Md(i.a,i.b),r.push(i.a,i.b))}return a}function Fd(e,t,i,{minLengthCm:s=20}={}){const n=[],o=Pd(e),a=Ad(o);for(const e of o){const r=zd(e,o,a)/i*(t||1),l=Math.max(s,Number(e.cm)>0?Number(e.cm):0);r{const s=i[0]-t[0],n=i[1]-t[1],o=s*s+n*n,a=o<=kd?0:Math.max(0,Math.min(1,((e[0]-t[0])*s+(e[1]-t[1])*n)/o));return Math.hypot(e[0]-(t[0]+s*a),e[1]-(t[1]+n*a))};function Ed(e,t,i,{minDistanceCm:s=5}={}){const n=Pd(e),o=new Map;for(const e of n)o.set(Sd(e.a),e.a),o.set(Sd(e.b),e.b);const a=((e,t,i)=>e/(t||1)*i)(s,t,i),r=a>kd?a:1,l=(e,t)=>`${Math.floor(e/r)},${Math.floor(t/r)}`,c=new Map;for(const[e,t]of o){const i=l(t[0],t[1]),s=c.get(i);s?s.push([e,t]):c.set(i,[[e,t]])}const h=new Map;for(const e of n){const t=Math.min(e.a[0],e.b[0])-a,i=Math.max(e.a[0],e.b[0])+a,s=Math.min(e.a[1],e.b[1])-a,n=Math.max(e.a[1],e.b[1])+a;for(let o=Math.floor(t/r);o<=Math.floor(i/r);o++)for(let t=Math.floor(s/r);t<=Math.floor(n/r);t++){const i=`${o},${t}`,s=h.get(i);s?s.push(e):h.set(i,[e])}}const d=[];for(const[e,n]of o){const o=Math.floor(n[0]/r),l=Math.floor(n[1]/r);for(let r=-1;r<=1;r++)for(let h=-1;h<=1;h++)for(const[p,u]of c.get(`${o+r},${l+h}`)||[]){if(e>=p)continue;const o=Md(n,u);o<=2e-7||o{const s=o[(i+1)%o.length];return e+(t[0]*s[1]-s[0]*t[1])},0))/2,r=(i||1)/s,l=a*r*r;return l0?i:1;return{a:[e[0]/s,e[1]/s],b:[t[0]/s,t[1]/s]}}function Ld(e,t){const i=t>0?t:1;return[e.a[0]*i,e.a[1]*i,e.b[0]*i,e.b[1]*i]}function Bd(e){return Array.isArray(e)&&e.length>=2&&Number.isFinite(Number(e[0]))&&Number.isFinite(Number(e[1]))}function jd(e){if(!Array.isArray(e))return[];const t=[];for(const i of e){if(!i||"object"!=typeof i)continue;const e=i;if(!Bd(e.a)||!Bd(e.b))continue;const s=[Number(e.a[0]),Number(e.a[1])],n=[Number(e.b[0]),Number(e.b[1])];Math.hypot(n[0]-s[0],n[1]-s[1])<.001||t.push({a:s,b:n})}return t}function qd(e,t,i,s,n=!0){const o=(e||[]).filter(e=>e?.id),a=jd(t);if(a.length)return function(e,t,i,s){const n=jd(e);if(!n.length)return[];const o=function(e,t){const i=[],s=(e||[]).filter(e=>e?.id);for(let e=0;e4*s||o>4*s)continue;const a=(e[0]-n)*p+(e[1]-l)*u,c=(e[2]-n)*p+(e[3]-l)*u,h=Math.max(0,Math.min(a,c)),m=Math.min(d,Math.max(a,c));if(m-he.lo-t.lo||e.hi-t.hi);const i=[];for(const e of t){const t=i[i.length-1];t&&e.lo<=t.hi+r?t.hi=Math.max(t.hi,e.hi):i.push({...e})}for(const t of i){const i=[n+p*t.lo,l+u*t.lo],s=[n+p*t.hi,l+u*t.hi];Math.hypot(s[0]-i[0],s[1]-i[1])i.pair===e&&Math.abs(i.ux*a-i.uy*o)<=1e-6&&Math.abs((t[0]-i.origin[0])*i.uy-(t[1]-i.origin[1])*i.ux)<=c&&Math.abs((t[2]-i.origin[0])*i.uy-(t[3]-i.origin[1])*i.ux)<=c);h||(h={pair:e,origin:[t[0],t[1]],ux:o,uy:a,ranges:[]},l.push(h));const d=(t[0]-h.origin[0])*h.ux+(t[1]-h.origin[1])*h.uy,p=(t[2]-h.origin[0])*h.ux+(t[3]-h.origin[1])*h.uy;h.ranges.push({lo:Math.min(d,p),hi:Math.max(d,p)})}const h=[];for(const e of l){e.ranges.sort((e,t)=>e.lo-t.lo||e.hi-t.hi);const t=[];for(const i of e.ranges){const e=t[t.length-1];e&&i.lo<=e.hi+r?e.hi=Math.max(e.hi,i.hi):t.push({...i})}for(const s of t){const t=[e.origin[0]+e.ux*s.lo,e.origin[1]+e.uy*s.lo],n=[e.origin[0]+e.ux*s.hi,e.origin[1]+e.uy*s.hi];Math.hypot(n[0]-t[0],n[1]-t[1])>=r&&h.push(Hd(t,n,i))}}return h}(a,e,i,s).map(e=>Ld(e,i));if(!n)return[];const r=[],l=(e,t)=>(e.open_to||[]).includes(t.id)||(t.open_to||[]).includes(e.id);for(let e=0;e{const t=Gd.get(e);if(!t)return;const i=Date.now();if("hidden"===e.visibilityState){t.hiddenAt||(t.hiddenAt=i);const e={kind:"hidden",token:t.token,at:i,hiddenFor:0,long:!1};for(const i of[...t.subscribers])i(e);return}const s=t.hiddenAt?Math.max(0,i-t.hiddenAt):0;t.hiddenAt=0,t.token++;const n={kind:"visible",token:t.token,at:i,hiddenFor:s,long:s>=Wd};for(const e of[...t.subscribers])e(n)},onPageShow:t=>{const i=Gd.get(e);if(!i)return;const s=Date.now();i.token++;const n={kind:"pageshow",token:i.token,at:s,hiddenFor:0,long:!!t.persisted,persisted:!!t.persisted};for(const e of[...i.subscribers])e(n)}},Gd.set(e,i),e.addEventListener("visibilitychange",i.onVisibility),e.defaultView?.addEventListener("pageshow",i.onPageShow)}return i.subscribers.add(t),()=>{const i=Gd.get(e);i&&(i.subscribers.delete(t),i.subscribers.size||(e.removeEventListener("visibilitychange",i.onVisibility),e.defaultView?.removeEventListener("pageshow",i.onPageShow),Gd.delete(e)))}}const Kd={now:()=>Date.now(),setTimeout:(e,t)=>window.setTimeout(e,t),clearTimeout:e=>window.clearTimeout(e),requestAnimationFrame:e=>window.requestAnimationFrame(e),cancelAnimationFrame:e=>window.cancelAnimationFrame(e)};class Yd{constructor(e,t=Kd){this.onChange=e,this.clock=t,this._state="steady",this._token=0,this._frameFingerprint="",this._hasCompleteFrame=!1,this._overlayPhase="none",this._recoveryReason=null,this._overlayTimer=0,this._overlayRaf=0,this._overlayOpaqueAt=0,this._barrierRafs=new Set,this._trace=[],this._disposed=!1}get state(){return this._state}get token(){return this._token}get frameFingerprint(){return this._frameFingerprint}get hasCompleteFrame(){return this._hasCompleteFrame}get overlayPhase(){return this._overlayPhase}get overlayVisible(){return"none"!==this._overlayPhase}get overlayBlocksInteraction(){return this.overlayVisible}get recoveryReason(){return this._recoveryReason}get trace(){return this._trace.map(e=>({...e,...e.stage?{stage:[...e.stage]}:{}}))}note(e,t={}){this.record(e,void 0,t)}record(e,t,i={}){this._trace.push({at:this.clock.now(),token:this._token,event:e,state:this._state,...t?{reason:t}:{},...i}),this._trace.length>80&&this._trace.splice(0,this._trace.length-80)}changed(){this._disposed||this.onChange()}adoptCompleteFrame(e){e&&(this._hasCompleteFrame=!0,this._frameFingerprint=e,this._state="steady",this._recoveryReason=null,this.clearOverlay(),this.record("frame-adopted"),this.changed())}markCompleteFrame(e){e&&(this._hasCompleteFrame=!0,this._frameFingerprint=e,this._state="steady",this._recoveryReason=null,this.clearOverlay(),this.record("frame-complete"),this.changed())}refreshCompleteFrame(e){!this._disposed&&this._hasCompleteFrame&&"steady"===this._state&&e&&e!==this._frameFingerprint&&(this._frameFingerprint=e,this.record("frame-refreshed"))}visibility(e){return"hidden"===e.kind?(this.record("visibility-hidden"),this._token):e.long||"visible"!==e.kind&&"pageshow"!==e.kind?this.beginCandidate("pageshow"===e.kind?"pageshow":"long-resume"):(this.record("pageshow"===e.kind?"pageshow-noop":"visibility-visible-quick"),this._token)}beginCandidate(e,t="plan"){return this._token++,this._recoveryReason=t,this._hasCompleteFrame?(this._state="connection"===t?"offline-stale":"holding",this.clearOverlay()):this.overlayVisible?this._state="overlay-visible":(this._state="overlay-pending",this.scheduleOverlay(t)),this.record("candidate-start",e),this.changed(),this._token}connectionLost(){return this.beginCandidate("connection-lost","connection")}candidateReady(e){return!this._disposed&&e===this._token&&(this._state="candidate-ready",this.record("candidate-ready"),this.changed(),!0)}async commitAfterPaint(e,t){if(this._disposed||e!==this._token)return!1;let i=!0;const s=(async()=>{for(await t.updateComplete();i&&!this._disposed&&e===this._token;)if(t.stageValid()&&t.assetsReady()){if(await this.nextFrame(),e!==this._token||!t.stageValid()||!t.assetsReady())continue;if(await this.nextFrame(),e===this._token&&t.stageValid()&&t.assetsReady())return!0}else await this.nextFrame();return!1})();let n=0;const o=new Promise(e=>{n=this.clock.setTimeout(()=>e({ready:!1,timedOut:!0}),Ud)}),a=await Promise.race([s.then(e=>({ready:e,timedOut:!1})),o]);return i=!1,this.clock.clearTimeout(n),!this._disposed&&e===this._token&&(!(!a.ready&&!a.timedOut)&&(a.ready?(this._hasCompleteFrame=!0,this._frameFingerprint=t.frameFingerprint(),this.record("paint-barrier"),this.finishOverlayAfterCommit(),!0):(this.record("paint-barrier-timeout"),this._hasCompleteFrame?(this._state="connection"===this._recoveryReason?"offline-stale":"steady",this.clearOverlay()):(this._state="recovery-error","opaque"!==this._overlayPhase&&this.showOverlayNow()),this.changed(),!1)))}retry(e=this._recoveryReason||"plan"){return this.beginCandidate("retry",e)}nextFrame(){return new Promise(e=>{const t=this.clock.requestAnimationFrame(()=>{this._barrierRafs.delete(t),e()});this._barrierRafs.add(t)})}scheduleOverlay(e){this.clearOverlayTimer(),this._recoveryReason=e,this._overlayTimer=this.clock.setTimeout(()=>{this._overlayTimer=0,this._hasCompleteFrame||"overlay-pending"!==this._state&&"candidate-ready"!==this._state||(this._state="overlay-visible",this._overlayPhase="entering",this.record("overlay-enter"),this.changed(),this._overlayRaf=this.clock.requestAnimationFrame(()=>{this._overlayRaf=0,"entering"===this._overlayPhase&&(this._overlayPhase="fading-in",this.changed(),this._overlayTimer=this.clock.setTimeout(()=>{this._overlayTimer=0,"fading-in"===this._overlayPhase&&(this._overlayPhase="opaque",this._overlayOpaqueAt=this.clock.now(),this.record("overlay-opaque"),this.changed())},150))}))},150)}showOverlayNow(){this.clearOverlayTimer(),this._overlayPhase="opaque",this._overlayOpaqueAt=this.clock.now(),this.record("overlay-error")}finishOverlayAfterCommit(){if(this.clearOverlayTimer(),"none"===this._overlayPhase||"entering"===this._overlayPhase||"fading-in"===this._overlayPhase)return this.clearOverlay(),this._state="steady",this._recoveryReason=null,this.record("candidate-committed"),void this.changed();const e=Math.max(0,this._overlayOpaqueAt+250-this.clock.now());this._overlayTimer=this.clock.setTimeout(()=>{this._overlayTimer=0,this._overlayPhase="leaving",this.record("overlay-leave"),this.changed(),this._overlayTimer=this.clock.setTimeout(()=>{this._overlayTimer=0,this._overlayPhase="none",this._state="steady",this._recoveryReason=null,this.record("candidate-committed"),this.changed()},180)},e)}clearOverlayTimer(){this._overlayTimer&&this.clock.clearTimeout(this._overlayTimer),this._overlayTimer=0}clearOverlay(){this.clearOverlayTimer(),this._overlayRaf&&this.clock.cancelAnimationFrame(this._overlayRaf),this._overlayRaf=0,this._overlayPhase="none",this._overlayOpaqueAt=0}dispose(){this._disposed=!0,this._token++,this.clearOverlay();for(const e of this._barrierRafs)this.clock.cancelAnimationFrame(e);this._barrierRafs.clear()}}function Jd(e){let t=2166136261,i=2654435769;const s=new WeakSet,n=e=>{for(let s=0;s>>0)+(i>>>2)}},o=e=>{if(null===e)return void n("n");const t=typeof e;if("string"===t)return void n(`s${e.length}:${e}`);if("number"===t)return void n(`d${Number.isNaN(e)?"NaN":String(e)}`);if("boolean"===t)return void n(e?"t":"f");if("undefined"===t)return void n("u");if("object"!==t)return void n(`${t}:${String(e)}`);const i=e;if(s.has(i))n("[cycle]");else{if(s.add(i),Array.isArray(e)){n("[");for(const t of e)o(t);return n("]"),void s.delete(i)}n("{");for(const t of Object.keys(e).sort())n(t),o(e[t]);n("}"),s.delete(i)}};return o(e),`${(t>>>0).toString(16).padStart(8,"0")}${(i>>>0).toString(16).padStart(8,"0")}`}function Xd(e){return Jd(e)}const Zd=new WeakMap;class Qd{constructor(e,t=()=>Date.now()){this.onUpdate=e,this.now=t,this.fallbackAuthority={},this.referenceOwner={},this.referenced=new Set,this.queued=new Set,this.disposed=!1,this.sharedUpdate=()=>{this.disposed||this.onUpdate()}}bind(e){const t=((e,t)=>{const i=e?.connection||e?.callWS||t;return!i||"object"!=typeof i&&"function"!=typeof i?t:i})(e,this.fallbackAuthority);return this.shared&&this.authority===t||(this.shared&&(this.shared.listeners.delete(this.sharedUpdate),this.shared.references.delete(this.referenceOwner)),this.authority=t,this.shared=(e=>{let t=Zd.get(e);return t||(t={signed:{},queued:new Set,inFlight:new Map,retry:new Map,listeners:new Set,settlers:new Set,references:new Map},Zd.set(e,t)),t})(t),this.disposed||(this.shared.listeners.add(this.sharedUpdate),this.shared.references.set(this.referenceOwner,new Set(this.referenced)))),this.shared}start(e,t){this.disposed=!1,this.referenced=new Set(t());const i=this.bind(e());i.listeners.add(this.sharedUpdate),i.references.set(this.referenceOwner,new Set(this.referenced)),this.stopTimer(),this.resignTimer=setInterval(()=>this.resign(e(),t()),288e5)}dispose(){if(this.disposed=!0,this.stopTimer(),clearTimeout(this.batchTimer),this.shared){this.shared.listeners.delete(this.sharedUpdate),this.shared.references.delete(this.referenceOwner);for(const e of this.queued)this.shared.queued.delete(e)}this.queued.clear()}invalidate(e){const t=this.bind(e);clearTimeout(this.batchTimer),this.queued.clear(),t.queued.clear(),t.inFlight.clear(),t.retry.clear(),t.signed={}}stopTimer(){void 0!==this.resignTimer&&clearInterval(this.resignTimer),this.resignTimer=void 0}display(e,t){const i=As(t);if(!i.startsWith("/api/houseplan/content/"))return i;const s=this.bind(e);this.referenced.add(i),s.references.set(this.referenceOwner,new Set(this.referenced));const n=s.signed[i],o=n?this.now()-n.at:1/0;return o{const t=[...this.queued];this.queued.clear();for(const e of t)i.queued.delete(e);this.sign(e,t)},30))}sign(e,t){if(!t.length||!e?.callWS)return;const i=this.bind(e);for(const s of function(e,t){const i=Math.max(1,Math.floor(t)),s=[];for(let t=0;t{const s=i.inFlight.get(e);return!(void 0!==s&&t-s<15e3)&&(i.inFlight.set(e,t),!0)});n.length&&e.callWS({type:"houseplan/content/sign",paths:n}).then(e=>{const t=this.now();let s=0,o=!1;for(const a of n){const n=e?.urls?.[a];if("string"==typeof n&&n){const e=i.signed[a];e?.loaded&&e.url!==n?(e.pending={url:n,at:t},this.preloadReplacement(i,a,n,t)):(i.signed[a]={url:n,at:t,loaded:e?.url===n&&!!e.loaded},o=!0),i.retry.delete(a),s++}else this.backOff(i,a)}if(s&&(this.trimShared(i),o))for(const e of[...i.listeners])e()}).catch(()=>{for(const e of n)this.backOff(i,e)}).finally(()=>{for(const e of n)i.inFlight.get(e)===t&&i.inFlight.delete(e);for(const e of[...i.settlers])e()})}}preloadReplacement(e,t,i,s){const n=()=>{const n=e.signed[t];if(n?.pending?.url===i){e.signed[t]={url:i,at:s,loaded:!0},e.retry.delete(t),this.trimShared(e);for(const t of[...e.listeners])t()}},o=()=>{const s=e.signed[t];s?.pending?.url===i&&(delete s.pending,this.backOff(e,t))};if("undefined"==typeof Image)return void n();const a=new Image;a.onload=()=>{("function"==typeof a.decode?a.decode():Promise.resolve()).then(n).catch(o)},a.onerror=o,a.src=i}preloadCurrentImage(e,t,i){if(i.loaded)return Promise.resolve(!0);if(i.preload)return i.preload;if("undefined"==typeof Image)return i.loaded=!0,Promise.resolve(!0);const s=i.url;return i.preload=new Promise(n=>{const o=new Image;let a=!1;const r=o=>{if(a)return;a=!0,clearTimeout(l);const r=e.signed[t];if(r===i&&delete r.preload,o&&r===i&&r.url===s){r.loaded=!0;for(const t of[...e.listeners])t();n(!0)}else o||r!==i||this.backOff(e,t),n(!1)};o.onload=()=>{("function"==typeof o.decode?o.decode():Promise.resolve()).then(()=>r(!0)).catch(()=>r(!1))},o.onerror=()=>r(!1);const l=setTimeout(()=>r(!1),Ud);o.src=s}),i.preload}prepareImage(e,t){const i=As(t);if(!i.startsWith("/api/houseplan/content/"))return Promise.resolve(!0);const s=this.bind(e),n=()=>{const t=s.signed[i];if(t&&this.now()-t.atthis.now()&&!s.inFlight.has(i)&&!s.queued.has(i)?Promise.resolve(!1):null},o=n();return o?(a=o,new Promise(e=>{let t=!1;const i=i=>{t||(t=!0,clearTimeout(s),e(i))},s=setTimeout(()=>i(!1),Ud);a.then(i).catch(()=>i(!1))})):new Promise(e=>{let t=!1,i=null;const o=i=>{t||(t=!0,clearTimeout(r),s.settlers.delete(a),e(i))},a=()=>{t||i||(i=n(),i&&i.then(o).catch(()=>o(!1)))},r=setTimeout(()=>o(!1),Ud);s.settlers.add(a),a()});var a}backOff(e,t){const i=e.retry.get(t)?.delay||0,s=Math.min(6e4,i?2*i:2e3);e.retry.set(t,{notBefore:this.now()+s,delay:s})}trimShared(e){const t=Object.entries(e.signed);if(t.length<=512)return;const i=new Set(e.inFlight.keys());for(const t of e.references.values())for(const e of t)i.add(e);for(const[e,s]of t)(s.pending||s.preload)&&i.add(e);t.sort((e,t)=>t[1].at-e[1].at);const s=t.filter(([e])=>i.has(e));for(const e of t){if(s.length>=512)break;i.has(e[0])||s.push(e)}e.signed=Object.fromEntries(s)}resign(e,t){const i=this.bind(e);this.referenced=new Set(t),i.references.set(this.referenceOwner,new Set(this.referenced));const s=this.now(),n=new Set;for(const e of i.references.values())for(const t of e)n.add(t);const o={};for(const[e,t]of Object.entries(i.signed))n.has(e)&&s-t.at!!o[e]&&!o[e].pending))}markLoaded(e,t,i){const s=As(t);if(!s.startsWith("/api/houseplan/content/"))return;const n=this.bind(e).signed[s];if(n&&(!i||n.url===i)&&!n.loaded){n.loaded=!0;for(const t of[...this.bind(e).listeners])t()}}isReady(e,t){const i=As(t);if(!i.startsWith("/api/houseplan/content/"))return!0;const s=this.bind(e).signed[i];return!!s&&this.now()-s.ate.id===t)}function tp(e="file"){const t="draw"===e;return{source:e,showBorders:t,showNames:t,displayTouched:!1}}function ip(e,t){if(e.displayTouched)return{...e,source:t};const i="draw"===t;return{...e,source:t,showBorders:i,showNames:i}}function sp(e,t,i){return{...e,[t]:i,displayTouched:!0}}function np(e,t,i){return[e[0]*t+e[1]*i+e[2],e[3]*t+e[4]*i+e[5]]}function op(e,t){let i=0;for(const[s,n]of t){const t=np(e,s[0],s[1]);i=Math.max(i,Math.hypot(t[0]-n[0],t[1]-n[1]))}return i}const ap=e=>{if(null==e||""===e)return null;const t=Number(e);return Number.isFinite(t)?t:null},rp=e=>{const t=e,i=ap(Array.isArray(t)?t[0]:t?.x),s=ap(Array.isArray(t)?t[1]:t?.y);return null==i||null==s?null:[i,s]};function lp(e){if(!Array.isArray(e))return null;const t=[];for(const i of e){const e=rp(i);if(!e)return null;t.push(e)}if(t.length>1){const e=t[0],i=t[t.length-1];e[0]===i[0]&&e[1]===i[1]&&t.pop()}if(t.length<3)return null;let i=0,s=0,n=0;for(let e=0;ee+t[0],0)/t.length,t.reduce((e,t)=>e+t[1],0)/t.length]:[s/(3*i),n/(3*i)]}function cp(e){if(!Array.isArray(e))return null;const t=[];for(const i of e){const e=rp(i);if(!e)return null;t.push(e)}if(t.length>1){const e=t[0],i=t[t.length-1];e[0]===i[0]&&e[1]===i[1]&&t.pop()}const i=lp(t);if(!i)return null;const s=t.map(e=>e[0]),n=t.map(e=>e[1]);return{centroid:i,bbox:[Math.min(...s),Math.min(...n),Math.max(...s),Math.max(...n)]}}function hp(e){let t=e;if(t&&"object"==typeof t&&!Array.isArray(t)){const e=t;t=e.path??e.points??t}if(!Array.isArray(t)||!t.length)return[];let i=(t.some(e=>(e=>Array.isArray(e)?e.length>=2&&!Array.isArray(e[0])&&("object"!=typeof e[0]||null==e[0]):!!e&&"object"==typeof e&&("x"in e||"y"in e))(e))?[t]:t.filter(Array.isArray)).map(e=>e.map(rp).filter(e=>null!=e)).filter(e=>e.length>=2);i.length>64&&(i=i.slice(-64));const s=i.reduce((e,t)=>e+t.length,0);if(s<=4e3)return i.map(e=>e.slice());const n=4e3-2*i.length,o=i.map(e=>e.length-2),a=o.reduce((e,t)=>e+t,0),r=o.map(e=>n*e/a),l=r.map(Math.floor);let c=n-l.reduce((e,t)=>e+t,0);const h=r.map((e,t)=>({index:t,fraction:e-Math.floor(e)})).sort((e,t)=>t.fraction-e.fraction||e.index-t.index);for(let e=0;efunction(e,t){if(t>=e.length)return e.slice();if(t<=2)return[e[0],e[e.length-1]];const i=[];for(let s=0;se.slice()),i=t[t.length-1];return i?(i.length>2?i.pop():t.pop(),t):[]}function up(e){if(!e)return null;const t=e.vacuum_position||e.robot_position||null,i=t&&null!=ap(t.x)&&null!=ap(t.y)?{x:ap(t.x),y:ap(t.y),a:ap(t.a??t.angle??t.theta)}:null,s=hp(e.path?.path??e.path?.points??e.path),n=[],o=e.rooms,a=Array.isArray(o)?o.map((e,t)=>[String(e?.id??t),e]):o&&"object"==typeof o?Object.entries(o):[];for(const[e,t]of a){if(!t||"object"!=typeof t)continue;const i=String(t.name??t.label??"").trim(),s=[[ap(t.cx),ap(t.cy)],[ap(t.center?.x),ap(t.center?.y)],[ap(t.x),ap(t.y)]].find(([e,t])=>null!=e&&null!=t);let o=s?.[0]??null,a=s?.[1]??null;const r=cp(t.outline),l=ap(t.x0),c=ap(t.y0),h=ap(t.x1),d=ap(t.y1),p=(null!=l&&null!=c&&null!=h&&null!=d?[Math.min(l,h),Math.min(c,d),Math.max(l,h),Math.max(c,d)]:null)||r?.bbox||null;if(null!=o&&null!=a||!r||([o,a]=r.centroid),null!=o&&null!=a||!p||(o=(p[0]+p[2])/2,a=(p[1]+p[3])/2),i&&null!=o&&null!=a){const t={id:e,name:i,cx:o,cy:a};p&&([t.x0,t.y0,t.x1,t.y1]=p),n.push(t)}}const r=function(e){return String(e.map_name??e.current_map??e.map_index??e.selected_map??"default")}(e);return i||n.length||s.length?{pos:i,path:s,rooms:n,mapId:r}:null}function _p(e,t,i){if(!e||!t)return null;const s=t.attributes||{},n=e.split(".")[0]||"",o=null!=i?.platform?String(i.platform):null,a=!!rp(s.vacuum_position??s.robot_position),r=!(!s.rooms||"object"!=typeof s.rooms),l=hp(s.path?.path??s.path?.points??s.path).length>0,c=[s.map_name,s.current_map,s.map_index,s.selected_map].some(e=>null!=e),h="xiaomi_cloud_map_extractor"===o,d=r||l||c;let p=null;if(a?p="compatible":h?p="known_xcme_incomplete":d?p="partial":"camera"===n&&(p="camera"),!p)return null;const u=a?"camera"===n?300:200:h?100:d?50:0;return{entityId:e,name:String(s.friendly_name||e),platform:o,category:p,hasPosition:a,hasRooms:r,hasPath:l,hasMapId:c,score:u}}const mp=e=>e.toLowerCase().replace(/[\s_\-.,]+/g,"");function gp(e,t){const i=new Map(t.map(e=>[mp(e.name),e])),s=[],n=[];for(const t of e){const e=i.get(mp(t.name));e&&(s.push([[t.cx,t.cy],[e.cx,e.cy]]),n.push(t.name))}if(s.length<3)return null;const o=function(e){if(e.length<3)return null;let t=0,i=0,s=0,n=0,o=0,a=0,r=0,l=0,c=0,h=0,d=0,p=0;for(const[[u,_],[m,g]]of e){if(![u,_,m,g].every(Number.isFinite))return null;t+=u*u,i+=u*_,s+=u,n+=_*_,o+=_,a+=1,r+=u*m,l+=_*m,c+=m,h+=u*g,d+=_*g,p+=g}const u=[t,i,s,i,n,o,s,o,a],_=e=>{const[t,i,s,n,o,a,r,l,c]=u,h=t*(o*c-a*l)-i*(n*c-a*r)+s*(n*l-o*r);if(!Number.isFinite(h)||Math.abs(h)<1e-9)return null;const d=[(o*c-a*l)/h,(s*l-i*c)/h,(i*a-s*o)/h,(a*r-n*c)/h,(t*c-s*r)/h,(s*n-t*a)/h,(n*l-o*r)/h,(i*r-t*l)/h,(t*o-i*n)/h];return[d[0]*e[0]+d[1]*e[1]+d[2]*e[2],d[3]*e[0]+d[4]*e[1]+d[5]*e[2],d[6]*e[0]+d[7]*e[1]+d[8]*e[2]]},m=_([r,l,c]),g=_([h,d,p]);if(!m||!g)return null;const f=[m[0],m[1],m[2],g[0],g[1],g[2]];return f.every(Number.isFinite)?f:null}(s);return o?{matrix:o,matched:n,residual:op(o,s)}:null}function fp(e,t,i){const s=e[e.length-1];if(s&&s[0]===t[0]&&s[1]===t[1])return e;if(e.push(t),e.length<=600)return e;let n=function(e,t){if(e.length<3)return e.slice();const i=new Uint8Array(e.length);i[0]=i[e.length-1]=1;const s=[[0,e.length-1]];for(;s.length;){const[n,o]=s.pop(),[a,r]=e[n],[l,c]=e[o],h=l-a,d=c-r,p=Math.hypot(h,d)||1e-9;let u=0,_=-1;for(let t=n+1;tu&&(u=i,_=t)}_>0&&u>t&&(i[_]=1,s.push([n,_],[_,o]))}const n=[];for(let t=0;t600&&(n=n.filter((e,t)=>t%2==0||t===n.length-1)),n}function vp(e){return"cleaning"===e||"returning"===e||"on"===e}const bp={0:[1,0],90:[0,1],180:[-1,0],270:[0,-1]};function yp(e){const[t,i]=bp[e.rot]||[1,0],s=e.mir?-1:1;return[e.s*t*s,-e.s*i,e.ox,e.s*i*s,e.s*t,e.oy]}function wp(e){const t=e[0]*e[4]-e[1]*e[3];if(!Number.isFinite(t)||Math.abs(t)<1e-12)return null;const i=t<0,s=Math.sqrt(Math.abs(t));let n=180*Math.atan2(-e[1],e[4])/Math.PI;return n=(90*Math.round(n/90)%360+360)%360,{ox:e[2],oy:e[5],s:s,rot:n,mir:i}}function kp(e,t,i,s){const[n,o]=np(yp(t),i,s),a=yp({...e,ox:0,oy:0}),[r,l]=np(a,i,s);return{...e,ox:n-r,oy:o-l}}function xp(e){const t=e?.trail_mode;return"never"===t||"cleaning"===t||"always"===t?t:!1===e?.trail?"never":"cleaning"}const $p=new Set(["group","template","derivative","min_max","threshold","integration","statistics","trend","utility_meter","tod","switch_as_x","schedule"]);function Sp(e){const{hass:t,devices:i,markers:s,showEntities:n,currentBinding:o,currentDeviceId:a,labels:r}=e,l=wo(s),c=new Set(s.filter(e=>e.removed).map(e=>e.binding)),h=new Set;for(const e of i)e.id!==a&&("device"===e.bindingKind&&e.bindingRef&&h.add(`device:${e.bindingRef}`),"entity"===e.bindingKind&&e.bindingRef&&h.add(`entity:${e.bindingRef}`));const d=new Set;for(const e of i)"device"===e.bindingKind&&e.name&&d.add(`${e.name.trim()}|${e.area||""}`);const p=[];for(const e of Object.values(t?.devices||{})){if(!e||"service"===e.entry_type)continue;const t=`device:${e.id}`;if(h.has(t))continue;const i=String(e.name_by_user||e.name||e.id).trim();t!==o&&!c.has(t)&&d.has(`${i}|${e.area_id||""}`)||p.push({value:t,label:i,sub:(e.model||r.device)+("Group"===e.model?r.z2mGroup:""),kind:"device",ref:e.id,areaId:e.area_id||"",model:e.model||""})}for(const[e,i]of Object.entries(t?.entities||{})){const s=`entity:${e}`;if(h.has(s))continue;if(ko(t,e,l)&&!c.has(s))continue;const n=$p.has(i?.platform),o="group"===i?.platform;if(!n&&!o)continue;if(i?.hidden&&!c.has(s))continue;const a=t?.states?.[e];p.push({value:s,label:i?.name||a?.attributes?.friendly_name||e,sub:`${e.split(".")[0]} · ${o?r.group:r.helper}`,kind:"entity",ref:e,areaId:i?.area_id||i?.device_id&&t?.devices?.[i.device_id]?.area_id||"",model:"",parentDeviceId:i?.device_id||void 0})}if(n){const e=new Set(p.map(e=>e.value));for(const[i,s]of Object.entries(t?.entities||{})){const n=`entity:${i}`;if(h.has(n)||e.has(n)||s?.hidden&&!c.has(n))continue;const o=!!s?.device_id&&l.devices.has(s.device_id);if(ko(t,i,l)&&!c.has(n)&&!o)continue;const a=t?.states?.[i],d=s?.device_id?t?.devices?.[s.device_id]:null,u=d?String(d.name_by_user||d.name||""):"";p.push({value:n,label:s?.name||a?.attributes?.friendly_name||i,sub:`${i.split(".")[0]} · ${r.entity}${u?` · ${u}`:""}`,kind:"entity",ref:i,areaId:s?.area_id||d?.area_id||"",model:"",parentDeviceId:s?.device_id||void 0})}}return p.sort((e,t)=>e.label.localeCompare(t.label)||e.value.localeCompare(t.value))}const Mp={kind:"active",enabledEntityIds:[],allEntityIds:[]};function Cp(e){return e.startsWith("entity:")?"entity":"device"}function Dp(e){const t=e.binding.indexOf(":"),i=t>=0?e.binding.slice(t+1):"",s=Object.keys(e).filter(e=>!["id","binding","hidden"].includes(e));return e.id===`h${i}`&&0===s.length}const Pp={offStates:["off"],unknownUsesToggle:!0},Tp={light:Pp,switch:Pp,fan:Pp,humidifier:Pp,input_boolean:Pp,automation:Pp,remote:Pp,group:Pp,climate:{...Pp,featureMasks:{turn_on:256,turn_off:128,toggle:384}},media_player:{...Pp,featureMasks:{turn_on:128,turn_off:256,toggle:384}},siren:{...Pp,featureMasks:{turn_on:1,turn_off:2,toggle:3}},vacuum:{...Pp,featureMasks:{turn_on:1,turn_off:2,toggle:3}},water_heater:{...Pp,featureMasks:{turn_on:8,turn_off:8,toggle:8}},camera:{...Pp,featureMasks:{turn_on:1,turn_off:1,toggle:1}}};function Ip(e){return e.slice(0,e.indexOf("."))}function Rp(e,t,i){if(!e?.services||"object"!=typeof e.services)return!1;const s=e.services?.[t];return!!s&&Object.prototype.hasOwnProperty.call(s,i)}function Ap(e,t,i){return Rp(e,t,i)?{domain:t,service:i}:Rp(e,"homeassistant",i)?{domain:"homeassistant",service:i}:null}function zp(e,t){return e?.entities?.[t]||null}function Fp(e,t,i){const s=e?.states?.[i],n=zp(t,i);return s?.attributes?.friendly_name||n?.name||n?.original_name||i}function Np(e,t){const i=zp(e,t);if(null!=i?.disabled_by)return!0;const s=i?.device_id?e?.devices?.[i.device_id]:null;return null!=s?.disabled_by}function Ep(e,t){const i=e?.attributes?.supported_features;if(null==i||""===i)return!1;const s=Number(i);return Number.isFinite(s)&&(s&t)===t}function Op(e,t,i){const s=Ip(i);if("lock"===s||"alarm_control_panel"===s)return!0;if("cover"!==s)return!1;const n=e?.states?.[i],o=zp(t,i),a=String(n?.attributes?.device_class||o?.device_class||o?.original_device_class||"");return Ui.has(a)}function Hp(e,t,i,s,n){return{ref:i,entityId:s,name:s?Fp(e,t,s):null,reason:n}}function Lp(e,t,i,s,n,o=null){return{target:null,skipped:Hp(e,t,i,s,n),semantics:o,nextEffect:null,command:null}}function Bp(e,t,i,s,n=i){const o=Ip(i);if("cover"!==o&&"valve"!==o)return function(e,t,i,s,n=i){const o=Ip(i),a=e?.states?.[i];if(Np(t,i))return Lp(e,t,n,i,"ha-disabled","power");if(Op(e,t,i))return Lp(e,t,n,i,"secure");if(!a)return Lp(e,t,n,i,"missing","power");if("unavailable"===a.state)return Lp(e,t,n,i,"unavailable","power");const r=Tp[o];if(!r)return Lp(e,t,n,i,"unsupported");let l,c;if("unknown"===a.state||""===a.state){if(!r.unknownUsesToggle)return Lp(e,t,n,i,"unsupported","power");l="toggle",c="toggle"}else r.offStates.includes(String(a.state))?(l="turn-on",c="turn_on"):(l="turn-off",c="turn_off");const h=r.featureMasks?.[c];if(h&&!Ep(a,h))return Lp(e,t,n,i,"unsupported","power");const d=Ap(e,o,c);return d?{target:{entityId:i,name:Fp(e,t,i),state:String(a.state||""),via:s},skipped:null,semantics:"power",nextEffect:l,command:{...d,data:{entity_id:i}}}:Lp(e,t,n,i,"unsupported","power")}(e,t,i,s,n);if(Np(t,i))return Lp(e,t,n,i,"ha-disabled",o);if(Op(e,t,i))return Lp(e,t,n,i,"secure",o);const a=e?.states?.[i];if(!a)return Lp(e,t,n,i,"missing",o);if("unavailable"===a.state)return Lp(e,t,n,i,"unavailable",o);const r=function(e,t,i){const s=String(i?.state||""),n="closed"===s?{service:`open_${t}`,effect:"open",feature:1}:"open"===s?{service:`close_${t}`,effect:"close",feature:2}:"opening"===s||"closing"===s?{service:`stop_${t}`,effect:"stop",feature:8}:null;return n&&Ep(i,n.feature)&&Rp(e,t,n.service)?n:Ep(i,3)&&Rp(e,t,"toggle")?{service:"toggle",effect:"toggle"}:null}(e,o,a);if(!r)return Lp(e,t,n,i,"unsupported",o);return{target:{entityId:i,name:Fp(e,t,i),state:String(a.state||""),via:s},skipped:null,semantics:o,nextEffect:r.effect,command:{domain:o,service:r.service,data:{entity_id:i}}}}function jp(e){return"entity"===e.bindingKind?e.bindingRef&&Ms(e.bindingRef)?[e.bindingRef]:[]:"virtual"===e.bindingKind?[]:eo(e)}function qp(e){const t=e.marker?.toggle_entity;return t&&jp(e).includes(t)?t:null}function Wp(e,t,i){const s=function(e,t){if("entity"===e.bindingKind&&e.bindingRef){const t=e.marker?.light_entity?to(e):null;return[...new Set([t,e.bindingRef].filter(e=>!!e))]}const i=e.entities.length?e.entities:e.allEntities||[],s=e.marker?.light_entity||e.primary&&Ms(e.primary)?to(e):null;return[...new Set([s,...Yn(t,i)].filter(e=>!!e))]}(i,t);if(!s.length)return null;const n="entity"===i.bindingKind?"binding":"device-role";if("binding"===n)return Bp(e,t,s[0],n);let o=null,a=null;for(const i of s){const s=Bp(e,t,i,n);if(s.command)return s;const r=s.skipped?.reason;if("missing"===r||"unavailable"===r||"secure"===r)return s;"ha-disabled"!==r?o||=s:a||=s}return o||a}function Up(e){const t=e.skipped?.reason;return"missing"===t?"unavailable":t||"unsupported"}function Gp(e,t){return{origin:e,kind:"none",semantics:null,targets:[],skippedTargets:[],noneReason:t,nextEffect:null,command:null}}function Vp(e,t){return"cover"===e||"toggle"===e?"toggle":"more-info"===e||"run"===e||"info"===e?e:(null==e||""===e)&&"light"===t?"toggle":"info"}function Kp(e,t){return{origin:e,kind:"single",semantics:t.semantics,targets:t.target?[t.target]:[],skippedTargets:t.skipped?[t.skipped]:[],noneReason:t.command?null:Up(t),nextEffect:t.nextEffect,command:t.command}}function Yp(e,t,i,s=[]){const n=new Map,o=[],a=new Set,r=e=>{const t=`${e.ref}\n${e.entityId||""}\n${e.reason}`;a.has(t)||(a.add(t),o.push(e))};for(const e of s)r(e);for(const s of i){const i=Bp(e,t,s.entityId,s.via,s.ref);i.target?n.set(s.entityId,n.get(s.entityId)||i.target):i.skipped&&r(i.skipped)}const l=[...n.values()];if(!l.length)return{origin:"explicit-toggle",kind:"group",semantics:"group-power",targets:l,skippedTargets:o,noneReason:o.length&&o.every(e=>"secure"===e.reason)?"secure":"configured-targets-missing",nextEffect:null,command:null};const c=l.some(e=>"on"===e.state)?"turn_off":"turn_on",h=Ap(e,"homeassistant",c);return h?{origin:"explicit-toggle",kind:"group",semantics:"group-power",targets:l,skippedTargets:o,noneReason:null,nextEffect:"turn_on"===c?"turn-on":"turn-off",command:{...h,data:{entity_id:l.map(e=>e.entityId)}}}:{origin:"explicit-toggle",kind:"group",semantics:"group-power",targets:[],skippedTargets:[...o,...l.map(i=>Hp(e,t,i.entityId,i.entityId,"unsupported"))],noneReason:"unsupported",nextEffect:null,command:null}}function Jp(e){return String(e.marker?.id||e.id||"")}function Xp(e){const{hass:t,device:i}=e,s=e.registryHass||t,n=function(e){return"toggle"===e.tapAction?"explicit-toggle":"cover"===e.tapAction?"legacy-cover":!e.tapAction&&e.primary?.startsWith("light.")?"default-light":null}(i);if(!n)return null;const o=i.marker;if("explicit-toggle"===n&&bn(o)){const t=String(o?.id||i.id||""),s=ro(e.devices).get(t);if(s)return function(e,t){const i=e.registryHass||e.hass;return Yp(e.hass,i,t.driverEids.map(e=>({entityId:e,via:"control-marker-driver",ref:`marker:${t.markerId}`})))}(e,s);const a=yn(o,e.virtualLights);return{origin:n,kind:"single",semantics:"power",targets:[{entityId:"",name:i.name,state:a?"on":"off",via:"virtual-light"}],skippedTargets:[],noneReason:null,nextEffect:a?"turn-off":"turn-on",command:null,operation:{kind:"virtual-light",markerId:o.id}}}if("explicit-toggle"===n){if(Zn(i.marker?.binding,i.marker?.controls??i.controls,i.entities).length)return function(e,t=null){const{hass:i,device:s,devices:n}=e,o=e.registryHass||i,a=Zn(s.marker?.binding,s.marker?.controls??s.controls,s.entities),r=e.lightSources||ho(i,n,null,e.virtualLights),l=new Map;for(const e of r){if(!e.key.startsWith("marker:"))continue;const t=l.get(e.key)||[];t.push(e),l.set(e.key,t)}const c=ro(n),h=new Map;for(const e of n){const t=String(e.marker?.id||e.id||"");t&&h.set(t,e)}const d=[],p=[];t&&d.push({entityId:t,via:"entity"===s.bindingKind?"binding":"device-role",ref:t});for(const e of a){if(!e.startsWith("marker:")){Ms(e)?d.push({entityId:e,via:"control-entity",ref:e}):p.push(Hp(i,o,e,e.includes(".")?e:null,"unsupported"));continue}const t=l.get(e)||[];if(!t.length){const t=h.get(e.slice(7));if("ha_disabled"===t?.bindingStatus?.kind){const s=t.bindingStatus.allEntityIds[0]||null;p.push({ref:e,entityId:s,name:t.name||(s?Fp(i,o,s):null),reason:"ha-disabled"})}else p.push(Hp(i,o,e,null,"missing"));continue}const n=[...new Set(t.flatMap(e=>e.serviceEids))];if(n.length){for(const t of n)d.push({entityId:t,via:"control-entity",ref:e});continue}const a=t.some(e=>e.passive),r=e.slice(7),u=Jp(s),_=c.get(r)?.controllers.find(e=>Jp(e.device)===u);if(a&&_?.driverEids.length)for(const t of _.driverEids)d.push({entityId:t,via:"control-marker-driver",ref:e});else p.push(Hp(i,o,e,null,a?"missing":"unsupported"))}return Yp(i,o,d,p)}(e,qp(i))}if("legacy-cover"===n){const e=i.entities.find(e=>e.startsWith("cover."))||i.allEntities?.find(e=>e.startsWith("cover."));return e?Kp(n,Bp(t,s,e,"device-role")):Gp(n,"no-actionable-entity")}const a=qp(i),r=a?Bp(t,s,a,"entity"===i.bindingKind?"binding":"device-role"):Wp(t,s,i);return r?Kp(n,r):Gp(n,i.virtual||"virtual"===i.bindingKind?"no-actionable-entity":"no-binding")}function Zp(e){if(!e)return[];const t=Array.isArray(e.data.entity_id)?e.data.entity_id:[e.data.entity_id];return[...new Set(t)].sort()}function Qp(e){return e?e.operation?e.operation:e.command?{kind:"ha-service",command:e.command}:null:null}const eu=new Set(["missing","ha-disabled","unavailable"]);function tu(e,t){const i=Qp(e),s=Qp(t);return!(!i||!s||i.kind!==s.kind)&&("virtual-light"===i.kind&&"virtual-light"===s.kind?i.markerId===s.markerId:"ha-service"===i.kind&&"ha-service"===s.kind&&function(e,t){const i=Zp(e),s=Zp(t);return i.length===s.length&&i.every((e,t)=>e===s[t])}(i.command,s.command))}const iu=e=>!!e&&e.length>=2&&Number.isFinite(e[0])&&Number.isFinite(e[1]);function su(e,t=5,i=5){return Ml(e.cm,t,i)/2}function nu(e,t,i=1,s=5,n=5,o=0){const a=e.host;if(!a||"partition"!==a.kind||"string"!=typeof a.id||!a.id)return{resolved:null,reason:"invalid-host"};const r=t.find(e=>e.id===a.id);if(!r)return{resolved:null,reason:"missing-partition"};if(!iu(r.a)||!iu(r.b)||!Number.isFinite(a.t)||a.t<0||a.t>1)return{resolved:null,reason:"invalid-position"};const l=r.b[0]-r.a[0],c=r.b[1]-r.a[1],h=Math.hypot(l,c),d=Number(e.length)*i;if(!(h>1e-9&&d>0&&Number.isFinite(d)))return{resolved:null,reason:"invalid-length"};const p=a.t*h;if(p-d/2h-o+1e-9)return{resolved:null,reason:"does-not-fit"};const u=l/h,_=c/h;let m=180*Math.atan2(c,l)/Math.PI;return m>=90?m-=180:m<-90&&(m+=180),{reason:null,resolved:{opening:e,host:a,partition:r,center:[r.a[0]+l*a.t,r.a[1]+c*a.t],angle:m,length:d,depth:Ml(r.cm,s,n),t:a.t,axis:{a:[r.a[0],r.a[1]],b:[r.b[0],r.b[1]],ux:u,uy:_,length:h}}}}function ou(e,t,i=1,s=5,n=5){return nu(e,t,i,s,n,0)}function au(e,t,i=1,s=5,n=5){const o=e.host,a="partition"===o?.kind?t.find(e=>e.id===o.id):void 0;if(!a)return ou(e,t,i,s,n);const r=nu(e,t,i,s,n,su(a,s,n));return"does-not-fit"===r.reason?{resolved:null,reason:"does-not-fit-jamb"}:r}function ru(e,t){return!e||(e.length!==t.length||e.host?.kind!==t.host?.kind||e.host?.id!==t.host?.id||e.host?.t!==t.host?.t)}function lu(e){const{center:t,length:i,axis:s,host:n}=e,o=i/2;return{hostId:n.id,a:[t[0]-s.ux*o,t[1]-s.uy*o],b:[t[0]+s.ux*o,t[1]+s.uy*o],depth:e.depth}}function cu(e,t=!1){const i=t?1:-1,s=e.depth/2;return{ox:-e.axis.uy*i*s,oy:e.axis.ux*i*s,cm:e.partition.cm,side:i}}function hu(e,t,i){return e.flatMap(e=>!iu(e.a)||!iu(e.b)||Math.hypot(e.b[0]-e.a[0],e.b[1]-e.a[1])<=1e-9?[]:[{roomId:"",a:[e.a[0],e.a[1]],b:[e.b[0],e.b[1]],key:`partition:${e.id}`,kind:"outer",cm:e.cm,open:!1,half:Ml(e.cm,t,i)/2,partitionHost:{kind:"partition",id:e.id}}])}function du(e,t,i){const{center:s,length:n,axis:o}=e,a=n/2,r=t.flatMap(e=>{if(!e.kind||e.open)return[];const t=e.b[0]-e.a[0],n=e.b[1]-e.a[1],r=Math.hypot(t,n);if(!(r>i))return[];const l=t/r,c=n/r;if(Math.abs(o.ux*c-o.uy*l)>1e-6)return[];if(Math.abs((s[0]-e.a[0])*c-(s[1]-e.a[1])*l)>i)return[];const h=e=>(e[0]-s[0])*o.ux+(e[1]-s[1])*o.uy,d=h(e.a),p=h(e.b),u=Math.max(-a,Math.min(d,p)),_=Math.min(a,Math.max(d,p));return _>=u-i?[[u,_]]:[]});if(!r.length)return!1;r.sort((e,t)=>e[0]-t[0]||e[1]-t[1]);let l=-a;for(const[e,t]of r){if(e>l+i)return!1;if(l=Math.max(l,t),l>=a-i)return!0}return!1}function pu(e,t,i){return{...e,x:t.center[0]/i,y:t.center[1]/i,angle:t.angle}}const uu=.001;function _u(e){return!!e&&e.length>=2&&Number.isFinite(e[0])&&Number.isFinite(e[1])}function mu(e){return`${e[0].toFixed(6)},${e[1].toFixed(6)}`}function gu(e,t,i){return Math.abs(e[0]-t[0])"room"!==e.kind&&i.some(i=>i!==e&&function(e,t,i){const s=e.b[0]-e.a[0],n=e.b[1]-e.a[1],o=Math.hypot(s,n);if(!(o>i))return!1;const a=s/o,r=n/o,l=t=>Math.abs((t[0]-e.a[0])*r-(t[1]-e.a[1])*a);if(l(t.a)>i||l(t.b)>i)return!1;const c=(t.a[0]-e.a[0])*a+(t.a[1]-e.a[1])*r,h=(t.b[0]-e.a[0])*a+(t.b[1]-e.a[1])*r;return Math.min(o,Math.max(c,h))-Math.max(0,Math.min(c,h))>i}(e,i,t))),n=s.map(e=>{const[t,i]=fu(e.a,e.b);return{a:t,b:i,key:`hidden|${bu(e,t,i)}`,sourceKind:e.kind,sourceId:e.id}}).sort((e,t)=>e.key.localeCompare(t.key)),o=s.flatMap(e=>[e.a,e.b].map((t,i)=>({point:[t[0],t[1]],key:`hidden|${vu(e)}|endpoint-${i}`,sourceKind:e.kind,sourceId:e.id}))).sort((e,t)=>e.key.localeCompare(t.key));return{segments:n,endpoints:o}}function xu(e){const t=e.epsilon??uu,i=e.roomCuts||[],s=new Map;for(const t of e.partitionCuts||[]){if("string"!=typeof t.hostId||!t.hostId||!_u(t.a)||!_u(t.b))continue;const e=s.get(t.hostId)||[];e.push([t.a[0],t.a[1],t.b[0],t.b[1]]),s.set(t.hostId,e)}const n=[];for(const[t,s]of pi(e.space.rooms).entries())s.length<4||n.push({a:[s[0],s[1]],b:[s[2],s[3]],kind:"room",id:`room-edge-${t}`,cuts:i});for(const t of e.space.room_drafts||[])if(t.id!==e.activeDraftId)for(let e=0;e+1wu(s,e,t)))continue;const e=mu(s);a.has(e)||a.set(e,{point:[s[0],s[1]],key:e})}}}return{segments:[...o.values()].sort((e,t)=>e.key.localeCompare(t.key)),endpoints:[...a.values()].sort((e,t)=>e.key.localeCompare(t.key))}}function $u(e,t,i){return t.some(t=>_u(t)&&gu(e,t,i))}function Su(e,t,i){if(!i)return!0;const s=e-i.distance;return s<-1e-9||Math.abs(s)<=1e-9&&t.localeCompare(i.key)<0}function Mu(e,t,i){const s=i.epsilon??uu,n=i.excludePoints||[],o=[...e.endpoints,...(i.extraEndpoints||[]).filter(e=>_u(e.point)).map(e=>({point:[e.point[0],e.point[1]],key:e.key}))],a=new Map;for(const e of o){if($u(e.point,n,s))continue;const t=mu(e.point),i=a.get(t);(!i||e.key.localeCompare(i.key)<0)&&a.set(t,e)}return[...a.values()].map(e=>({kind:"endpoint",point:[...e.point],key:e.key,distance:Math.hypot(t[0]-e.point[0],t[1]-e.point[1])})).filter(e=>e.distance<=i.tolerance).sort((e,t)=>e.distance-t.distance||e.key.localeCompare(t.key))}function Cu(e,t){if(!e.length)return null;if(t>0){const i=new Set;for(let s=0;s1)return{kind:"ambiguous",candidate:null,conflicts:e.filter(e=>i.has(e.key)).map(e=>({point:[...e.point],key:e.key}))}}return{kind:"resolved",candidate:e[0],conflicts:[]}}function Du(e,t,i){const s=e.b[0]-e.a[0],n=e.b[1]-e.a[1],o=Math.hypot(s,n);if(!(o>0))return[...e.a];const a=s/o,r=n/o,l=Math.max(0,Math.min(o,(t[0]-e.a[0])*a+(t[1]-e.a[1])*r)),c=i>0?Math.max(0,Math.min(o,Math.round(l/i)*i)):l;return[e.a[0]+a*c,e.a[1]+r*c]}function Pu(e,t,i){if(!(_u(t)&&_u(i.anchor)&&i.tolerance>=0))return{kind:"none",candidate:null,conflicts:[]};const s=i.epsilon??uu,n=function(e,t){const i=t[0]-e[0],s=t[1]-e[1];if(!(Math.hypot(i,s)>Number.EPSILON))return null;const n=Math.round(Math.atan2(s,i)/(Math.PI/4))*(Math.PI/4);return[Math.cos(n),Math.sin(n)]}(i.anchor,t);if(!n)return{kind:"none",candidate:null,conflicts:[]};const o=Mu(e,t,i).filter(e=>function(e,t,i,s){const n=e[0]-t[0],o=e[1]-t[1];return n*i[0]+o*i[1]>s&&Math.abs(n*i[1]-o*i[0])<=s*Math.max(Math.hypot(n,o),1)}(e.point,i.anchor,n,s)),a=Cu(o,i.distinguishTolerance||0);if(a)return a;let r=null;for(const o of e.segments){const e=o.b[0]-o.a[0],a=o.b[1]-o.a[1],l=o.a[0]-i.anchor[0],c=o.a[1]-i.anchor[1],h=n[0]*a-n[1]*e;let d=null;if(Math.abs(h)<=s*Math.max(Math.hypot(e,a),1)){if(Math.abs(l*n[1]-c*n[0])>s*Math.max(Math.hypot(l,c),1))continue;const e=[o.a,o.b].map(e=>({point:e,along:(e[0]-i.anchor[0])*n[0]+(e[1]-i.anchor[1])*n[1]})).filter(e=>e.along>s).sort((e,t)=>e.along-t.along);e.length&&(d=[...e[0].point])}else{const t=(l*a-c*e)/h,o=(l*n[1]-c*n[0])/h;t>s&&o>=-s&&o<=1+s&&(d=[i.anchor[0]+n[0]*t,i.anchor[1]+n[1]*t])}if(!d)continue;const p=Math.hypot(t[0]-d[0],t[1]-d[1]);p>i.tolerance||$u(d,i.excludePoints||[],s)||!Su(p,o.key,r)||(r={kind:"line",point:d,key:o.key,distance:p,segment:o})}return r?{kind:"resolved",candidate:r,conflicts:[]}:{kind:"none",candidate:null,conflicts:[]}}const Tu=.001;function Iu(e,t,i,s){const n=Number.isFinite(e)&&e>0?Math.floor(e):0,o=e=>"number"==typeof e&&Number.isFinite(e)&&e>=0?e:null,a=o(i),r=o(s)??0,l=[];let c=null;for(let e=0;e=2&&Number.isFinite(e[0])&&Number.isFinite(e[1])}function zu(e,t,i,s){return e*s-t*i}function Fu(e){let t=0;for(let i=0;ie.localeCompare(t)),t[0]}function Lu(e,t,i){const s=[...e];for(let e=!0;e&&s.length>=3;){e=!1;for(let n=0;n=0){s.splice(n,1),e=!0;break}}}return s}function Bu(e,t){const i=e.map(e=>Math.max(0,Math.min(1,e))).sort((e,t)=>e-t),s=[];for(const e of i)(!s.length||Math.abs(e-s[s.length-1])>t)&&s.push(e);return s}function ju(e,t){return[e.a[0]+(e.b[0]-e.a[0])*t,e.a[1]+(e.b[1]-e.a[1])*t]}function qu(e,t,i){const s=function(e,t){const i=t.b[0]-t.a[0],s=t.b[1]-t.a[1],n=i*i+s*s;return n>0?((e[0]-t.a[0])*i+(e[1]-t.a[1])*s)/n:0}(e,t);if(s<-i||s>1+i)return null;const n=ju(t,s);return Math.hypot(n[0]-e[0],n[1]-e[1])<=i?Math.max(0,Math.min(1,s)):null}function Wu(e,t,i,s,n){const o=e.b[0]-e.a[0],a=e.b[1]-e.a[1],r=t.b[0]-t.a[0],l=t.b[1]-t.a[1],c=t.a[0]-e.a[0],h=t.a[1]-e.a[1],d=zu(o,a,r,l),p=Math.max(Math.hypot(o,a),Math.hypot(r,l),1);if(Math.abs(d)>n*p){const e=zu(c,h,r,l)/d,t=zu(c,h,o,a)/d;return void(e>=-n&&e<=1+n&&t>=-n&&t<=1+n&&(i.push(Math.max(0,Math.min(1,e))),s.push(Math.max(0,Math.min(1,t)))))}if(!(Math.abs(zu(c,h,o,a))>n*p)){for(const s of[t.a,t.b]){const t=qu(s,e,n);null!=t&&i.push(t)}for(const i of[e.a,e.b]){const e=qu(i,t,n);null!=e&&s.push(e)}}}function Uu(e){let t=e+1|0;return t^=t<<13,t^=t>>>17,t^=t<<5,t>>>0}function Gu(e,t){return e.minY-t.minY||e.index-t.index}function Vu(e){return e.subtreeMaxY=Math.max(e.item.maxY,e.left?.subtreeMaxY??-1/0,e.right?.subtreeMaxY??-1/0),e}function Ku(e){const t=e.right;return e.right=t.left,t.left=Vu(e),Vu(t)}function Yu(e){const t=e.left;return e.left=t.right,t.right=Vu(e),Vu(t)}function Ju(e,t){return e?(Gu(t,e.item)<0?(e.left=Ju(e.left,t),e.left.priority0)e.right=Xu(e.right,t);else{if(!e.left)return e.right;if(!e.right)return e.left;e.left.priority=t&&Zu(e.left,t,i,s),e.item.minY<=i&&e.item.maxY>=t&&s.push(e.item),e.item.minY<=i&&Zu(e.right,t,i,s))}function Qu(e,t=.001){const i=Number.isFinite(t)&&t>0?t:Tu,s=e.filter(e=>Au(e.a)&&Au(e.b)&&"string"==typeof e.key&&e.key.length>0&&Math.hypot(e.b[0]-e.a[0],e.b[1]-e.a[1])>i),n=s.map(()=>[0,1]);!function(e,t,i){const s=e.map((e,t)=>({index:t,minX:Math.min(e.a[0],e.b[0])-i,maxX:Math.max(e.a[0],e.b[0])+i,minY:Math.min(e.a[1],e.b[1])-i,maxY:Math.max(e.a[1],e.b[1])+i})),n=[...s].sort((e,t)=>e.minX-t.minX||e.minY-t.minY||e.index-t.index),o=[...s].sort((e,t)=>e.maxX-t.maxX||e.index-t.index),a=new Set;let r=null,l=0;for(const s of n){for(;le.index-t.index);for(const o of n)Wu(e[o.index],e[s.index],t[o.index],t[s.index],i);a.add(s.index),r=Ju(r,s)}}(s,n,i);const o=new Map;for(let e=0;e({key:e,a:t.a,b:t.b,sourceKeys:[...t.sourceKeys].sort((e,t)=>e.localeCompare(t))})).sort((e,t)=>e.key.localeCompare(t.key))}function e_(e,t=.001){const i=Number.isFinite(t)&&t>0?t:Tu,s=Qu(e,i),n=new Map,o=new Map,a=new Map(s.map(e=>[e.key,e]));for(const e of s){const t=Eu(e.a,i),s=Eu(e.b,i);n.set(t,e.a),n.set(s,e.b),o.has(t)||o.set(t,new Set),o.has(s)||o.set(s,new Set),o.get(t).add(s),o.get(s).add(t)}const r=new Map;for(const[e,t]of o){const i=n.get(e);r.set(e,[...t].sort((e,t)=>{const s=n.get(e),o=n.get(t);return Math.atan2(s[1]-i[1],s[0]-i[0])-Math.atan2(o[1]-i[1],o[0]-i[0])||e.localeCompare(t)}))}const l=new Set,c=new Map,h=(e,t)=>`${e}>${t}`;for(const e of s){const t=[Eu(e.a,i),Eu(e.b,i)];for(const[e,o]of[t,[t[1],t[0]]]){if(l.has(h(e,o)))continue;const t=[],d=[];let p=e,u=o,_=!1;for(let i=0;i<=2*s.length+2;i++){const i=h(p,u);if(l.has(i)){_=p===e&&u===o;break}l.add(i),t.push(p),d.push(Ou(p,u));const s=r.get(u)||[],n=s.indexOf(p);if(n<0||!s.length)break;if(p=u,u=s[(n-1+s.length)%s.length],p===e&&u===o){_=!0;break}}if(!_||new Set(t).size<3||new Set(t).size!==t.length)continue;const m=t.map(e=>n.get(e)),g=Fu(m);if(!(g>i*i))continue;const f=Hu(Lu(t,n,i)),v=new Set;for(const e of d)for(const t of a.get(e)?.sourceKeys||[])v.add(t);const b={ring:m,key:f,area:g,atomKeys:[...d],sourceKeys:[...v].sort((e,t)=>e.localeCompare(t))};c.has(f)||c.set(f,b)}}return{atoms:s,faces:[...c.values()].sort((e,t)=>e.area-t.area||e.key.localeCompare(t.key))}}function t_(e,t,i=.001){if(!Au(t))return null;const s=e.faces.filter(e=>{if(e.ring.some((s,n)=>function(e,t,i,s){const n=i[0]-t[0],o=i[1]-t[1],a=n*n+o*o;if(!(a>0))return Math.hypot(e[0]-t[0],e[1]-t[1])<=s;const r=((e[0]-t[0])*n+(e[1]-t[1])*o)/a;return!(r<0||r>1)&&Math.hypot(t[0]+n*r-e[0],t[1]+o*r-e[1])<=s}(t,s,e.ring[(n+1)%e.ring.length],i)))return!1;let s=!1;for(let i=0,n=e.ring.length-1;it[1]!=a[1]>t[1]&&t[0]<(a[0]-o[0])*(t[1]-o[1])/(a[1]-o[1])+o[0]&&(s=!s)}return s});return[...s].sort((e,t)=>e.area-t.area||e.key.localeCompare(t.key))[0]||null}function i_(e,t,i){const s=i[0]-t[0],n=i[1]-t[1],o=s*s+n*n,a=o>0?Math.max(0,Math.min(1,((e[0]-t[0])*s+(e[1]-t[1])*n)/o)):0;return Math.hypot(t[0]+s*a-e[0],t[1]+n*a-e[1])}function s_(e,t,i=.5){let s=Math.abs(e-t)%180;return s>90&&(s=180-s),s<=i}function n_(e,t,i,s){const n=e.filter(e=>"outer"===e.kind&&!e.open&&e.cm>0).map(e=>({interval:e,reusePartitionId:t.find(t=>function(e,t,i){return!(Math.abs(e.cm-t.cm)>1e-6)&&(s_(180*Math.atan2(e.b[1]-e.a[1],e.b[0]-e.a[0])/Math.PI,180*Math.atan2(t.b[1]-t.a[1],t.b[0]-t.a[0])/Math.PI,.001)&&i_(t.a,e.a,e.b)<=i&&i_(t.b,e.a,e.b)<=i)}(t,e,s))?.id||null})),o=new Map;for(const t of i){if("partition"===t.host?.kind)continue;const i=e.map(e=>{const i=180*Math.atan2(e.b[1]-e.a[1],e.b[0]-e.a[0])/Math.PI;return{interval:e,distance:s_(t.angle,i)?i_([t.x,t.y],e.a,e.b):1/0}}).filter(e=>e.distance<=s).sort((e,t)=>e.distance-t.distance||e.interval.key.localeCompare(t.interval.key)),n=i[0]?.interval;"outer"===n?.kind&&!n.open&&n.cm>0&&o.set(t.id,n.key)}return{materialize:n,openingIntervals:o,removeOpeningIds:[...o.keys()].sort((e,t)=>e.localeCompare(t))}}function o_(e,t){const i=t.b[0]-t.a[0],s=t.b[1]-t.a[1],n=i*i+s*s;return n>0?Math.max(0,Math.min(1,((e[0]-t.a[0])*i+(e[1]-t.a[1])*s)/n)):0}const a_=e=>!e.key.startsWith("static:room|");function r_(e,t){const i=t.b[0]-t.a[0],s=t.b[1]-t.a[1],n=i*i+s*s;if(!(n>0))return null;const o=((e[0]-t.a[0])*i+(e[1]-t.a[1])*s)/n,a=[t.a[0]+i*o,t.a[1]+s*o];return{point:a,t:o,distance:Math.hypot(a[0]-e[0],a[1]-e[1])}}function l_(e,t){return t>0?Math.hypot(e[0]-Math.round(e[0]/t)*t,e[1]-Math.round(e[1]/t)*t):0}function c_(e,t,i){if(a_(e.source)!==a_(t.source))return a_(e.source)?e:t;const s=l_(e.point,i),n=l_(t.point,i);return Math.abs(s-n)>1e-9?s>n?e:t:function(e,t){return e[0]-t[0]||e[1]-t[1]}(e.point,t.point)>0?e:t}function h_(e){return[e.from[0],e.from[1],e.to[0],e.to[1],e.targetKind].map(e=>"number"==typeof e?e.toFixed(6):e).join("|")}function d_(e,t){return e.map(e=>e.key!==t.sourceKey?e:{...e,[t.endpoint]:[...t.to]})}function p_(e,t){const i=t.epsilon??.001;if(!(t.maxDistance>=0)||!t.point&&!t.requiredSourceKey)return{kind:"none"};const s=new Map,n=e.flatMap(e=>[{source:e,endpoint:"a",point:e.a},{source:e,endpoint:"b",point:e.b}]);for(let e=0;ei)||l>t.maxDistance)continue;const c=c_(a,r,t.gridStep||0),h=c===a?r:a;if(!a_(c.source))continue;const d={sourceKey:c.source.key,endpoint:c.endpoint,from:[c.point[0],c.point[1]],to:[h.point[0],h.point[1]],targetSourceKey:h.source.key,targetKind:"endpoint",distance:l};s.set(h_(d),d)}for(const o of n)if(a_(o.source))for(const n of e){if(n===o.source)continue;const e=r_(o.point,n);if(!e||e.t<=i||e.t>=1-i||!(e.distance>i)||e.distance>t.maxDistance)continue;const a={sourceKey:o.source.key,endpoint:o.endpoint,from:[o.point[0],o.point[1]],to:e.point,targetSourceKey:n.key,targetKind:"line",distance:e.distance};s.set(h_(a),a)}const o=[];for(const n of s.values()){const s=e_(d_(e,n),i),a=t.point?t_(s,t.point,i):s.faces.find(e=>!t.requiredSourceKey||e.sourceKeys.includes(t.requiredSourceKey))||null;a&&o.push({proposal:n,face:a})}if(o.sort((e,t)=>e.proposal.distance-t.proposal.distance||h_(e.proposal).localeCompare(h_(t.proposal))),!o.length)return{kind:"none"};return new Set(o.map(e=>`${h_(e.proposal)}|${e.face.key}`)).size>1?{kind:"ambiguous",proposals:o.map(e=>e.proposal)}:{kind:"repair",...o[0]}}const u_=1e-5;function __(e){const t=e.map(()=>[]);for(let i=0;i=1-1e-9||p<=1e-9||p>=1-1e-9||(t[i].push(d),t[a].push(p))}}const i=[];for(let s=0;se-t),1];for(let e=1;e{const i=t[2]-t[0],s=t[3]-t[1],n=i*i+s*s;if(!(n>0))return Math.hypot(e[0]-t[0],e[1]-t[1]);const o=Math.max(0,Math.min(1,((e[0]-t[0])*i+(e[1]-t[1])*s)/n));return Math.hypot(e[0]-(t[0]+o*i),e[1]-(t[1]+o*s))},f_=(e,t,i,s)=>{const n=s[2]-s[0],o=s[3]-s[1],a=t*o-i*n;if(Math.abs(a)<1e-12)return 1/0;const r=s[0]-e[0],l=s[1]-e[1],c=(r*o-l*n)/a;if(!(c>1e-9))return 1/0;const h=(r*i-l*t)/a;return h<-1e-9||h>1+1e-9?1/0:c};function v_(e,t,i,s=96){if(!(t>0&&Number.isFinite(e[0])&&Number.isFinite(e[1])))return[];const n=[];for(const s of i){if(!s||s.length<4)continue;if(![s[0],s[1],s[2],s[3]].every(Number.isFinite))continue;const i=g_(e,s);if(i<1e-7)return[];i>t||n.push(s)}const o=[],a=Math.max(12,Math.round(s));for(let e=0;ee-t);const l=[];let c=Number.NEGATIVE_INFINITY;for(const i of o){if(i-c<1e-9)continue;c=i;const s=Math.cos(i),o=Math.sin(i);let a=t;for(const t of n){const i=f_(e,s,o,t);i=3?l:[]}class b_ extends he{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1,this._spacesAuthoritative=!1}setConfig(e){this._config=e}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const e=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(e?.config?.spaces||[]).map(e=>({value:e.id,label:e.title||e.id})),this._spacesAuthoritative=!0}catch{this._spaces=[],this._spacesAuthoritative=!1}finally{this._spacesLoading=!1}}}get _lang(){return pa(this.hass,this._config?.language)}get _floorToken(){const e=this._config?.floor;return"number"==typeof e?`__houseplan_yaml_floor_index__:${String(e)}`:null}get _formData(){const e={...this._config},t=this._floorToken;return t?e.floor=t:Object.prototype.hasOwnProperty.call(e,"floor")||(e.floor=""),e}get _schema(){const e=this._spaces||[],t=this._lang,i=[{value:"",label:ua(t,"editor.floor_none")}],s=this._floorToken;s&&i.push({value:s,label:ua(t,"editor.floor_index",{index:String(this._config?.floor)})});const n="string"==typeof this._config?.floor?this._config.floor:"";n&&!e.some(e=>e.value===n)&&i.push({value:n,label:n}),i.push(...e);const o="string"==typeof this._config?.default_floor?this._config.default_floor:"",a=[...e];return o&&!e.some(e=>e.value===o)&&a.unshift({value:o,label:o}),[{name:"title",selector:{text:{}}},{name:"floor",selector:{select:{mode:"dropdown",options:i}}},e.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:a}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:da(ua(t,"editor.lang_auto"),this._config?.language)}}},{name:"icon_size",selector:{number:{min:1,max:6,step:.1,mode:"box"}}},{name:"show_temperature",selector:{boolean:{}}},{name:"live_states",selector:{boolean:{}}},{name:"show_signal",selector:{boolean:{}}},{name:"kiosk",selector:{boolean:{}}},{name:"cycle",selector:{number:{min:0,max:3600,step:5,mode:"box"}}}]}render(){if(!this.hass||!this._config)return G;this._loadSpaces();const e=this._lang,t={title:ua(e,"editor.title"),floor:ua(e,"editor.floor"),default_floor:ua(e,"editor.default_floor"),language:ua(e,"editor.language"),icon_size:ua(e,"editor.icon_size"),show_temperature:ua(e,"editor.show_temperature"),live_states:ua(e,"editor.live_states"),show_signal:ua(e,"editor.show_signal"),kiosk:ua(e,"editor.kiosk"),cycle:ua(e,"editor.cycle")},i=this._schema,s=function(e,t,i){if(!i||null===t)return null;const s="string"==typeof e?.default_floor?e.default_floor:"";return!s||t.some(e=>e.value===s)?null:s}(this._config,this._spaces,this._spacesAuthoritative),n=e=>q``:G} ${n(i.slice(3))} - `}_valueChanged(e){const t={...this._config,...e.detail.value};""===t.floor?delete t.floor:t.floor===this._floorToken&&(t.floor=this._config?.floor);const i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:t},this.dispatchEvent(i)}}function f_(e){const t=e.length/2,i=e.face.cm>0?e.face.cm/e.cellCm*e.gridPitch/2:Dh(4,e.cellCm),s="gate"===e.type?Math.sin(10*Math.PI/180)*t:0;return{half:t,jambHalf:i,gateDepth:s,outlineHalf:Math.max(Dh(16,e.cellCm),i+Dh(8,e.cellCm),s+Dh(8,e.cellCm)),hitHalf:Math.max(Dh(20,e.cellCm),i+Dh(10,e.cellCm),s+Dh(12,e.cellCm))}}function v_(e){if("passage"===e.type)return W``;const t=Math.max(0,Math.min(1,e.amount)),{half:i,jambHalf:s}=f_(e),n=Ch(e.cellCm),o=1.75*n,a=e.flipH?-1:1,r=e.flipV?-1:1,l="gate"===e.type?1:r;e.type,e.flipV,e.angle,e.face;let c;if("window"===e.type){const o=Math.PI/2*i,a=e.face.cm>0?W`0?e.face.cm/e.cellCm*e.gridPitch/2:Dh(4,e.cellCm),s="gate"===e.type?Math.sin(10*Math.PI/180)*t:0;return{half:t,jambHalf:i,gateDepth:s,outlineHalf:Math.max(Dh(16,e.cellCm),i+Dh(8,e.cellCm),s+Dh(8,e.cellCm)),hitHalf:Math.max(Dh(20,e.cellCm),i+Dh(10,e.cellCm),s+Dh(12,e.cellCm))}}function w_(e){if("passage"===e.type)return W``;const t=Math.max(0,Math.min(1,e.amount)),{half:i,jambHalf:s}=y_(e),n=Ch(e.cellCm),o=1.75*n,a=e.flipH?-1:1,r=e.flipV?-1:1,l="gate"===e.type?1:r;e.type,e.flipV,e.angle,e.face;let c;if("window"===e.type){const o=Math.PI/2*i,a=e.face.cm>0?W``:W``;c=W` ${c} - `}g_.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",g_);const b_={availability:"available",status:"neutral",activity:"none"};function y_(e){return e<=40?"low":e<180?"mid":"high"}function w_(e){return ai(e)}function k_(e){return"static_icon"===e.display?"neutral":"alarm"===e.visual.status?"alarm":"unavailable"===e.visual.availability?"unavailable":e.lockState?e.lockState:"working"===e.visual.status?"working":"open"===e.visual.status?"open":"neutral"}const x_={sourceKind:"none",decisionIds:["source.skipped_static_fast_path"],visualSources:[],criticalSources:[],samples:[]};function $_(e,t){const i=e?.entities?.[t],s=e?.states?.[t];return String(i?.name||i?.original_name||s?.attributes?.friendly_name||t)}function S_(e,t){const i=e?.states?.[t];if(!i)return"";if("function"==typeof e?.formatEntityState)try{const t=e.formatEntityState(i);if("string"==typeof t&&t)return t}catch{}return String(i.state??"")}function M_(e,t){if(t.virtual||"virtual"===t.bindingKind||"virtual"===t.marker?.binding)return"available";return(t.entities||[]).some(t=>{const i=String(e?.states?.[t]?.state??"").trim().toLowerCase();return""!==i&&"unknown"!==i&&"unavailable"!==i})?"available":"unavailable"}function C_(e,t,i,s){return{eid:t,role:i,name:$_(e,t),state:String(e?.states?.[t]?.state??""),stateText:S_(e,t),integrationDomain:e?.entities?.[t]?.platform||null,sample:s||cn(e,t)}}function D_(e,t,i=[t],s,n=e){const o=t.hidden&&t.userHidden?{...t,hidden:!1}:t;let a="none",r=[];const l=[],c=[...o.entities,...o.allEntities||[]].some(e=>e.startsWith("cover."))?{...o,tapAction:"cover"}:o,h=(d=Kp({hass:e,devices:i,device:c,lightSources:s,registryHass:n}),"cover"!==d?.semantics?null:d.targets[0]?.entityId||d.skippedTargets[0]?.entityId||null);var d;const p=ho(e,[o]),u=Zn(o.marker?.binding,o.marker?.controls??o.controls,o.entities),_=new Set(u.filter(e=>e.startsWith("marker:"))),m=!0===o.marker?.is_light||_.size>0?s||ho(e,i):p,g=m.filter(e=>e.device.id===o.id&&"controls"!==e.via),f=p.filter(e=>"controls"!==e.via),v=g.length?g:f,b=bn(o.marker),y=b?[...v]:[...p.filter(e=>"controls"===e.via),...v];if(_.size)for(const e of m){if(!_.has(e.key))continue;e.stateEids.length>0&&y.some(t=>t.stateEids.some(t=>e.stateEids.includes(t)))||y.some(t=>t.key===e.key)||y.push({...e,via:"controls",castsGlow:!1})}const w=Yn(n,o.entities),k=!!h&&("cover"===o.tapAction||o.primary?.startsWith("cover.")||w.some(e=>e.startsWith("cover.")));if(h&&!k&&l.push("source.cover_capability_bypassed"),k)a="cover",l.push("source.cover"),r=[C_(e,h,"cover")];else if(y.length)a=!b&&y.some(e=>"controls"===e.via)?"controls":"light",b?l.push("source.manual_virtual_light"):l.push("controls"===a?"source.controls":"source.owned_light"),"controls"!==a||!o.virtual&&"virtual"!==o.bindingKind||l.push("source.virtual_controller"),r=y.map(i=>{const s="controls"===i.via?"control":"forced"===i.via?"forced_light":"light";if(!i.passive)return C_(e,i.eid,s);const n=i.on?"on":"off";return{eid:i.key,role:s,name:t.name,state:n,stateText:n,integrationDomain:null,sample:{eid:i.key,state:n,availability:"available",status:i.on?"working":"neutral",activity:"none",edge:"none"}}});else{const t=Yn(n,o.entities),i=t.length?t:o.entities.filter(t=>!!e?.states?.[t]);if(i.length){a="device_role",l.push("source.device_role");const t=hn(n===e?e:{...n,states:e?.states||{}},i,o.entities),s=i.some(e=>an(n,e));r=t.map(t=>{const i=s?rn(n,t.eid)?"power_gate":an(n,t.eid)?"lifecycle":"device_role":"device_role";return C_(e,t.eid,i,t)})}else o.primary?(a="primary",l.push("source.primary_fallback"),r=[C_(e,o.primary,"primary")]):l.push("source.none")}u.length>0&&"controls"!==a&&"light"!==a&&"cover"!==a&&l.push("source.filtered_saved_controls");const x=[];for(const t of o.entities||[]){const i=cn(e,t);"alarm"!==i.status||r.some(e=>e.eid===t)||x.push(C_(e,t,"critical",i))}return x.length&&l.push("source.critical_sibling"),{sourceKind:a,decisionIds:l,visualSources:r,criticalSources:x,samples:[...r,...x].map(e=>e.sample)}}function P_(e,t,i,s){if(t.virtual)return{source:null,text:null,fallback:"value_virtual"};if(s){const i=function(e,t){if(!0!==t.marker?.use_climate_temp)return null;for(const i of t.entities){if(!i.startsWith("climate."))continue;const t=Number(e?.states?.[i]?.attributes?.current_temperature);if(Number.isFinite(t))return{eid:i,text:Math.round(10*t)/10+"°"}}return null}(e,t);if(i)return{source:{kind:"temperature",eid:i.eid,attribute:"current_temperature",text:i.text},text:i.text,fallback:null};const s=function(e,t){if("mdi:thermometer"!==t.icon&&"mdi:air-filter"!==t.icon)return null;for(const i of t.entities){if(!Gn(e,i))continue;const t=Number(e?.states?.[i]?.state);if(Number.isFinite(t))return{eid:i,text:Math.round(10*t)/10+"°"}}return null}(e,t);if(s)return{source:{kind:"temperature",eid:s.eid,text:s.text},text:s.text,fallback:null};const n=function(e,t){if(!t.primary||!fo(e,t.primary))return null;const i=Number(e?.states?.[t.primary]?.state);return Number.isFinite(i)?{eid:t.primary,text:`${Math.round(i)}%`}:null}(e,t);if(n)return{source:{kind:"humidity",eid:n.eid,text:n.text},text:n.text,fallback:null}}const n=i.visualSources.filter(e=>!e.eid.startsWith("marker:"));let o=n.map(e=>e.eid);const a=n.find(e=>"power_gate"===e.role);if(a&&(o=[a.eid]),!o.length&&i.visualSources.some(e=>e.eid.startsWith("marker:"))&&(o=Yn(e,t.entities),!o.length&&t.primary&&e?.states?.[t.primary]&&(o=[t.primary])),o=[...new Set(o)],1!==o.length)return{source:null,text:null,fallback:o.length?"value_ambiguous_sources":"value_no_state"};const r=o[0],l=function(e,t){const i=e?.states?.[t];if(!i||null==i.state||""===String(i.state).trim())return{text:"",fallback:"value_no_state"};const s=i.state;if(!["string","number","boolean"].includes(typeof s))return{text:"",fallback:"value_non_scalar"};const n=String(s).trim().toLowerCase();if("unknown"===n||"unavailable"===n)return{text:"",fallback:"value_no_state"};const o=Xi(e,t);return o?{text:Zi(o,String(i.attributes?.unit_of_measurement||"")),fallback:null}:{text:"",fallback:"value_no_state"}}(e,r);return l.fallback?{source:null,text:null,fallback:l.fallback}:{source:{kind:"entity",eid:r,text:l.text},text:l.text,fallback:null}}function T_(e,t,i){const s=e.marker?.binding||(e.bindingKind&&e.bindingRef?`${e.bindingKind}:${e.bindingRef}`:e.virtual?"virtual":""),n=[...t.visualSources.map(e=>`${e.role}:${e.eid}`),...t.criticalSources.map(e=>`critical:${e.eid}`)].sort(),o=i?`${i.kind}:${i.eid}:${i.attribute||""}`:"none";return[s,t.sourceKind,...n,`value:${o}`].join("\n")}function I_(e,t,i){return T_(t,i||D_(e,t),null).replace(/\nvalue:none$/,"")}function R_(e){if(e.effectiveHidden)return[];if("static_icon"===e.display)return["static-icon"];const t=[],{visual:i}=e;return"alarm"===e.pulse.kind?t.push("alarm"):"unavailable"===i.availability?t.push("unavail"):"working"===i.status?t.push("on"):"open"===i.status&&t.push("open"),e.lockState&&t.push(`lock-${e.lockState}`),"none"!==e.pulse.reason&&"alarm"!==e.pulse.reason&&t.push("activity-"+e.pulse.reason),e.pulse.generation%2==0&&(t.push("pulse-gen2"),"short"===e.pulse.kind&&t.push("activity-gen2")),t}function A_(e,t,i){const s=Bi(t.marker?.display),n="static_icon"===s,o=n&&!1===i.sourceDetails?x_:D_(e,t,i.lightDevices||[t],i.lightSources,i.registryHass||e),a=t.bindingStatus,r="ha_disabled"===a?.kind,l="orphaned"===a?.kind,c=r?"ha_disabled":l?"orphaned":"active",h=!0===t.userHidden||!0===t.marker?.hidden,d=dn(o.samples),p=o.visualSources.find(e=>e.eid.startsWith("lock.")),u=p?["unlocked","open"].includes(p.state.toLowerCase())?"unlocked":"locked"===p.state.toLowerCase()?"locked":null:null,_=!bn(t.marker)&&Zn(t.marker?.binding,t.marker?.controls??t.controls,t.entities).length>0,m="controls"===o.sourceKind||_&&"light"!==o.sourceKind&&"cover"!==o.sourceKind,g=n&&!1===i.sourceDetails?{source:null,text:null,fallback:null}:P_(e,t,o,i.showTemperature),f=T_(t,o,g.source),v=I_(e,t,o),b=i.activityRuntime,y=i.now??Date.now(),w=b?.expiresAt||(b?.flashTs?b.flashTs+3300:0),k=b?.sources===v&&b.flashTs&&b.flashKind&&w>y?b.flashKind:null,x=function(e){const t=[],i="ha_disabled"===e.bindingLifecycle||"orphaned"===e.bindingLifecycle;let s=!1;"ha_disabled"===e.bindingLifecycle?(s=!0,t.push("lifecycle.ha_disabled_hidden")):e.userHidden&&!e.designPreview?(s=!0,t.push("lifecycle.user_hidden")):e.userHidden?t.push("lifecycle.user_hidden_preview"):"orphaned"===e.bindingLifecycle?t.push("lifecycle.orphaned_diagnostic"):t.push("lifecycle.active");const n="static_icon"===e.display;let o=e.controllerFace?{...e.sourceVisual,availability:e.controllerAvailability}:e.sourceVisual;t.push(e.controllerFace?"available"===e.controllerAvailability?"availability.controller_available":"availability.controller_unavailable":"availability.source"),s?(o=b_,t.push("face.hidden")):n?(o=b_,t.push("face.static")):"alarm"===e.sourceVisual.status||e.liveStates?t.push("face.dynamic"):(o=b_,t.push("face.live_states_disabled")),!n&&!s&&e.liveStates&&"alarm"!==e.sourceVisual.status&&e.shortActivity&&(o={...o,activity:e.shortActivity},t.push(`activity.short_${e.shortActivity}`)),"alarm"===o.status?t.push("status.alarm"):"unavailable"===o.availability?t.push("status.unavailable"):"working"===o.status?t.push("status.working"):"open"===o.status?t.push("status.open"):t.push("status.neutral");const a="value"===e.display&&!s&&e.valueAvailable?"value":"icon";t.push("value"===a?"content.value":"value"===e.display?"content.value_fallback_icon":"content.icon"),"value"===e.display&&!e.valueAvailable&&e.valueFallback&&t.push(`content.${e.valueFallback}`);const r=e.liveStates&&!n&&!s,l=!n&&!s,c=!s&&!i&&!n&&"available"===o.availability&&("alarm"===o.status||e.liveStates&&"icon_ripple"===e.display),h=l&&e.vacuumLiveRequested;return t.push(r?"diagnostics.dynamic_icon":"diagnostics.base_icon"),t.push(l?"diagnostics.metrics_enabled":"diagnostics.metrics_suppressed"),t.push(c?"activity.pulse_eligible":"activity.pulse_suppressed"),t.push(h?"diagnostics.vacuum_live":"diagnostics.vacuum_static"),{effectiveHidden:s,bindingUnavailable:i,visual:o,face:a,dynamicIcon:r,metrics:l,liveColor:r,pulseEligible:c,vacuumLive:h,decisionIds:t}}({bindingLifecycle:c,userHidden:h,designPreview:!0===i.designPreview,display:s,liveStates:i.liveStates,sourceVisual:d,controllerFace:m,controllerAvailability:m?M_(e,t):"available",shortActivity:k,valueAvailable:null!=g.text,valueFallback:g.fallback,vacuumLiveRequested:!0===t.marker?.vacuum?.live}),{effectiveHidden:$,visual:S}=x,M="icon_ripple"===s&&!$&&i.liveStates&&"alarm"!==S.status?S.activity:"none",C=x.metrics&&i.showTemperature?!0===t.marker?.use_climate_temp?go(e,t.entities):"mdi:thermometer"===t.icon||"mdi:air-filter"===t.icon?mo(e,t.entities):null:null,D=x.metrics&&i.showTemperature&&t.primary&&fo(e,t.primary)?vo(e,t.entities):null,P=x.metrics&&i.showSignal&&!t.virtual?_o(e,t.entities):null,T=i.lightSources||("derived_marker_state"===t.marker?.value_badge?.source?.kind?ho(e,i.lightDevices||[t]):[]),I=function(e,t,i){const s=null!=t.marker?.value_badge;if(i.effectiveHidden||"static_icon"===i.display)return null;const n=t.marker?.value_badge;if(s)return n?.enabled?n.source?{configured:!0,enabled:!0,position:n.position||"right",...Ho(e,t,n.source,i.markerStates)}:{configured:!0,enabled:!0,source:null,sourceLabel:"",text:"—",fullText:e?.localize?.("state.default.unavailable")||"Unavailable",position:n.position||"right",availability:"missing",isLqi:!1,tone:"default"}:null;if(!i.showTemperature||"value"===i.display)return null;const o=Lo(e,t);if(!o)return null;let a=null,r="default";if("entity_attribute"===o.kind&&"current_temperature"===o.attribute){const i=go(e,t.entities);null!=i&&(a=`${i}°`),r="temperature"}else if("mdi:thermometer"===t.icon||"mdi:air-filter"===t.icon){const i=mo(e,t.entities);null!=i&&(a=`${i}°`),r="temperature"}else{const i=vo(e,t.entities);null!=i&&(a=`${i}%`),r="humidity"}return null==a?null:{configured:!1,enabled:!0,source:o,position:"right",sourceLabel:"entity_attribute"===o.kind?`${No(o.attribute)} · ${Fo(e,o.entity_id)}`:"entity_state"===o.kind?Fo(e,o.entity_id):"",text:a,fullText:a,availability:"available",isLqi:!1,tone:r}}(e,t,{showTemperature:i.showTemperature,showSignal:i.showSignal,display:s,effectiveHidden:$,markerStates:T.filter(e=>e.key.startsWith("marker:")).map(e=>({ref:e.key,on:e.on,name:e.device.name}))}),R=o.visualSources.find(e=>!e.eid.startsWith("marker:")),A="cover"===o.sourceKind?R?.eid:t.primary||R?.eid,z=A?e?.states?.[A]:null,F=x.dynamicIcon?vs(t.icon,A?.split(".")[0],z?.attributes?.device_class,z?.state,!!t.marker?.icon):t.icon,N=x.liveColor&&o.visualSources.filter(e=>!e.eid.startsWith("marker:")).map(t=>function(e){return e&&"on"===e.state?ze(e.attributes?.rgb_color):null}(e?.states?.[t.eid])).find(e=>!!e)||null,E=Number(t.marker?.size)>0?Number(t.marker.size):1,O=Number(t.marker?.angle)||0,H=Number(t.marker?.ripple_size)>0?Number(t.marker.ripple_size):1.5,L=Ie(t.marker?.ripple_color,null),B=n?null:L||N||null,j=function(e){const{display:t,visual:i,semanticActivity:s,liveStates:n,effectiveHidden:o,bindingUnavailable:a=!1,reducedMotion:r=!1}=e,l=Math.max(1,Math.trunc(e.shortGeneration||1)),c=Number.isFinite(e.diameterScale)?Math.max(1,Number(e.diameterScale)):1.5,h=e.color||function(e,t){return"presence"===t?"#1DC21D":"running"===t||"working"===e.status||"open"===e.status?"#F0A00C":"#0C82F0"}(i,s);if(o||a||"unavailable"===i.availability||"static_icon"===t)return Yo(l,h,c);if("alarm"===i.status)return{kind:"alarm",reason:"alarm",generation:l,expiresAt:null,color:"#F0410C",diameterScale:1.5,animated:!r,reducedMotionIndicator:"none"};if(!n||"icon_ripple"!==t)return Yo(l,h,c);const d=e.now??Date.now(),p=e.shortExpiresAt||null;return e.shortReason&&null!=p&&p>d?{kind:"short",reason:e.shortReason,generation:l,expiresAt:p,color:h,diameterScale:r?1:c,animated:!r,reducedMotionIndicator:r?"dot":"none"}:"presence"===s||"transition"===s||"running"===s?{kind:"continuous",reason:s,generation:l,expiresAt:null,color:h,diameterScale:r?1:c,animated:!r,reducedMotionIndicator:r?"dot":"none"}:Yo(l,h,c)}({display:s,visual:S,semanticActivity:d.activity,shortReason:b?.sources===v?b?.flashKind:null,shortGeneration:b?.gen,shortExpiresAt:b?.sources===v?w:null,now:y,liveStates:i.liveStates,effectiveHidden:$,bindingUnavailable:x.bindingUnavailable,reducedMotion:i.reducedMotion,color:B,diameterScale:H}),q="value"===x.face?g.text:null,W="ha_disabled"===a?.kind?a.reason:null,U="ha_disabled"===(G={lifecycle:c,display:s,liveStates:i.liveStates,sourceKind:o.sourceKind,primaryDomain:(t.primary||"").split(".")[0],visual:S,activity:M}).lifecycle?"ha_disabled":"orphaned"===G.lifecycle?"orphaned":"static_icon"===G.display?"static_icon":"alarm"===G.visual.status?"alarm":G.liveStates?"unavailable"===G.visual.availability?"unavailable":"cover"===G.sourceKind?"cover_icon_state":"presence"===G.activity?"presence":"event"===G.activity?"event":"transition"===G.activity?"transition":"working"===G.visual.status?"icon_ripple"===G.display&&"none"!==G.activity?"working_activity":"working":"open"===G.visual.status?"open":"media_player"===G.primaryDomain?"media_neutral":"neutral":"live_states_disabled";var G;const V=[];i.designPreview&&h&&V.push("hidden_design_preview"),n||!0!==t.marker?.vacuum?.live||V.push("vacuum_live_plan_only");const K=o.visualSources.some(t=>"power_gate"===t.role||rn(e,t.eid)),Y=t.entities.filter(t=>t.startsWith("switch.")&&!e?.entities?.[t]?.entity_category).length;K&&Y>1&&V.push("composite_power_source"),"icon_ripple"!==s&&"static_icon"!==s&&"alarm"!==d.status&&"none"!==d.activity&&V.push("activity_display_disabled");const J={binding:t.marker?.binding||(t.bindingKind&&t.bindingRef?`${t.bindingKind}:${t.bindingRef}`:t.virtual?"virtual":""),sourceKind:o.sourceKind,visualSources:o.visualSources,criticalSources:o.criticalSources,valueSource:g.source,sourceSignature:f,decisionIds:[...o.decisionIds,...x.decisionIds,...I?["diagnostics.value_badge"]:[],...null==P?[]:[`diagnostics.lqi_${y_(P)}`],`pulse.${j.kind}_${j.reason}`],visual:S,lockState:u,display:s,icon:F,valueText:q,valueFullText:q,fallbackReason:"value"!==s||q?null:g.fallback,activity:M,activityGeneration:b?.gen||1,pulse:j,classes:[],tempText:null==C?null:String(C),humText:null==D?null:String(D),valueBadge:I,lqiText:null==P||I?.isLqi?null:String(P),lqiColor:null==P?null:w_(P),lqiBand:null==P?null:y_(P),lightColor:N,scale:E,angle:O,rippleScale:H,rippleColor:B,userHidden:h,effectiveHidden:$,haDisabled:r,disabledReason:W,orphaned:l,vacuumLive:x.vacuumLive,explanation:{reason:U,notices:V}};return{...J,classes:R_(J)}}function z_(e,t=new WeakMap){if(null===e||"object"!=typeof e)return e;const i=e,s=t.get(i);if(s)return s;if(Array.isArray(e)){const s=[];t.set(i,s);for(const i of e)s.push(z_(i,t));return Object.freeze(s)}const n={};t.set(i,n);for(const[i,s]of Object.entries(e))"function"!=typeof s&&(n[i]=z_(s,t));return Object.freeze(n)}function F_(e){const t=new Map(e);let i;return i=Object.freeze({get size(){return t.size},get:e=>t.get(e),has:e=>t.has(e),entries:()=>t.entries(),keys:()=>t.keys(),values:()=>t.values(),forEach:(e,s)=>t.forEach((t,n)=>e.call(s,t,n,i)),[Symbol.iterator]:()=>t[Symbol.iterator]()}),i}function N_(e){const t=new Set(e.entityIds||[]),i=new Set(e.deviceIds||[]),s=new Set(e.areaIds||[]);for(const s of e.devices){for(const e of s.entities||[])t.add(e);s.primary&&t.add(s.primary);for(const e of s.controls||[])t.add(e);s.marker?.vacuum?.source&&t.add(s.marker.vacuum.source),"device"===s.bindingKind&&s.bindingRef&&i.add(s.bindingRef),"entity"===s.bindingKind&&s.bindingRef&&t.add(s.bindingRef)}const n={},o={};for(const[o,a]of Object.entries(e.hass?.entities||{})){const r=a?.device_id?e.hass?.devices?.[a.device_id]:null;(t.has(o)||a?.device_id&&i.has(a.device_id)||s.has(a?.area_id)||s.has(r?.area_id))&&(t.add(o),n[o]=a,a?.device_id&&i.add(a.device_id))}for(const t of i){const i=e.hass?.devices?.[t];i&&(o[t]=i)}const a={};for(const i of t){const t=e.hass?.states?.[i];t&&(a[i]=t)}const r=Object.freeze({states:z_(a),entities:z_(n),devices:z_(o),config:z_(e.hass?.config),locale:z_(e.hass?.locale),themes:z_(e.hass?.themes)});return Object.freeze({sourceSequence:e.sourceSequence,capturedAt:e.capturedAt??Date.now(),hass:r,devices:z_([...e.devices]),positions:F_([...e.positions||[]].map(([e,t])=>[e,Object.freeze({x:t.x,y:t.y})])),presentations:F_([...e.presentations].map(([e,t])=>[e,z_(t)])),facts:F_([...e.facts||[]].map(([e,t])=>[e,z_(t)]))})}const E_=(e,t)=>`${e}:${t?1:0}`;function O_(e){return!Number.isFinite(e)||e<=0?2.25:e/2.5*2.25}function H_({openings:e,geometries:t,fillsByRoomId:i,idPrefix:s="data",groupClass:n="opening-tunnels",dataLayer:o="data"}){const a=e.map((e,n)=>{const o=t[n];if(!o)return G;const a=o.faces.find(e=>-1===e.side),r=o.faces.find(e=>1===e.side);if(!a||!r)return G;const l=i.get(a.roomId)||null,c=i.get(r.roomId)||null;if(!l&&!c)return G;const h=`${a.d} ${r.d}`,d=!!l&&!!c&&l.color===c.color&&l.opacity===c.opacity,p=`translate(${e.rx} ${e.ry}) rotate(${e.angle})`;if(d)return W``}b_.properties={hass:{attribute:!1},_config:{state:!0},_spaces:{state:!0}},customElements.get("houseplan-card-editor")||customElements.define("houseplan-card-editor",b_);const k_={availability:"available",status:"neutral",activity:"none"};function x_(e){return e<=40?"low":e<180?"mid":"high"}function $_(e){return ai(e)}function S_(e){return"static_icon"===e.display?"neutral":"alarm"===e.visual.status?"alarm":"unavailable"===e.visual.availability?"unavailable":e.lockState?e.lockState:"working"===e.visual.status?"working":"open"===e.visual.status?"open":"neutral"}const M_={sourceKind:"none",decisionIds:["source.skipped_static_fast_path"],visualSources:[],criticalSources:[],samples:[]};function C_(e,t){const i=e?.entities?.[t],s=e?.states?.[t];return String(i?.name||i?.original_name||s?.attributes?.friendly_name||t)}function D_(e,t){const i=e?.states?.[t];if(!i)return"";if("function"==typeof e?.formatEntityState)try{const t=e.formatEntityState(i);if("string"==typeof t&&t)return t}catch{}return String(i.state??"")}function P_(e,t){if(t.virtual||"virtual"===t.bindingKind||"virtual"===t.marker?.binding)return"available";return(t.entities||[]).some(t=>{const i=String(e?.states?.[t]?.state??"").trim().toLowerCase();return""!==i&&"unknown"!==i&&"unavailable"!==i})?"available":"unavailable"}function T_(e,t,i,s){return{eid:t,role:i,name:C_(e,t),state:String(e?.states?.[t]?.state??""),stateText:D_(e,t),integrationDomain:e?.entities?.[t]?.platform||null,sample:s||cn(e,t)}}function I_(e,t,i=[t],s,n=e){const o=t.hidden&&t.userHidden?{...t,hidden:!1}:t;let a="none",r=[];const l=[],c=[...o.entities,...o.allEntities||[]].some(e=>e.startsWith("cover."))?{...o,tapAction:"cover"}:o,h=(d=Xp({hass:e,devices:i,device:c,lightSources:s,registryHass:n}),"cover"!==d?.semantics?null:d.targets[0]?.entityId||d.skippedTargets[0]?.entityId||null);var d;const p=ho(e,[o]),u=Zn(o.marker?.binding,o.marker?.controls??o.controls,o.entities),_=new Set(u.filter(e=>e.startsWith("marker:"))),m=!0===o.marker?.is_light||_.size>0?s||ho(e,i):p,g=m.filter(e=>e.device.id===o.id&&"controls"!==e.via),f=p.filter(e=>"controls"!==e.via),v=g.length?g:f,b=bn(o.marker),y=b?[...v]:[...p.filter(e=>"controls"===e.via),...v];if(_.size)for(const e of m){if(!_.has(e.key))continue;e.stateEids.length>0&&y.some(t=>t.stateEids.some(t=>e.stateEids.includes(t)))||y.some(t=>t.key===e.key)||y.push({...e,via:"controls",castsGlow:!1})}const w=Yn(n,o.entities),k=!!h&&("cover"===o.tapAction||o.primary?.startsWith("cover.")||w.some(e=>e.startsWith("cover.")));if(h&&!k&&l.push("source.cover_capability_bypassed"),k)a="cover",l.push("source.cover"),r=[T_(e,h,"cover")];else if(y.length)a=!b&&y.some(e=>"controls"===e.via)?"controls":"light",b?l.push("source.manual_virtual_light"):l.push("controls"===a?"source.controls":"source.owned_light"),"controls"!==a||!o.virtual&&"virtual"!==o.bindingKind||l.push("source.virtual_controller"),r=y.map(i=>{const s="controls"===i.via?"control":"forced"===i.via?"forced_light":"light";if(!i.passive)return T_(e,i.eid,s);const n=i.on?"on":"off";return{eid:i.key,role:s,name:t.name,state:n,stateText:n,integrationDomain:null,sample:{eid:i.key,state:n,availability:"available",status:i.on?"working":"neutral",activity:"none",edge:"none"}}});else{const t=Yn(n,o.entities),i=t.length?t:o.entities.filter(t=>!!e?.states?.[t]);if(i.length){a="device_role",l.push("source.device_role");const t=hn(n===e?e:{...n,states:e?.states||{}},i,o.entities),s=i.some(e=>an(n,e));r=t.map(t=>{const i=s?rn(n,t.eid)?"power_gate":an(n,t.eid)?"lifecycle":"device_role":"device_role";return T_(e,t.eid,i,t)})}else o.primary?(a="primary",l.push("source.primary_fallback"),r=[T_(e,o.primary,"primary")]):l.push("source.none")}u.length>0&&"controls"!==a&&"light"!==a&&"cover"!==a&&l.push("source.filtered_saved_controls");const x=[];for(const t of o.entities||[]){const i=cn(e,t);"alarm"!==i.status||r.some(e=>e.eid===t)||x.push(T_(e,t,"critical",i))}return x.length&&l.push("source.critical_sibling"),{sourceKind:a,decisionIds:l,visualSources:r,criticalSources:x,samples:[...r,...x].map(e=>e.sample)}}function R_(e,t,i,s){if(t.virtual)return{source:null,text:null,fallback:"value_virtual"};if(s){const i=function(e,t){if(!0!==t.marker?.use_climate_temp)return null;for(const i of t.entities){if(!i.startsWith("climate."))continue;const t=Number(e?.states?.[i]?.attributes?.current_temperature);if(Number.isFinite(t))return{eid:i,text:Math.round(10*t)/10+"°"}}return null}(e,t);if(i)return{source:{kind:"temperature",eid:i.eid,attribute:"current_temperature",text:i.text},text:i.text,fallback:null};const s=function(e,t){if("mdi:thermometer"!==t.icon&&"mdi:air-filter"!==t.icon)return null;for(const i of t.entities){if(!Gn(e,i))continue;const t=Number(e?.states?.[i]?.state);if(Number.isFinite(t))return{eid:i,text:Math.round(10*t)/10+"°"}}return null}(e,t);if(s)return{source:{kind:"temperature",eid:s.eid,text:s.text},text:s.text,fallback:null};const n=function(e,t){if(!t.primary||!fo(e,t.primary))return null;const i=Number(e?.states?.[t.primary]?.state);return Number.isFinite(i)?{eid:t.primary,text:`${Math.round(i)}%`}:null}(e,t);if(n)return{source:{kind:"humidity",eid:n.eid,text:n.text},text:n.text,fallback:null}}const n=i.visualSources.filter(e=>!e.eid.startsWith("marker:"));let o=n.map(e=>e.eid);const a=n.find(e=>"power_gate"===e.role);if(a&&(o=[a.eid]),!o.length&&i.visualSources.some(e=>e.eid.startsWith("marker:"))&&(o=Yn(e,t.entities),!o.length&&t.primary&&e?.states?.[t.primary]&&(o=[t.primary])),o=[...new Set(o)],1!==o.length)return{source:null,text:null,fallback:o.length?"value_ambiguous_sources":"value_no_state"};const r=o[0],l=function(e,t){const i=e?.states?.[t];if(!i||null==i.state||""===String(i.state).trim())return{text:"",fallback:"value_no_state"};const s=i.state;if(!["string","number","boolean"].includes(typeof s))return{text:"",fallback:"value_non_scalar"};const n=String(s).trim().toLowerCase();if("unknown"===n||"unavailable"===n)return{text:"",fallback:"value_no_state"};const o=Xi(e,t);return o?{text:Zi(o,String(i.attributes?.unit_of_measurement||"")),fallback:null}:{text:"",fallback:"value_no_state"}}(e,r);return l.fallback?{source:null,text:null,fallback:l.fallback}:{source:{kind:"entity",eid:r,text:l.text},text:l.text,fallback:null}}function A_(e,t,i){const s=e.marker?.binding||(e.bindingKind&&e.bindingRef?`${e.bindingKind}:${e.bindingRef}`:e.virtual?"virtual":""),n=[...t.visualSources.map(e=>`${e.role}:${e.eid}`),...t.criticalSources.map(e=>`critical:${e.eid}`)].sort(),o=i?`${i.kind}:${i.eid}:${i.attribute||""}`:"none";return[s,t.sourceKind,...n,`value:${o}`].join("\n")}function z_(e,t,i){return A_(t,i||I_(e,t),null).replace(/\nvalue:none$/,"")}function F_(e){if(e.effectiveHidden)return[];if("static_icon"===e.display)return["static-icon"];const t=[],{visual:i}=e;return"alarm"===e.pulse.kind?t.push("alarm"):"unavailable"===i.availability?t.push("unavail"):"working"===i.status?t.push("on"):"open"===i.status&&t.push("open"),e.lockState&&t.push(`lock-${e.lockState}`),"none"!==e.pulse.reason&&"alarm"!==e.pulse.reason&&t.push("activity-"+e.pulse.reason),e.pulse.generation%2==0&&(t.push("pulse-gen2"),"short"===e.pulse.kind&&t.push("activity-gen2")),t}function N_(e,t,i){const s=Bi(t.marker?.display),n="static_icon"===s,o=n&&!1===i.sourceDetails?M_:I_(e,t,i.lightDevices||[t],i.lightSources,i.registryHass||e),a=t.bindingStatus,r="ha_disabled"===a?.kind,l="orphaned"===a?.kind,c=r?"ha_disabled":l?"orphaned":"active",h=!0===t.userHidden||!0===t.marker?.hidden,d=dn(o.samples),p=o.visualSources.find(e=>e.eid.startsWith("lock.")),u=p?["unlocked","open"].includes(p.state.toLowerCase())?"unlocked":"locked"===p.state.toLowerCase()?"locked":null:null,_=!bn(t.marker)&&Zn(t.marker?.binding,t.marker?.controls??t.controls,t.entities).length>0,m="controls"===o.sourceKind||_&&"light"!==o.sourceKind&&"cover"!==o.sourceKind,g=n&&!1===i.sourceDetails?{source:null,text:null,fallback:null}:R_(e,t,o,i.showTemperature),f=A_(t,o,g.source),v=z_(e,t,o),b=i.activityRuntime,y=i.now??Date.now(),w=b?.expiresAt||(b?.flashTs?b.flashTs+3300:0),k=b?.sources===v&&b.flashTs&&b.flashKind&&w>y?b.flashKind:null,x=function(e){const t=[],i="ha_disabled"===e.bindingLifecycle||"orphaned"===e.bindingLifecycle;let s=!1;"ha_disabled"===e.bindingLifecycle?(s=!0,t.push("lifecycle.ha_disabled_hidden")):e.userHidden&&!e.designPreview?(s=!0,t.push("lifecycle.user_hidden")):e.userHidden?t.push("lifecycle.user_hidden_preview"):"orphaned"===e.bindingLifecycle?t.push("lifecycle.orphaned_diagnostic"):t.push("lifecycle.active");const n="static_icon"===e.display;let o=e.controllerFace?{...e.sourceVisual,availability:e.controllerAvailability}:e.sourceVisual;t.push(e.controllerFace?"available"===e.controllerAvailability?"availability.controller_available":"availability.controller_unavailable":"availability.source"),s?(o=k_,t.push("face.hidden")):n?(o=k_,t.push("face.static")):"alarm"===e.sourceVisual.status||e.liveStates?t.push("face.dynamic"):(o=k_,t.push("face.live_states_disabled")),!n&&!s&&e.liveStates&&"alarm"!==e.sourceVisual.status&&e.shortActivity&&(o={...o,activity:e.shortActivity},t.push(`activity.short_${e.shortActivity}`)),"alarm"===o.status?t.push("status.alarm"):"unavailable"===o.availability?t.push("status.unavailable"):"working"===o.status?t.push("status.working"):"open"===o.status?t.push("status.open"):t.push("status.neutral");const a="value"===e.display&&!s&&e.valueAvailable?"value":"icon";t.push("value"===a?"content.value":"value"===e.display?"content.value_fallback_icon":"content.icon"),"value"===e.display&&!e.valueAvailable&&e.valueFallback&&t.push(`content.${e.valueFallback}`);const r=e.liveStates&&!n&&!s,l=!n&&!s,c=!s&&!i&&!n&&"available"===o.availability&&("alarm"===o.status||e.liveStates&&"icon_ripple"===e.display),h=l&&e.vacuumLiveRequested;return t.push(r?"diagnostics.dynamic_icon":"diagnostics.base_icon"),t.push(l?"diagnostics.metrics_enabled":"diagnostics.metrics_suppressed"),t.push(c?"activity.pulse_eligible":"activity.pulse_suppressed"),t.push(h?"diagnostics.vacuum_live":"diagnostics.vacuum_static"),{effectiveHidden:s,bindingUnavailable:i,visual:o,face:a,dynamicIcon:r,metrics:l,liveColor:r,pulseEligible:c,vacuumLive:h,decisionIds:t}}({bindingLifecycle:c,userHidden:h,designPreview:!0===i.designPreview,display:s,liveStates:i.liveStates,sourceVisual:d,controllerFace:m,controllerAvailability:m?P_(e,t):"available",shortActivity:k,valueAvailable:null!=g.text,valueFallback:g.fallback,vacuumLiveRequested:!0===t.marker?.vacuum?.live}),{effectiveHidden:$,visual:S}=x,M="icon_ripple"===s&&!$&&i.liveStates&&"alarm"!==S.status?S.activity:"none",C=x.metrics&&i.showTemperature?!0===t.marker?.use_climate_temp?go(e,t.entities):"mdi:thermometer"===t.icon||"mdi:air-filter"===t.icon?mo(e,t.entities):null:null,D=x.metrics&&i.showTemperature&&t.primary&&fo(e,t.primary)?vo(e,t.entities):null,P=x.metrics&&i.showSignal&&!t.virtual?_o(e,t.entities):null,T=i.lightSources||("derived_marker_state"===t.marker?.value_badge?.source?.kind?ho(e,i.lightDevices||[t]):[]),I=function(e,t,i){const s=null!=t.marker?.value_badge;if(i.effectiveHidden||"static_icon"===i.display)return null;const n=t.marker?.value_badge;if(s)return n?.enabled?n.source?{configured:!0,enabled:!0,position:n.position||"right",...Ho(e,t,n.source,i.markerStates)}:{configured:!0,enabled:!0,source:null,sourceLabel:"",text:"—",fullText:e?.localize?.("state.default.unavailable")||"Unavailable",position:n.position||"right",availability:"missing",isLqi:!1,tone:"default"}:null;if(!i.showTemperature||"value"===i.display)return null;const o=Lo(e,t);if(!o)return null;let a=null,r="default";if("entity_attribute"===o.kind&&"current_temperature"===o.attribute){const i=go(e,t.entities);null!=i&&(a=`${i}°`),r="temperature"}else if("mdi:thermometer"===t.icon||"mdi:air-filter"===t.icon){const i=mo(e,t.entities);null!=i&&(a=`${i}°`),r="temperature"}else{const i=vo(e,t.entities);null!=i&&(a=`${i}%`),r="humidity"}return null==a?null:{configured:!1,enabled:!0,source:o,position:"right",sourceLabel:"entity_attribute"===o.kind?`${No(o.attribute)} · ${Fo(e,o.entity_id)}`:"entity_state"===o.kind?Fo(e,o.entity_id):"",text:a,fullText:a,availability:"available",isLqi:!1,tone:r}}(e,t,{showTemperature:i.showTemperature,showSignal:i.showSignal,display:s,effectiveHidden:$,markerStates:T.filter(e=>e.key.startsWith("marker:")).map(e=>({ref:e.key,on:e.on,name:e.device.name}))}),R=o.visualSources.find(e=>!e.eid.startsWith("marker:")),A="cover"===o.sourceKind?R?.eid:t.primary||R?.eid,z=A?e?.states?.[A]:null,F=x.dynamicIcon?vs(t.icon,A?.split(".")[0],z?.attributes?.device_class,z?.state,!!t.marker?.icon):t.icon,N=x.liveColor&&o.visualSources.filter(e=>!e.eid.startsWith("marker:")).map(t=>function(e){return e&&"on"===e.state?ze(e.attributes?.rgb_color):null}(e?.states?.[t.eid])).find(e=>!!e)||null,E=Number(t.marker?.size)>0?Number(t.marker.size):1,O=Number(t.marker?.angle)||0,H=Number(t.marker?.ripple_size)>0?Number(t.marker.ripple_size):1.5,L=Ie(t.marker?.ripple_color,null),B=n?null:L||N||null,j=function(e){const{display:t,visual:i,semanticActivity:s,liveStates:n,effectiveHidden:o,bindingUnavailable:a=!1,reducedMotion:r=!1}=e,l=Math.max(1,Math.trunc(e.shortGeneration||1)),c=Number.isFinite(e.diameterScale)?Math.max(1,Number(e.diameterScale)):1.5,h=e.color||function(e,t){return"presence"===t?"#1DC21D":"running"===t||"working"===e.status||"open"===e.status?"#F0A00C":"#0C82F0"}(i,s);if(o||a||"unavailable"===i.availability||"static_icon"===t)return Yo(l,h,c);if("alarm"===i.status)return{kind:"alarm",reason:"alarm",generation:l,expiresAt:null,color:"#F0410C",diameterScale:1.5,animated:!r,reducedMotionIndicator:"none"};if(!n||"icon_ripple"!==t)return Yo(l,h,c);const d=e.now??Date.now(),p=e.shortExpiresAt||null;return e.shortReason&&null!=p&&p>d?{kind:"short",reason:e.shortReason,generation:l,expiresAt:p,color:h,diameterScale:r?1:c,animated:!r,reducedMotionIndicator:r?"dot":"none"}:"presence"===s||"transition"===s||"running"===s?{kind:"continuous",reason:s,generation:l,expiresAt:null,color:h,diameterScale:r?1:c,animated:!r,reducedMotionIndicator:r?"dot":"none"}:Yo(l,h,c)}({display:s,visual:S,semanticActivity:d.activity,shortReason:b?.sources===v?b?.flashKind:null,shortGeneration:b?.gen,shortExpiresAt:b?.sources===v?w:null,now:y,liveStates:i.liveStates,effectiveHidden:$,bindingUnavailable:x.bindingUnavailable,reducedMotion:i.reducedMotion,color:B,diameterScale:H}),q="value"===x.face?g.text:null,W="ha_disabled"===a?.kind?a.reason:null,U="ha_disabled"===(G={lifecycle:c,display:s,liveStates:i.liveStates,sourceKind:o.sourceKind,primaryDomain:(t.primary||"").split(".")[0],visual:S,activity:M}).lifecycle?"ha_disabled":"orphaned"===G.lifecycle?"orphaned":"static_icon"===G.display?"static_icon":"alarm"===G.visual.status?"alarm":G.liveStates?"unavailable"===G.visual.availability?"unavailable":"cover"===G.sourceKind?"cover_icon_state":"presence"===G.activity?"presence":"event"===G.activity?"event":"transition"===G.activity?"transition":"working"===G.visual.status?"icon_ripple"===G.display&&"none"!==G.activity?"working_activity":"working":"open"===G.visual.status?"open":"media_player"===G.primaryDomain?"media_neutral":"neutral":"live_states_disabled";var G;const V=[];i.designPreview&&h&&V.push("hidden_design_preview"),n||!0!==t.marker?.vacuum?.live||V.push("vacuum_live_plan_only");const K=o.visualSources.some(t=>"power_gate"===t.role||rn(e,t.eid)),Y=t.entities.filter(t=>t.startsWith("switch.")&&!e?.entities?.[t]?.entity_category).length;K&&Y>1&&V.push("composite_power_source"),"icon_ripple"!==s&&"static_icon"!==s&&"alarm"!==d.status&&"none"!==d.activity&&V.push("activity_display_disabled");const J={binding:t.marker?.binding||(t.bindingKind&&t.bindingRef?`${t.bindingKind}:${t.bindingRef}`:t.virtual?"virtual":""),sourceKind:o.sourceKind,visualSources:o.visualSources,criticalSources:o.criticalSources,valueSource:g.source,sourceSignature:f,decisionIds:[...o.decisionIds,...x.decisionIds,...I?["diagnostics.value_badge"]:[],...null==P?[]:[`diagnostics.lqi_${x_(P)}`],`pulse.${j.kind}_${j.reason}`],visual:S,lockState:u,display:s,icon:F,valueText:q,valueFullText:q,fallbackReason:"value"!==s||q?null:g.fallback,activity:M,activityGeneration:b?.gen||1,pulse:j,classes:[],tempText:null==C?null:String(C),humText:null==D?null:String(D),valueBadge:I,lqiText:null==P||I?.isLqi?null:String(P),lqiColor:null==P?null:$_(P),lqiBand:null==P?null:x_(P),lightColor:N,scale:E,angle:O,rippleScale:H,rippleColor:B,userHidden:h,effectiveHidden:$,haDisabled:r,disabledReason:W,orphaned:l,vacuumLive:x.vacuumLive,explanation:{reason:U,notices:V}};return{...J,classes:F_(J)}}function E_(e,t=new WeakMap){if(null===e||"object"!=typeof e)return e;const i=e,s=t.get(i);if(s)return s;if(Array.isArray(e)){const s=[];t.set(i,s);for(const i of e)s.push(E_(i,t));return Object.freeze(s)}const n={};t.set(i,n);for(const[i,s]of Object.entries(e))"function"!=typeof s&&(n[i]=E_(s,t));return Object.freeze(n)}function O_(e){const t=new Map(e);let i;return i=Object.freeze({get size(){return t.size},get:e=>t.get(e),has:e=>t.has(e),entries:()=>t.entries(),keys:()=>t.keys(),values:()=>t.values(),forEach:(e,s)=>t.forEach((t,n)=>e.call(s,t,n,i)),[Symbol.iterator]:()=>t[Symbol.iterator]()}),i}function H_(e){const t=new Set(e.entityIds||[]),i=new Set(e.deviceIds||[]),s=new Set(e.areaIds||[]);for(const s of e.devices){for(const e of s.entities||[])t.add(e);s.primary&&t.add(s.primary);for(const e of s.controls||[])t.add(e);s.marker?.vacuum?.source&&t.add(s.marker.vacuum.source),"device"===s.bindingKind&&s.bindingRef&&i.add(s.bindingRef),"entity"===s.bindingKind&&s.bindingRef&&t.add(s.bindingRef)}const n={},o={};for(const[o,a]of Object.entries(e.hass?.entities||{})){const r=a?.device_id?e.hass?.devices?.[a.device_id]:null;(t.has(o)||a?.device_id&&i.has(a.device_id)||s.has(a?.area_id)||s.has(r?.area_id))&&(t.add(o),n[o]=a,a?.device_id&&i.add(a.device_id))}for(const t of i){const i=e.hass?.devices?.[t];i&&(o[t]=i)}const a={};for(const i of t){const t=e.hass?.states?.[i];t&&(a[i]=t)}const r=Object.freeze({states:E_(a),entities:E_(n),devices:E_(o),config:E_(e.hass?.config),locale:E_(e.hass?.locale),themes:E_(e.hass?.themes)});return Object.freeze({sourceSequence:e.sourceSequence,capturedAt:e.capturedAt??Date.now(),hass:r,devices:E_([...e.devices]),positions:O_([...e.positions||[]].map(([e,t])=>[e,Object.freeze({x:t.x,y:t.y})])),presentations:O_([...e.presentations].map(([e,t])=>[e,E_(t)])),facts:O_([...e.facts||[]].map(([e,t])=>[e,E_(t)]))})}const L_=(e,t)=>`${e}:${t?1:0}`;function B_(e){return!Number.isFinite(e)||e<=0?2.25:e/2.5*2.25}function j_({openings:e,geometries:t,fillsByRoomId:i,idPrefix:s="data",groupClass:n="opening-tunnels",dataLayer:o="data"}){const a=e.map((e,n)=>{const o=t[n];if(!o)return G;const a=o.faces.find(e=>-1===e.side),r=o.faces.find(e=>1===e.side);if(!a||!r)return G;const l=i.get(a.roomId)||null,c=i.get(r.roomId)||null;if(!l&&!c)return G;const h=`${a.d} ${r.d}`,d=!!l&&!!c&&l.color===c.color&&l.opacity===c.opacity,p=`translate(${e.rx} ${e.ry}) rotate(${e.angle})`;if(d)return W``;const u=o.maxY-o.minY;if(!(u>0))return G;const _=`${(100*Math.max(0,Math.min(1,-o.minY/u))).toFixed(6)}%`,m=l||{color:"#000000",opacity:0},g=c||{color:"#000000",opacity:0},f=`hp-opening-tunnel-${s.replace(/[^a-zA-Z0-9_-]/g,"-")}-${n}`;return W` - `});return W``}const L_=e=>Array.isArray(e)&&e.length>=2&&Number.isFinite(Number(e[0]))&&Number.isFinite(Number(e[1])),B_=(e,t,i)=>[Number(e[0])*i,Number(e[1])*i,Number(t[0])*i,Number(t[1])*i],j_=e=>{const t=`${Number(e[0]).toFixed(9)},${Number(e[1]).toFixed(9)}`,i=`${Number(e[2]).toFixed(9)},${Number(e[3]).toFixed(9)}`;return t<=i?`${t}|${i}`:`${i}|${t}`},q_=e=>{const t=new Map;for(const i of e.flat())!Array.isArray(i)||i.length<4||!i.every(Number.isFinite)||Math.hypot(i[2]-i[0],i[3]-i[1])<=1e-9||t.set(j_(i),[...i]);return[...t.values()]};function W_(e){return"solid"===e?.zero_wall_style?"solid":"dashed"}function U_(e,t=1,i){const s=[];for(const n of Array.isArray(e?.wall_segments)?e.wall_segments:[])0===Number(n?.cm)&&L_(n?.a)&&L_(n?.b)&&(i&&!i.has(String(n.id||""))||s.push(B_(n.a,n.b,t)));return q_([s])}function G_(e,t=1){const i=[];for(const s of Array.isArray(e?.partitions)?e.partitions:[])0===Number(s?.cm)&&L_(s?.a)&&L_(s?.b)&&i.push(B_(s.a,s.b,t));for(const s of Array.isArray(e?.room_drafts)?e.room_drafts:[]){const e=Array.isArray(s?.points)?s.points:[];for(let n=0;n+1e?.host?.kind===t.kind&&e.host.id===t.id)}const J_=new WeakMap,X_=new WeakMap;function Z_(e){const t=Eh(e.cfg),i={};for(const t of e.cfg.spaces||[])for(const e of t.rooms||[])e.area&&(i[e.area]=t.id);const s=e.cfg.settings?.exclude_integrations?new Set(e.cfg.settings.exclude_integrations):Ye,n=Xe(e.cfg.settings?.icon_rules?.length?e.cfg.settings.icon_rules:Je);return So({hass:e.hass,registry:e.registry,areaToSpace:i,markers:e.cfg.markers||[],settings:e.cfg.settings||{},excluded:s,showAll:!!e.cfg.settings?.show_all,firstSpaceId:t[0]?.id||"",loc:t=>ua(e.lang,t),iconRules:n})}function Q_(e){const t=Eh(e.cfg).find(t=>t.id===e.spaceId);if(!t)return null;const i=cs(e.cfg.spaces.find(t=>t.id===e.spaceId)),s=ps(e.cfg.settings),n=e.iconSize??2.5,o=n>8?2.5:n,a=O_(o),r=e.registry?En(e.hass,e.registry):e.hass,l=e.registry?On(e.hass,e.registry):e.hass,c=(e.devices||Z_(e)).filter(t=>t.space===e.spaceId),h=c.filter(e=>!e.hidden),d=function(e,t,i){const s={},n=i/100*nd(t)*1.3;for(const i of t.rooms){if(!i.area)continue;const t=e.filter(e=>e.area===i.area);if(!t.length)continue;const o=rd(i),a=.1*Math.min(o.w,o.h),r=o.w-2*a,l=o.h-2*a,c=Math.max(1,Math.round(Math.sqrt(t.length*r/Math.max(l,1)))),h=r/c,d=l/Math.max(Math.ceil(t.length/c),1),p=t.map((e,t)=>({x:o.x+a+h*(t%c+.5),y:o.y+a+d*(Math.floor(t/c)+.5)}));Oi(p,o,n,.5*a),t.forEach((e,t)=>s[e.id]=Wh(p[t]))}return s}(c,t,o),p=[];for(const t of h){const i=e.layout[t.id];if(i&&i.s===e.spaceId){const e=i.x*Ih,t=i.y*Ih;p.push({minX:e,minY:t,maxX:e,maxY:t})}}const u=e.cfg.spaces.find(t=>t.id===e.spaceId)||{},_=Array.isArray(u.walls)?u.walls:[],m=Number(u.cell_cm)>0?Number(u.cell_cm):5,g=K_(u,t,Ih,.02*Bh),f=(u.openings||[]).flatMap(e=>{if("partition"!==e.host?.kind)return[];const i=iu(e,t.partitions,Ih,m,Bh).resolved;return i?[i]:[]}),v=Vd({partitions:t.partitions,roomDrafts:t.room_drafts,columns:t.wall_columns,cellCm:m,hostedOpenings:f.map(e=>({id:e.opening.id,host:e.host,length:e.length,type:e.opening.type}))}),b=function(e,t,i,s){let n=X_.get(e);n||(n=new Map,X_.set(e,n));const o=n.get(t);if(o?.fingerprint===i)return o.value;const a=s();return n.set(t,{fingerprint:i,value:a}),a}(e.cfg,t.id,v,()=>_h(t,m,Bh,2e-4*Bh,f.map(ou)).all);for(const e of b){const t=e.map(e=>e[0]),i=e.map(e=>e[1]);t.length&&p.push({minX:Math.min(...t),minY:Math.min(...i),maxX:Math.max(...t),maxY:Math.max(...i)})}for(const e of g.lines)p.push({minX:Math.min(e[0],e[2]),minY:Math.min(e[1],e[3]),maxX:Math.max(e[0],e[2]),maxY:Math.max(e[1],e[3])});const y=id(t,p),w=[y.x,y.y,y.w,y.h],k=new Map(t.rooms.map(t=>{const n=zs(i.fill,t);return[t,_s(n,"lqi"===n&&t.area?Do(r,c,t.area):null,"light"===n?uo(ho(r,c,t,e.virtualLights)):"none","temp"===n&&t.area?Po(r,c,t.area):null,i.tempMin,i.tempMax,s,ls(i.customFill,t))]})),x=new Map;for(const e of t.rooms)e.id&&x.set(e.id,k.get(e)||null);const $=function(e,t=1e3){const i=[];for(const[s,n]of(e||[]).entries()){if("passage"!==n?.type)continue;const e={...n,id:String(n.id||`passage-${s}`),type:"passage",rx:Number(n.x)*t,ry:Number(n.y)*t,rlen:Number(n.length)*t};[e.rx,e.ry,e.rlen,e.angle].every(Number.isFinite)&&e.rlen>0&&i.push(e)}return i}((u.openings||[]).flatMap(e=>{if(!e.host||"wall"===e.host.kind)return[e];const t=f.find(t=>t.opening.id===e.id);return t?[cu(e,t,Ih)]:[]}),Ih),S=wc(t.rooms,_,g.contour,jh,m,Bh,Ih),M=f.filter(e=>lu(e,S,2e-4*Bh)).map(e=>({x:e.center[0],y:e.center[1],angle:e.angle,length:e.length})),C=t.rooms.filter(e=>e.area||i.showBorders||"none"!==zs(i.fill,e)).map(e=>{let s="room "+(t.bg?"overlay":"yard"),n="";const o=zs(i.fill,e);if(i.showBorders||"none"!==o){s+=" styled";const t=[`--room-stroke:${i.color}`,`--room-stroke-op:${i.showBorders&&!g.contour.length?i.opacity:0}`],o=k.get(e)||null;o?(s+=" filled",t.push(`--room-fill:${o.color}`,`--room-fill-op:${o.opacity.toFixed(3)}`)):t.push("--room-fill:transparent","--room-fill-op:0"),n=t.join(";")}const a=e.id||G,r=e.area||G,l=e.poly?W``});return W``}const q_=e=>Array.isArray(e)&&e.length>=2&&Number.isFinite(Number(e[0]))&&Number.isFinite(Number(e[1])),W_=(e,t,i)=>[Number(e[0])*i,Number(e[1])*i,Number(t[0])*i,Number(t[1])*i],U_=e=>{const t=`${Number(e[0]).toFixed(9)},${Number(e[1]).toFixed(9)}`,i=`${Number(e[2]).toFixed(9)},${Number(e[3]).toFixed(9)}`;return t<=i?`${t}|${i}`:`${i}|${t}`},G_=e=>{const t=new Map;for(const i of e.flat())!Array.isArray(i)||i.length<4||!i.every(Number.isFinite)||Math.hypot(i[2]-i[0],i[3]-i[1])<=1e-9||t.set(U_(i),[...i]);return[...t.values()]};function V_(e){return"solid"===e?.zero_wall_style?"solid":"dashed"}function K_(e,t=1,i){const s=[];for(const n of Array.isArray(e?.wall_segments)?e.wall_segments:[])0===Number(n?.cm)&&q_(n?.a)&&q_(n?.b)&&(i&&!i.has(String(n.id||""))||s.push(W_(n.a,n.b,t)));return G_([s])}function Y_(e,t=1){const i=[];for(const s of Array.isArray(e?.partitions)?e.partitions:[])0===Number(s?.cm)&&q_(s?.a)&&q_(s?.b)&&i.push(W_(s.a,s.b,t));for(const s of Array.isArray(e?.room_drafts)?e.room_drafts:[]){const e=Array.isArray(s?.points)?s.points:[];for(let n=0;n+1e?.host?.kind===t.kind&&e.host.id===t.id)}const Q_=new WeakMap,em=new WeakMap;function tm(e){const t=Eh(e.cfg),i={};for(const t of e.cfg.spaces||[])for(const e of t.rooms||[])e.area&&(i[e.area]=t.id);const s=e.cfg.settings?.exclude_integrations?new Set(e.cfg.settings.exclude_integrations):Ye,n=Xe(e.cfg.settings?.icon_rules?.length?e.cfg.settings.icon_rules:Je);return So({hass:e.hass,registry:e.registry,areaToSpace:i,markers:e.cfg.markers||[],settings:e.cfg.settings||{},excluded:s,showAll:!!e.cfg.settings?.show_all,firstSpaceId:t[0]?.id||"",loc:t=>ua(e.lang,t),iconRules:n})}function im(e){const t=Eh(e.cfg).find(t=>t.id===e.spaceId);if(!t)return null;const i=cs(e.cfg.spaces.find(t=>t.id===e.spaceId)),s=ps(e.cfg.settings),n=e.iconSize??2.5,o=n>8?2.5:n,a=B_(o),r=e.registry?En(e.hass,e.registry):e.hass,l=e.registry?On(e.hass,e.registry):e.hass,c=(e.devices||tm(e)).filter(t=>t.space===e.spaceId),h=c.filter(e=>!e.hidden),d=function(e,t,i){const s={},n=i/100*nd(t)*1.3;for(const i of t.rooms){if(!i.area)continue;const t=e.filter(e=>e.area===i.area);if(!t.length)continue;const o=rd(i),a=.1*Math.min(o.w,o.h),r=o.w-2*a,l=o.h-2*a,c=Math.max(1,Math.round(Math.sqrt(t.length*r/Math.max(l,1)))),h=r/c,d=l/Math.max(Math.ceil(t.length/c),1),p=t.map((e,t)=>({x:o.x+a+h*(t%c+.5),y:o.y+a+d*(Math.floor(t/c)+.5)}));Oi(p,o,n,.5*a),t.forEach((e,t)=>s[e.id]=Wh(p[t]))}return s}(c,t,o),p=[];for(const t of h){const i=e.layout[t.id];if(i&&i.s===e.spaceId){const e=i.x*Ih,t=i.y*Ih;p.push({minX:e,minY:t,maxX:e,maxY:t})}}const u=e.cfg.spaces.find(t=>t.id===e.spaceId)||{},_=Array.isArray(u.walls)?u.walls:[],m=Number(u.cell_cm)>0?Number(u.cell_cm):5,g=X_(u,t,Ih,.02*Bh),f=(u.openings||[]).flatMap(e=>{if("partition"!==e.host?.kind)return[];const i=ou(e,t.partitions,Ih,m,Bh).resolved;return i?[i]:[]}),v=Jd({partitions:t.partitions,roomDrafts:t.room_drafts,columns:t.wall_columns,cellCm:m,hostedOpenings:f.map(e=>({id:e.opening.id,host:e.host,length:e.length,type:e.opening.type}))}),b=function(e,t,i,s){let n=em.get(e);n||(n=new Map,em.set(e,n));const o=n.get(t);if(o?.fingerprint===i)return o.value;const a=s();return n.set(t,{fingerprint:i,value:a}),a}(e.cfg,t.id,v,()=>_h(t,m,Bh,2e-4*Bh,f.map(lu)).all);for(const e of b){const t=e.map(e=>e[0]),i=e.map(e=>e[1]);t.length&&p.push({minX:Math.min(...t),minY:Math.min(...i),maxX:Math.max(...t),maxY:Math.max(...i)})}for(const e of g.lines)p.push({minX:Math.min(e[0],e[2]),minY:Math.min(e[1],e[3]),maxX:Math.max(e[0],e[2]),maxY:Math.max(e[1],e[3])});const y=id(t,p),w=[y.x,y.y,y.w,y.h],k=new Map(t.rooms.map(t=>{const n=zs(i.fill,t);return[t,_s(n,"lqi"===n&&t.area?Do(r,c,t.area):null,"light"===n?uo(ho(r,c,t,e.virtualLights)):"none","temp"===n&&t.area?Po(r,c,t.area):null,i.tempMin,i.tempMax,s,ls(i.customFill,t))]})),x=new Map;for(const e of t.rooms)e.id&&x.set(e.id,k.get(e)||null);const $=function(e,t=1e3){const i=[];for(const[s,n]of(e||[]).entries()){if("passage"!==n?.type)continue;const e={...n,id:String(n.id||`passage-${s}`),type:"passage",rx:Number(n.x)*t,ry:Number(n.y)*t,rlen:Number(n.length)*t};[e.rx,e.ry,e.rlen,e.angle].every(Number.isFinite)&&e.rlen>0&&i.push(e)}return i}((u.openings||[]).flatMap(e=>{if(!e.host||"wall"===e.host.kind)return[e];const t=f.find(t=>t.opening.id===e.id);return t?[pu(e,t,Ih)]:[]}),Ih),S=wc(t.rooms,_,g.contour,jh,m,Bh,Ih),M=f.filter(e=>du(e,S,2e-4*Bh)).map(e=>({x:e.center[0],y:e.center[1],angle:e.angle,length:e.length})),C=t.rooms.filter(e=>e.area||i.showBorders||"none"!==zs(i.fill,e)).map(e=>{let s="room "+(t.bg?"overlay":"yard"),n="";const o=zs(i.fill,e);if(i.showBorders||"none"!==o){s+=" styled";const t=[`--room-stroke:${i.color}`,`--room-stroke-op:${i.showBorders&&!g.contour.length?i.opacity:0}`],o=k.get(e)||null;o?(s+=" filled",t.push(`--room-fill:${o.color}`,`--room-fill-op:${o.opacity.toFixed(3)}`)):t.push("--room-fill:transparent","--room-fill-op:0"),n=t.join(";")}const a=e.id||G,r=e.area||G,l=e.poly?W``:W``;return l}),D=t.rooms.filter(e=>{const t=k.get(e);return Fs(i.glow,e)&&(!t||t.opacity<=0)}).map(e=>e.poly?W`