diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 08e82622..52f688a7 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,6 +53,18 @@ jobs: - uses: actions/setup-node@v4 with: { node-version: 22 } - run: npm ci && npm run build + - name: Verify compositor frame continuity for a stable release + if: ${{ !github.event.release.prerelease }} + run: | + npx playwright install --with-deps chromium + cp dist/houseplan-card.js demo/srv/assets/houseplan-card.js + npm run continuity:screencast + - name: Upload failed continuity frames + if: ${{ failure() && !github.event.release.prerelease }} + uses: actions/upload-artifact@v4 + with: + name: continuity-screencast + path: artifacts/continuity-screencast - run: cp dist/houseplan-card.js custom_components/houseplan/frontend/ - name: Attach card to release uses: softprops/action-gh-release@v2 diff --git a/custom_components/houseplan/const.py b/custom_components/houseplan/const.py index c3d1bbbd..723353bc 100755 --- a/custom_components/houseplan/const.py +++ b/custom_components/houseplan/const.py @@ -45,7 +45,7 @@ PLAN_ORPHAN_TTL_S = 3600 SCHEDULED_GRACE_S = 30 * 24 * 3600 FILES_DIR = "houseplan/files" CONF_ADMIN_ONLY = "admin_only" -VERSION = "1.61.0-beta.6" +VERSION = "1.61.0-beta.7" DEFAULT_CONFIG: dict = { "spaces": [], diff --git a/custom_components/houseplan/frontend/houseplan-card.js b/custom_components/houseplan/frontend/houseplan-card.js index 6655bb0d..569edbba 100755 --- a/custom_components/houseplan/frontend/houseplan-card.js +++ b/custom_components/houseplan/frontend/houseplan-card.js @@ -1,5 +1,5 @@ -globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96fbc26ab50eb69b49466b3c7bab33";const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),s=new WeakMap;let o=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const i=this.t;if(e&&void 0===t){const e=void 0!==i&&1===i.length;e&&(t=s.get(i)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&s.set(i,t))}return t}toString(){return this.cssText}};const n=(t,...e)=>{const s=1===t.length?t[0]:e.reduce((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1],t[0]);return new o(s,t,i)},r=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new o("string"==typeof t?t:t+"",void 0,i))(e)})(t):t,{is:a,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:d,getPrototypeOf:p}=Object,u=globalThis,_=u.trustedTypes,m=_?_.emptyScript:"",g=u.reactiveElementPolyfillSupport,f=(t,e)=>t,v={toAttribute(t,e){switch(e){case Boolean:t=t?m:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},y=(t,e)=>!a(t,e),b={attribute:!0,type:String,converter:v,reflect:!1,useDefault:!1,hasChanged:y};Symbol.metadata??=Symbol("metadata"),u.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=b){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(t,i,e);void 0!==s&&l(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){const{get:s,set:o}=c(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:s,set(e){const n=s?.call(this);o?.call(this,e),this.requestUpdate(t,n,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??b}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const t=p(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const t=this.properties,e=[...h(t),...d(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(r(t))}else void 0!==t&&e.push(r(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.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(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(e)i.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of s){const s=document.createElement("style"),o=t.litNonce;void 0!==o&&s.setAttribute("nonce",o),s.textContent=e.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$ET(t,e){const i=this.constructor.elementProperties.get(t),s=this.constructor._$Eu(t,i);if(void 0!==s&&!0===i.reflect){const o=(void 0!==i.converter?.toAttribute?i.converter:v).toAttribute(e,i.type);this._$Em=t,null==o?this.removeAttribute(s):this.setAttribute(s,o),this._$Em=null}}_$AK(t,e){const i=this.constructor,s=i._$Eh.get(t);if(void 0!==s&&this._$Em!==s){const t=i.getPropertyOptions(s),o="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:v;this._$Em=s;const n=o.fromAttribute(e,t.type);this[s]=n??this._$Ej?.get(s)??n,this._$Em=null}}requestUpdate(t,e,i,s=!1,o){if(void 0!==t){const n=this.constructor;if(!1===s&&(o=this[t]),i??=n.getPropertyOptions(t),!((i.hasChanged??y)(o,e)||i.useDefault&&i.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,i))))return;this.C(t,e,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:i,reflect:s,wrapped:o},n){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),!0!==o||void 0!==n)||(this._$AL.has(t)||(this.hasUpdated||i||(e=void 0),this._$AL.set(t,e)),!0===s&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,i]of t){const{wrapped:t}=i,s=this[e];!0!==t||this._$AL.has(e)||void 0===s||this.C(e,void 0,i,s)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};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,$=t=>t,x=k.trustedTypes,S=x?x.createPolicy("lit-html",{createHTML:t=>t}):void 0,M="$lit$",C=`lit$${Math.random().toFixed(9).slice(2)}$`,D="?"+C,T=`<${D}>`,P=document,R=()=>P.createComment(""),A=t=>null===t||"object"!=typeof t&&"function"!=typeof t,z=Array.isArray,I="[ \t\n\f\r]",E=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,N=/-->/g,F=/>/g,O=RegExp(`>|${I}(?:([^\\s"'>=/]+)(${I}*=${I}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),L=/'/g,H=/"/g,q=/^(?:script|style|textarea|title)$/i,U=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),B=U(1),j=U(2),G=Symbol.for("lit-noChange"),W=Symbol.for("lit-nothing"),V=new WeakMap,K=P.createTreeWalker(P,129);function Y(t,e){if(!z(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const J=(t,e)=>{const i=t.length-1,s=[];let o,n=2===e?"":3===e?"":"",r=E;for(let e=0;e"===l[0]?(r=o??E,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?O:'"'===l[3]?H:L):r===H||r===L?r=O:r===N||r===F?r=E:(r=O,o=void 0);const d=r===O&&t[e+1].startsWith("/>")?" ":"";n+=r===E?i+T:c>=0?(s.push(a),i.slice(0,c)+M+i.slice(c)+C+d):i+C+(-2===c?e:d)}return[Y(t,n+(t[i]||"")+(2===e?"":3===e?"":"")),s]};class Z{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let o=0,n=0;const r=t.length-1,a=this.parts,[l,c]=J(t,e);if(this.el=Z.createElement(l,i),K.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(s=K.nextNode())&&a.length0){s.textContent=x?x.emptyScript:"";for(let i=0;iz(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==W&&A(this._$AH)?this._$AA.nextSibling.data=t:this.T(P.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:i}=t,s="number"==typeof i?this._$AC(t):(void 0===i.el&&(i.el=Z.createElement(Y(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(e);else{const t=new Q(s,this),i=t.u(this.options);t.p(e),this.T(i),this._$AH=t}}_$AC(t){let e=V.get(t.strings);return void 0===e&&V.set(t.strings,e=new Z(t)),e}k(t){z(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const o of t)s===e.length?e.push(i=new tt(this.O(R()),this.O(R()),this,this.options)):i=e[s],i._$AI(o),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=W}_$AI(t,e=this,i,s){const o=this.strings;let n=!1;if(void 0===o)t=X(this,t,e,0),n=!A(t)||t!==this._$AH&&t!==G,n&&(this._$AH=t);else{const s=t;let r,a;for(t=o[0],r=0;r{const s=i?.renderBefore??e;let o=s._$litPart$;if(void 0===o){const t=i?.renderBefore??null;s._$litPart$=o=new tt(e.insertBefore(R(),t),t,void 0,i??{})}return o._$AI(t),o})(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return G}};ct._$litElement$=!0,ct.finalized=!0,lt.litElementHydrateSupport?.({LitElement:ct});const ht=lt.litElementPolyfillSupport;ht?.({LitElement:ct}),(lt.litElementVersions??=[]).push("4.2.2");const dt=2;let pt=class{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}};const{I:ut}=rt,_t=t=>t,mt=()=>document.createComment(""),gt=(t,e,i)=>{const s=t._$AA.parentNode,o=void 0===e?t._$AB:e._$AA;if(void 0===i){const e=s.insertBefore(mt(),o),n=s.insertBefore(mt(),o);i=new ut(e,n,t,t.options)}else{const e=i._$AB.nextSibling,n=i._$AM,r=n!==t;if(r){let e;i._$AQ?.(t),i._$AM=t,void 0!==i._$AP&&(e=t._$AU)!==n._$AU&&i._$AP(e)}if(e!==o||r){let t=i._$AA;for(;t!==e;){const e=_t(t).nextSibling;_t(s).insertBefore(t,o),t=e}}}return i},ft=(t,e,i=t)=>(t._$AI(e,i),t),vt={},yt=(t,e=vt)=>t._$AH=e,bt=t=>{t._$AR(),t._$AA.remove()},wt=(t,e,i)=>{const s=new Map;for(let o=e;o<=i;o++)s.set(t[o],o);return s},kt=(t=>(...e)=>({_$litDirective$:t,values:e}))(class extends pt{constructor(t){if(super(t),t.type!==dt)throw Error("repeat() can only be used in text expressions")}dt(t,e,i){let s;void 0===i?i=e:void 0!==e&&(s=e);const o=[],n=[];let r=0;for(const e of t)o[r]=s?s(e,r):r,n[r]=i(e,r),r++;return{values:n,keys:o}}render(t,e,i){return this.dt(t,e,i).values}update(t,[e,i,s]){const o=(t=>t._$AH)(t),{values:n,keys:r}=this.dt(e,i,s);if(!Array.isArray(o))return this.ut=r,n;const a=this.ut??=[],l=[];let c,h,d=0,p=o.length-1,u=0,_=n.length-1;for(;d<=p&&u<=_;)if(null===o[d])d++;else if(null===o[p])p--;else if(a[d]===r[u])l[u]=ft(o[d],n[u]),d++,u++;else if(a[p]===r[_])l[_]=ft(o[p],n[_]),p--,_--;else if(a[d]===r[_])l[_]=ft(o[d],n[_]),gt(t,l[_+1],o[d]),d++,_--;else if(a[p]===r[u])l[u]=ft(o[p],n[u]),gt(t,o[d],o[p]),p--,u++;else if(void 0===c&&(c=wt(r,u,_),h=wt(a,d,p)),c.has(a[d]))if(c.has(a[p])){const e=h.get(r[u]),i=void 0!==e?o[e]:null;if(null===i){const e=gt(t,o[d]);ft(e,n[u]),l[u]=e}else l[u]=ft(i,n[u]),gt(t,o[d],i),o[e]=null;u++}else bt(o[p]),p--;else bt(o[d]),d++;for(;u<=_;){const e=gt(t,l[_+1]);ft(e,n[u]),l[u++]=e}for(;d<=p;){const t=o[d++];null!==t&&bt(t)}return this.ut=r,yt(t,l),G}}),$t=new WeakMap;let xt=0;class St extends ct{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._titleId="hp-dialog-title-"+ ++xt,this._focusInitial=()=>{const t=this.querySelector("[autofocus]")||this._focusableElements()[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=t=>{if("Escape"===t.key)return t.preventDefault(),t.stopImmediatePropagation(),void this._requestClose();if("Tab"!==t.key||this._useHaDialog)return;const e=this.renderRoot.querySelector(".close"),i=e?[e,...this._focusableElements()]:this._focusableElements();if(!i.length)return t.preventDefault(),void this.renderRoot.querySelector(".surface")?.focus({preventScroll:!0});const s=this._deepActiveElement(),o=i[0],n=i[i.length-1];!t.shiftKey||s!==o&&i.includes(s)?t.shiftKey||s!==n||(t.preventDefault(),o.focus()):(t.preventDefault(),n.focus())},this._onFallbackCancel=t=>{t.preventDefault(),this._requestClose()},this._onFallbackClick=t=>{this.dismissOnScrim&&t.target===t.currentTarget&&this._requestClose()}}connectedCallback(){super.connectedCallback(),this._opener=this._deepActiveElement();const t=this.getRootNode();this._focusRoot=t;const e=$t.get(t)||{dialogs:new Set,opener:this._opener};e.dialogs.add(this),$t.set(t,e),this._useHaDialog=!!customElements.get("ha-dialog"),this.addEventListener("keydown",this._onKeyDown,!0)}disconnectedCallback(){this.removeEventListener("keydown",this._onKeyDown,!0);const t=this._focusRoot,e=this._opener;this._opener=null,this._focusRoot=null;const i=t?$t.get(t):void 0;i?.dialogs.delete(this),super.disconnectedCallback(),t&&i&&requestAnimationFrame(()=>{const i=$t.get(t);if(!i)return;if(!i.dialogs.size)return i.opener?.isConnected&&i.opener.focus({preventScroll:!0}),void $t.delete(t);const s=e?.closest("hp-dialog");e?.isConnected&&s&&i.dialogs.has(s)&&e.focus({preventScroll:!0})})}firstUpdated(t){if(super.firstUpdated(t),!this._useHaDialog){const t=this.renderRoot.querySelector("dialog");t&&!t.open&&t.showModal()}queueMicrotask(()=>this._focusInitial())}_deepActiveElement(){let t=document.activeElement;for(;t?.shadowRoot?.activeElement;)t=t.shadowRoot.activeElement;return t&&t!==document.body?t:null}_focusableElements(){const t=["[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(",");return Array.from(this.querySelectorAll(t)).filter(t=>{const e=getComputedStyle(t);return!t.hidden&&"none"!==e.display&&"hidden"!==e.visibility})}render(){const t=B` - ${this.icon?B``:W} +globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="0f963f24ce2ba631daf1931ac9ffd44c7326b1bfee5fca1dac9b3a1678c892f7";const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),s=new WeakMap;let n=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const i=this.t;if(e&&void 0===t){const e=void 0!==i&&1===i.length;e&&(t=s.get(i)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&s.set(i,t))}return t}toString(){return this.cssText}};const o=(t,...e)=>{const s=1===t.length?t[0]:e.reduce((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1],t[0]);return new n(s,t,i)},r=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new n("string"==typeof t?t:t+"",void 0,i))(e)})(t):t,{is:a,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:d,getPrototypeOf:p}=Object,u=globalThis,_=u.trustedTypes,m=_?_.emptyScript:"",g=u.reactiveElementPolyfillSupport,f=(t,e)=>t,v={toAttribute(t,e){switch(e){case Boolean:t=t?m:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},y=(t,e)=>!a(t,e),b={attribute:!0,type:String,converter:v,reflect:!1,useDefault:!1,hasChanged:y};Symbol.metadata??=Symbol("metadata"),u.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=b){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(t,i,e);void 0!==s&&l(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){const{get:s,set:n}=c(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:s,set(e){const o=s?.call(this);n?.call(this,e),this.requestUpdate(t,o,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??b}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const t=p(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const t=this.properties,e=[...h(t),...d(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(r(t))}else void 0!==t&&e.push(r(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.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(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(e)i.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of s){const s=document.createElement("style"),n=t.litNonce;void 0!==n&&s.setAttribute("nonce",n),s.textContent=e.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$ET(t,e){const i=this.constructor.elementProperties.get(t),s=this.constructor._$Eu(t,i);if(void 0!==s&&!0===i.reflect){const n=(void 0!==i.converter?.toAttribute?i.converter:v).toAttribute(e,i.type);this._$Em=t,null==n?this.removeAttribute(s):this.setAttribute(s,n),this._$Em=null}}_$AK(t,e){const i=this.constructor,s=i._$Eh.get(t);if(void 0!==s&&this._$Em!==s){const t=i.getPropertyOptions(s),n="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:v;this._$Em=s;const o=n.fromAttribute(e,t.type);this[s]=o??this._$Ej?.get(s)??o,this._$Em=null}}requestUpdate(t,e,i,s=!1,n){if(void 0!==t){const o=this.constructor;if(!1===s&&(n=this[t]),i??=o.getPropertyOptions(t),!((i.hasChanged??y)(n,e)||i.useDefault&&i.reflect&&n===this._$Ej?.get(t)&&!this.hasAttribute(o._$Eu(t,i))))return;this.C(t,e,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:i,reflect:s,wrapped:n},o){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,o??e??this[t]),!0!==n||void 0!==o)||(this._$AL.has(t)||(this.hasUpdated||i||(e=void 0),this._$AL.set(t,e)),!0===s&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,i]of t){const{wrapped:t}=i,s=this[e];!0!==t||this._$AL.has(e)||void 0===s||this.C(e,void 0,i,s)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};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,$=t=>t,x=k.trustedTypes,S=x?x.createPolicy("lit-html",{createHTML:t=>t}):void 0,M="$lit$",C=`lit$${Math.random().toFixed(9).slice(2)}$`,D="?"+C,T=`<${D}>`,R=document,P=()=>R.createComment(""),A=t=>null===t||"object"!=typeof t&&"function"!=typeof t,z=Array.isArray,I="[ \t\n\f\r]",E=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,F=/-->/g,N=/>/g,O=RegExp(`>|${I}(?:([^\\s"'>=/]+)(${I}*=${I}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),L=/'/g,H=/"/g,q=/^(?:script|style|textarea|title)$/i,U=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),B=U(1),W=U(2),j=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),V=new WeakMap,K=R.createTreeWalker(R,129);function Y(t,e){if(!z(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const J=(t,e)=>{const i=t.length-1,s=[];let n,o=2===e?"":3===e?"":"",r=E;for(let e=0;e"===l[0]?(r=n??E,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?O:'"'===l[3]?H:L):r===H||r===L?r=O:r===F||r===N?r=E:(r=O,n=void 0);const d=r===O&&t[e+1].startsWith("/>")?" ":"";o+=r===E?i+T:c>=0?(s.push(a),i.slice(0,c)+M+i.slice(c)+C+d):i+C+(-2===c?e:d)}return[Y(t,o+(t[i]||"")+(2===e?"":3===e?"":"")),s]};class Z{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let n=0,o=0;const r=t.length-1,a=this.parts,[l,c]=J(t,e);if(this.el=Z.createElement(l,i),K.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(s=K.nextNode())&&a.length0){s.textContent=x?x.emptyScript:"";for(let i=0;iz(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==G&&A(this._$AH)?this._$AA.nextSibling.data=t:this.T(R.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:i}=t,s="number"==typeof i?this._$AC(t):(void 0===i.el&&(i.el=Z.createElement(Y(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(e);else{const t=new Q(s,this),i=t.u(this.options);t.p(e),this.T(i),this._$AH=t}}_$AC(t){let e=V.get(t.strings);return void 0===e&&V.set(t.strings,e=new Z(t)),e}k(t){z(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const n of t)s===e.length?e.push(i=new tt(this.O(P()),this.O(P()),this,this.options)):i=e[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(t,e=this,i,s){const n=this.strings;let o=!1;if(void 0===n)t=X(this,t,e,0),o=!A(t)||t!==this._$AH&&t!==j,o&&(this._$AH=t);else{const s=t;let r,a;for(t=n[0],r=0;r{const s=i?.renderBefore??e;let n=s._$litPart$;if(void 0===n){const t=i?.renderBefore??null;s._$litPart$=n=new tt(e.insertBefore(P(),t),t,void 0,i??{})}return n._$AI(t),n})(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return j}};ct._$litElement$=!0,ct.finalized=!0,lt.litElementHydrateSupport?.({LitElement:ct});const ht=lt.litElementPolyfillSupport;ht?.({LitElement:ct}),(lt.litElementVersions??=[]).push("4.2.2");const dt=2;let pt=class{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}};const{I:ut}=rt,_t=t=>t,mt=()=>document.createComment(""),gt=(t,e,i)=>{const s=t._$AA.parentNode,n=void 0===e?t._$AB:e._$AA;if(void 0===i){const e=s.insertBefore(mt(),n),o=s.insertBefore(mt(),n);i=new ut(e,o,t,t.options)}else{const e=i._$AB.nextSibling,o=i._$AM,r=o!==t;if(r){let e;i._$AQ?.(t),i._$AM=t,void 0!==i._$AP&&(e=t._$AU)!==o._$AU&&i._$AP(e)}if(e!==n||r){let t=i._$AA;for(;t!==e;){const e=_t(t).nextSibling;_t(s).insertBefore(t,n),t=e}}}return i},ft=(t,e,i=t)=>(t._$AI(e,i),t),vt={},yt=(t,e=vt)=>t._$AH=e,bt=t=>{t._$AR(),t._$AA.remove()},wt=(t,e,i)=>{const s=new Map;for(let n=e;n<=i;n++)s.set(t[n],n);return s},kt=(t=>(...e)=>({_$litDirective$:t,values:e}))(class extends pt{constructor(t){if(super(t),t.type!==dt)throw Error("repeat() can only be used in text expressions")}dt(t,e,i){let s;void 0===i?i=e:void 0!==e&&(s=e);const n=[],o=[];let r=0;for(const e of t)n[r]=s?s(e,r):r,o[r]=i(e,r),r++;return{values:o,keys:n}}render(t,e,i){return this.dt(t,e,i).values}update(t,[e,i,s]){const n=(t=>t._$AH)(t),{values:o,keys:r}=this.dt(e,i,s);if(!Array.isArray(n))return this.ut=r,o;const a=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(a[d]===r[u])l[u]=ft(n[d],o[u]),d++,u++;else if(a[p]===r[_])l[_]=ft(n[p],o[_]),p--,_--;else if(a[d]===r[_])l[_]=ft(n[d],o[_]),gt(t,l[_+1],n[d]),d++,_--;else if(a[p]===r[u])l[u]=ft(n[p],o[u]),gt(t,n[d],n[p]),p--,u++;else if(void 0===c&&(c=wt(r,u,_),h=wt(a,d,p)),c.has(a[d]))if(c.has(a[p])){const e=h.get(r[u]),i=void 0!==e?n[e]:null;if(null===i){const e=gt(t,n[d]);ft(e,o[u]),l[u]=e}else l[u]=ft(i,o[u]),gt(t,n[d],i),n[e]=null;u++}else bt(n[p]),p--;else bt(n[d]),d++;for(;u<=_;){const e=gt(t,l[_+1]);ft(e,o[u]),l[u++]=e}for(;d<=p;){const t=n[d++];null!==t&&bt(t)}return this.ut=r,yt(t,l),j}}),$t=new WeakMap;let xt=0;class St extends ct{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._titleId="hp-dialog-title-"+ ++xt,this._focusInitial=()=>{const t=this.querySelector("[autofocus]")||this._focusableElements()[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=t=>{if("Escape"===t.key)return t.preventDefault(),t.stopImmediatePropagation(),void this._requestClose();if("Tab"!==t.key||this._useHaDialog)return;const e=this.renderRoot.querySelector(".close"),i=e?[e,...this._focusableElements()]:this._focusableElements();if(!i.length)return t.preventDefault(),void this.renderRoot.querySelector(".surface")?.focus({preventScroll:!0});const s=this._deepActiveElement(),n=i[0],o=i[i.length-1];!t.shiftKey||s!==n&&i.includes(s)?t.shiftKey||s!==o||(t.preventDefault(),n.focus()):(t.preventDefault(),o.focus())},this._onFallbackCancel=t=>{t.preventDefault(),this._requestClose()},this._onFallbackClick=t=>{this.dismissOnScrim&&t.target===t.currentTarget&&this._requestClose()}}connectedCallback(){super.connectedCallback(),this._opener=this._deepActiveElement();const t=this.getRootNode();this._focusRoot=t;const e=$t.get(t)||{dialogs:new Set,opener:this._opener};e.dialogs.add(this),$t.set(t,e),this._useHaDialog=!!customElements.get("ha-dialog"),this.addEventListener("keydown",this._onKeyDown,!0)}disconnectedCallback(){this.removeEventListener("keydown",this._onKeyDown,!0);const t=this._focusRoot,e=this._opener;this._opener=null,this._focusRoot=null;const i=t?$t.get(t):void 0;i?.dialogs.delete(this),super.disconnectedCallback(),t&&i&&requestAnimationFrame(()=>{const i=$t.get(t);if(!i)return;if(!i.dialogs.size)return i.opener?.isConnected&&i.opener.focus({preventScroll:!0}),void $t.delete(t);const s=e?.closest("hp-dialog");e?.isConnected&&s&&i.dialogs.has(s)&&e.focus({preventScroll:!0})})}firstUpdated(t){if(super.firstUpdated(t),!this._useHaDialog){const t=this.renderRoot.querySelector("dialog");t&&!t.open&&t.showModal()}queueMicrotask(()=>this._focusInitial())}_deepActiveElement(){let t=document.activeElement;for(;t?.shadowRoot?.activeElement;)t=t.shadowRoot.activeElement;return t&&t!==document.body?t:null}_focusableElements(){const t=["[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(",");return Array.from(this.querySelectorAll(t)).filter(t=>{const e=getComputedStyle(t);return!t.hidden&&"none"!==e.display&&"hidden"!==e.visibility})}render(){const t=B` + ${this.icon?B``:G} ${this.title} `;return this._useHaDialog?B` - `}}St.properties={title:{type:String},icon:{type:String},wide:{type:Boolean,reflect:!0},dismissOnScrim:{type:Boolean,attribute:"dismiss-on-scrim"},hass:{attribute:!1}},St.styles=n` + `}}St.properties={title:{type:String},icon:{type:String},wide:{type:Boolean,reflect:!0},dismissOnScrim:{type:Boolean,attribute:"dismiss-on-scrim"},hass:{attribute:!1}},St.styles=o` :host { display: contents; color: var(--primary-text-color, #e6e7eb); @@ -191,8 +191,8 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 display: flex; flex-direction: column; } - `,customElements.get("hp-dialog")||customElements.define("hp-dialog",St);const Mt=/^#[0-9a-fA-F]{6}$/;function Ct(t,e){return"string"==typeof t&&Mt.test(t)?t:e}const Dt="(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])",Tt=new RegExp(`^rgb\\(${Dt}, ${Dt}, ${Dt}\\)$`);function Pt(t){if(!Array.isArray(t)||t.length<3)return null;const e=t.slice(0,3);if(!e.every(t=>"number"==typeof t&&Number.isFinite(t)))return null;const[i,s,o]=e.map(t=>Math.round(Math.min(255,Math.max(0,t))));return`rgb(${i}, ${s}, ${o})`}class Rt extends ct{constructor(){super(...arguments),this.label="",this.opacityLabel="Opacity",this.color="#607d8b",this.opacity=1,this.disabled=!1,this.showOpacity=!0,this._open=!1,this._pickerRaf=0,this._outsidePointerDown=t=>{this._open&&!t.composedPath().includes(this)&&this._closePicker()},this._keyDown=t=>{this._open&&"Escape"===t.key&&(t.preventDefault(),t.stopImmediatePropagation(),this._closePicker(!0))},this._queuePickerPosition=()=>{this._open&&(this._pickerRaf&&cancelAnimationFrame(this._pickerRaf),this._pickerRaf=requestAnimationFrame(()=>{this._pickerRaf=0,this._positionPicker()}))}}connectedCallback(){super.connectedCallback(),document.addEventListener("pointerdown",this._outsidePointerDown,!0),document.addEventListener("scroll",this._queuePickerPosition,!0),window.addEventListener("resize",this._queuePickerPosition),this.addEventListener("keydown",this._keyDown,!0)}disconnectedCallback(){document.removeEventListener("pointerdown",this._outsidePointerDown,!0),document.removeEventListener("scroll",this._queuePickerPosition,!0),window.removeEventListener("resize",this._queuePickerPosition),this.removeEventListener("keydown",this._keyDown,!0),this._pickerRaf&&cancelAnimationFrame(this._pickerRaf),this._pickerRaf=0,super.disconnectedCallback()}_toggle(){this.disabled||(this._open?this._closePicker():(this._open=!0,this.updateComplete.then(()=>this._positionPicker())))}_closePicker(t=!1){const e=this.renderRoot.querySelector(".picker");if(e?.hidePopover)try{e.matches(":popover-open")&&e.hidePopover()}catch{}this._open=!1,t&&this.updateComplete.then(()=>this.renderRoot.querySelector(".trigger")?.focus())}_positionPicker(){if(!this._open)return;const t=this.renderRoot.querySelector(".trigger"),e=this.renderRoot.querySelector(".picker");if(!t||!e)return;if(e.style.visibility="hidden",e.showPopover)try{e.matches(":popover-open")||e.showPopover()}catch{}const i=t.getBoundingClientRect(),s=e.getBoundingClientRect(),o=Math.max(8,window.innerWidth-s.width-8);let n=i.left;n+s.width>window.innerWidth-8&&(n=i.right-s.width),n=Math.min(o,Math.max(8,n));const r=i.bottom+7,a=i.top-7-s.height;let l=r;l=r+s.height>window.innerHeight-8&&a>=8?a:Math.min(Math.max(8,l),Math.max(8,window.innerHeight-s.height-8)),e.style.left=`${Math.round(n)}px`,e.style.top=`${Math.round(l)}px`,e.style.visibility=""}_emit(t,e){const i=Number.isFinite(e)?e:Number(this.opacity)||0,s=Math.min(1,Math.max(0,i));this.color=t,this.opacity=s,this.dispatchEvent(new CustomEvent("hp-color-opacity-change",{detail:{color:t,opacity:s},bubbles:!0,composed:!0}))}render(){const t=Math.round(100*Math.min(1,Math.max(0,Number(this.opacity)||0))),e=Ct(this.color,"#607d8b"),i=this.showOpacity?`${this.label||"Color"}: ${e}, ${t}%`:`${this.label||"Color"}: ${e}`;return B` - ${this.label?B`${this.label}`:W} + `,customElements.get("hp-dialog")||customElements.define("hp-dialog",St);const Mt=/^#[0-9a-fA-F]{6}$/;function Ct(t,e){return"string"==typeof t&&Mt.test(t)?t:e}const Dt="(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])",Tt=new RegExp(`^rgb\\(${Dt}, ${Dt}, ${Dt}\\)$`);function Rt(t){if(!Array.isArray(t)||t.length<3)return null;const e=t.slice(0,3);if(!e.every(t=>"number"==typeof t&&Number.isFinite(t)))return null;const[i,s,n]=e.map(t=>Math.round(Math.min(255,Math.max(0,t))));return`rgb(${i}, ${s}, ${n})`}class Pt extends ct{constructor(){super(...arguments),this.label="",this.opacityLabel="Opacity",this.color="#607d8b",this.opacity=1,this.disabled=!1,this.showOpacity=!0,this._open=!1,this._pickerRaf=0,this._outsidePointerDown=t=>{this._open&&!t.composedPath().includes(this)&&this._closePicker()},this._keyDown=t=>{this._open&&"Escape"===t.key&&(t.preventDefault(),t.stopImmediatePropagation(),this._closePicker(!0))},this._queuePickerPosition=()=>{this._open&&(this._pickerRaf&&cancelAnimationFrame(this._pickerRaf),this._pickerRaf=requestAnimationFrame(()=>{this._pickerRaf=0,this._positionPicker()}))}}connectedCallback(){super.connectedCallback(),document.addEventListener("pointerdown",this._outsidePointerDown,!0),document.addEventListener("scroll",this._queuePickerPosition,!0),window.addEventListener("resize",this._queuePickerPosition),this.addEventListener("keydown",this._keyDown,!0)}disconnectedCallback(){document.removeEventListener("pointerdown",this._outsidePointerDown,!0),document.removeEventListener("scroll",this._queuePickerPosition,!0),window.removeEventListener("resize",this._queuePickerPosition),this.removeEventListener("keydown",this._keyDown,!0),this._pickerRaf&&cancelAnimationFrame(this._pickerRaf),this._pickerRaf=0,super.disconnectedCallback()}_toggle(){this.disabled||(this._open?this._closePicker():(this._open=!0,this.updateComplete.then(()=>this._positionPicker())))}_closePicker(t=!1){const e=this.renderRoot.querySelector(".picker");if(e?.hidePopover)try{e.matches(":popover-open")&&e.hidePopover()}catch{}this._open=!1,t&&this.updateComplete.then(()=>this.renderRoot.querySelector(".trigger")?.focus())}_positionPicker(){if(!this._open)return;const t=this.renderRoot.querySelector(".trigger"),e=this.renderRoot.querySelector(".picker");if(!t||!e)return;if(e.style.visibility="hidden",e.showPopover)try{e.matches(":popover-open")||e.showPopover()}catch{}const i=t.getBoundingClientRect(),s=e.getBoundingClientRect(),n=Math.max(8,window.innerWidth-s.width-8);let o=i.left;o+s.width>window.innerWidth-8&&(o=i.right-s.width),o=Math.min(n,Math.max(8,o));const r=i.bottom+7,a=i.top-7-s.height;let l=r;l=r+s.height>window.innerHeight-8&&a>=8?a:Math.min(Math.max(8,l),Math.max(8,window.innerHeight-s.height-8)),e.style.left=`${Math.round(o)}px`,e.style.top=`${Math.round(l)}px`,e.style.visibility=""}_emit(t,e){const i=Number.isFinite(e)?e:Number(this.opacity)||0,s=Math.min(1,Math.max(0,i));this.color=t,this.opacity=s,this.dispatchEvent(new CustomEvent("hp-color-opacity-change",{detail:{color:t,opacity:s},bubbles:!0,composed:!0}))}render(){const t=Math.round(100*Math.min(1,Math.max(0,Number(this.opacity)||0))),e=Ct(this.color,"#607d8b"),i=this.showOpacity?`${this.label||"Color"}: ${e}, ${t}%`:`${this.label||"Color"}: ${e}`;return B` + ${this.label?B`${this.label}`:G} `:W} - `}}pn.properties={hass:{attribute:!1},presentation:{attribute:!1},registry:{attribute:!1},deviceName:{attribute:!1}},pn.styles=[At,n` + `:G} + `}}po.properties={hass:{attribute:!1},presentation:{attribute:!1},registry:{attribute:!1},deviceName:{attribute:!1}},po.styles=[At,o` :host { display: block; min-width: 0; } .devicepreview { margin: 4px 0 14px; @@ -3389,80 +3416,102 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 .previewstage { min-height: 142px; } .previewfacts > div { grid-template-columns: minmax(0, 1fr); gap: 2px; } } - `],customElements.get("hp-device-preview")||customElements.define("hp-device-preview",pn);const un=(t,e)=>[t[0]-e[0],t[1]-e[1]],_n=(t,e)=>[t[0]+e[0],t[1]+e[1]],mn=(t,e)=>t[0]*e[0]+t[1]*e[1],gn=t=>Math.hypot(t[0],t[1]);function fn(t){let e=0;for(let i=0;it[1]!=l>t[1]&&t[0]<(a-n)*(t[1]-r)/(l-r)+n&&(i=!i)}return i}([a[0]+r[0]*l,a[1]+r[1]*l],t)&&(r=[-r[0],-r[1]]),r}function bn(t,e,i,s){const o=s||yn(t,e),n=(e+1)%t.length;return t.map((t,s)=>s===e||s===n?[t[0]+o[0]*i,t[1]+o[1]*i]:[...t])}function wn(t,e,i,s){const o=[],n=un(i,e),r=gn(n);if(rs||h>s)continue;const d=mn(un(n,e),a),p=mn(un(l,e),a),u=Math.max(0,Math.min(d,p)),_=Math.min(r,Math.max(d,p));_-u>s&&o.push([[e[0]+a[0]*u,e[1]+a[1]*u],[e[0]+a[0]*_,e[1]+a[1]*_]])}return o}function kn(t,e,i,s,o){const n=wn(t,e,i,o);if(!n.length)return null;const r=t=>n.some(([e,i])=>vn(t,e,i)<=o),a=t.length,l=[];for(let n=0;no||f>o)continue;const v=mn(un(e,c),u),y=mn(un(i,c),u),b=Math.max(0,Math.min(v,y)),w=Math.min(p,Math.max(v,y));if(!(w-b<=o)){if(b>o&&bo&&wgn(un(i,t[(s+1)%t.length]))>e);for(let t=0;t<2;t++)i=i.filter((t,s)=>{const o=i[(s-1+i.length)%i.length],n=i[(s+1)%i.length],r=(t[0]-o[0])*(n[1]-o[1])-(t[1]-o[1])*(n[0]-o[0]),a=gn(un(n,o))||1;return Math.abs(r)/a>e});return i.length>=3?i:t}function xn(t){const e=t.length;if(e<3)return!1;for(let i=0;i(t[0]-o[0])*a[1]-(t[1]-o[1])*a[0],c=t=>(t[0]-o[0])*a[0]+(t[1]-o[1])*a[1],h=i,d=r-i;for(const e of t){const t=Math.abs(l(e));if(t<=i)continue;const o=c(e);o<=h||o>=d||tMath.abs(r+(t-p)/g*(a-r));s=Math.min(s,f(_),f(m))}}return s}function Mn(t,e,i){if(ui(t,e,i))return!0;if(pi(t,e,i)||pi(e,t,i))return!1;let s=0;try{const i=je([[...t.map(t=>[t[0],t[1]]),[t[0][0],t[0][1]]]],[[...e.map(t=>[t[0],t[1]]),[e[0][0],e[0][1]]]]);for(const t of i)t?.[0]&&(s+=mi(t[0]))}catch{return!1}return s>Math.max(1e-7,i*i)}function Cn(t,e,i,s,o){const n=[i.n[0]*s,i.n[1]*s],r={polys:{},openings:{},movedSpans:{}};if(Math.abs(s)<1e-9)return r;for(const e of t){if(e.id===i.roomId){r.polys[e.id]=bn(e.poly,i.edge,s,i.n),r.movedSpans[e.id]=[[_n(i.a,n),_n(i.b,n)]];continue}const t=wn(e.poly,i.a,i.b,o);if(!t.length)continue;const a=kn(e.poly,i.a,i.b,n,o);a&&(r.polys[e.id]=a,r.movedSpans[e.id]=t.map(([t,e])=>[_n(t,n),_n(e,n)]))}for(const t of e)vn([t.x,t.y],i.a,i.b)<=o&&(r.openings[t.id]=[t.x+n[0],t.y+n[1]]);return r}function Dn(t,e,i){for(const s of e)for(let e=0;ei)continue;const c=(t.x-o[0])*l[0]+(t.y-o[1])*l[1];if(c-t.length/2>=-i&&c+t.length/2<=a+i)return!0}return!1}function Tn(t,e,i){return t.filter(t=>e.some(e=>{for(let s=0;sa.polys[t.id]||t.poly;for(const e of l){const s=t.find(t=>t.id===e),o=a.polys[e];if(!xn(o))return!1;const l=fn(s.poly),h=fn(o);if(Math.abs(h)t.find(t=>t.id===e).poly),d=t.map(c);for(const t of Tn(e,h,2*r)){const e=a.openings[t.id];if(!Dn(e?{...t,x:e[0],y:e[1]}:t,d,2*r))return!1}return!0}function Rn(t,e,i,s,o,n){const r=t=>[s[0]+(t[0]-s[0])*o,s[1]+(t[1]-s[1])*o],a={poly:t.poly.map(r),openings:{}};for(const s of e){let e=!1;for(let i=0;i{for(let e=0;et.id===i);if(!l)return!1;const c=t.filter(t=>t.id!==i).map(t=>t.poly),h=Rn(l,e,c,s,o,2*a),d=h.poly,p=function(t){const e=function(t){const e=[...t].sort((t,e)=>t[0]-e[0]||t[1]-e[1]);if(e.length<3)return e;const i=(t,e,i)=>(e[0]-t[0])*(i[1]-t[1])-(e[1]-t[1])*(i[0]-t[0]),s=[];for(const t of e){for(;s.length>=2&&i(s[s.length-2],s[s.length-1],t)<=0;)s.pop();s.push(t)}const o=[];for(let t=e.length-1;t>=0;t--){const s=e[t];for(;o.length>=2&&i(o[o.length-2],o[o.length-1],s)<=0;)o.pop();o.push(s)}return s.pop(),o.pop(),s.concat(o)}(t);if(e.length<3)return 0;let i=1/0;for(let t=0;tt.id===i?d:t.poly);for(const t of Tn(e,[l.poly],2*a)){const e=h.openings[t.id];if(!Dn(e?{...t,x:e[0],y:e[1]}:t,u,2*a))return!1}return!0}function zn(t,e,i){const s=i/e;return mi(t)*s*s/1e4}function In(t,e){return e?`${Math.round(10.7639*t)} ft²`:`${(Math.round(10*t)/10).toFixed(1)} m²`}const En=[[-90,"#070c14"],[-12,"#070c14"],[-4,"#131a28"],[0,"#4a3527"],[10,"#e8ddcf"],[30,"#ffffff"],[90,"#ffffff"]],Nn=t=>Math.min(1,Math.max(0,t));function Fn(t){const e=Math.min(90,Math.max(-90,Number(t)||0));let i=En[En.length-1][1];for(let t=1;t{const n=[t.x+o[0]*i*s,t.y+o[1]*i*s];return e.find(t=>t.poly.length>=3&&si(n,t.poly))||null},r=n(1),a=n(-1);return r&&a?null:r||a?r?{normal:[-o[0],-o[1]],roomId:r.id}:{normal:o,roomId:a.id}:null}function Ln(t,e,i){return i>0&&t[0]*e[0]+t[1]*e[1]>.05}function Hn(t,e,i,s){return[[t[0],t[1]],[e[0],e[1]],[e[0]+i[0]*s,e[1]+i[1]*s],[t[0]+i[0]*s,t[1]+i[1]*s]]}function qn(t,e){try{const i=je([[...t.map(t=>[t[0],t[1]]),[t[0][0],t[0][1]]]],[[...e.map(t=>[t[0],t[1]]),[e[0][0],e[0][1]]]]),s=[];for(const t of i){const e=t?.[0];!Array.isArray(e)||e.length<4||s.push(e.slice(0,e.length-1).map(t=>[t[0],t[1]]))}return s}catch{return[]}}function Un(t,e,i,s,o,n,r){if(!(s>0))return[];const a=function(t,e){const i=function(t,e){return function(t){const e=t%360;return e<0?e+360:e}(t-e)}(t,e)*Math.PI/180;return[Math.sin(i),-Math.cos(i)]}(i,o),l=[-a[0],-a[1]],c=function(t){const e=Math.min(90,Math.max(0,t));return.7*(.8+1.7*Math.pow(1-e/90,1.6))}(s),h=[];for(const i of e){if(!(i.length>0))continue;const e=On(i,t);if(!e||!Ln(e.normal,a,s))continue;const o=t.find(t=>t.id===e.roomId);if(!o)continue;const d=n&&n[e.roomId]||o.poly,p=i.angle*Math.PI/180,u=i.length/2,_=[-e.normal[0],-e.normal[1]],m=Math.max(0,r?.[i.id]||0),g=i.x+_[0]*m/2,f=i.y+_[1]*m/2,v=Math.cos(p)*u,y=Math.sin(p)*u,b=[g-v,f-y],w=[g+v,f+y],k=c*i.length,$=qn(Hn(b,w,l,k),d);if(!$.length)continue;const x=l[0]*_[0]+l[1]*_[1];h.push({openingId:i.id,roomId:e.roomId,polys:$,a:b,b:w,dir:l,len:k,normal:_,depth:k*x})}return h}function Bn(t){return Math.round(10*(Number(t)||0))/10}const jn=t=>"number"==typeof t&&Number.isInteger(t)&&t>=0&&t<=359?t:null;function Gn(t,e){const i=jn(e?.north_deg);return null!==i?i:jn(t?.north_deg)}function Wn(t,e){const i=t=>"static"===t||"daynight"===t?t:null;return i(e?.bg_mode)??i(t?.bg_mode)??"static"}function Vn(t,e){const i=e?.sun_rays;return"boolean"==typeof i?i:!0===t?.sun_rays}function Kn(t){const e=t?.states?.["sun.sun"]?.attributes,i=Number(e?.azimuth),s=Number(e?.elevation);return Number.isFinite(i)&&Number.isFinite(s)?{azimuth:i,elevation:s}:null}const Yn={color:"#607d8b",opacity:1,widthCm:3.6,fill:!1,fillColor:"#607d8b",fillOpacity:.25},Jn=(t,e=1)=>{const i=Number(t);return Number.isFinite(i)?Math.min(1,Math.max(0,i)):e},Zn=t=>{let e=Number(t);return Number.isFinite(e)?(e=(e%360+360)%360,e>180?e-360:e):0};function Xn(t,e,i){return Zn(e)?[t.x,t.y]:i([t.x,t.y])}const Qn=(t,e,i)=>{const s=Number(t),o=Number.isFinite(e)&&e>0?e:5;return Number.isFinite(s)&&s>0?s/o*i:0},tr=(t,e,i)=>{const s=Number(t),o=Number.isFinite(e)&&e>0?e:5;return Number.isFinite(s)&&i>0?s/i*o:0},er=(t,e,i,s=Yn.widthCm)=>{const o=Number(t?.width_cm);if(Number.isFinite(o)&&o>0)return o;const n=Number(t?.width);return Number.isFinite(n)&&n>0?tr(n,e,i):s};function ir(t,e){return{color:t.color,opacity:Jn(t.opacity),width_cm:Math.max(.1,Math.min(100,Number(t.widthCm)||.1)),...e?{fill:t.fill,fill_color:t.fillColor,fill_opacity:Jn(t.fillOpacity,.25)}:{}}}function sr(t){const e=t.x+t.w/2,i=t.y+t.h/2,s=Zn(t.angle)*Math.PI/180,o=Math.cos(s),n=Math.sin(s),r=(t,s)=>{const r=t-e,a=s-i;return[e+r*o-a*n,i+r*n+a*o]};return[r(t.x,t.y),r(t.x+t.w,t.y),r(t.x+t.w,t.y+t.h),r(t.x,t.y+t.h)]}function or(t){const e=sr(t),i=(t,e)=>[(t[0]+e[0])/2,(t[1]+e[1])/2],s=[(e[0][0]+e[2][0])/2,(e[0][1]+e[2][1])/2];return{points:[...e,i(e[0],e[1]),i(e[1],e[2]),i(e[2],e[3]),i(e[3],e[0]),s],segments:e.map((t,i)=>({a:t,b:e[(i+1)%4]}))}}function nr(t,e,i,s,o,n,r,a){const l=Zn(t.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l),d=-Math.sin(l),p=Math.cos(l),u=t.x+t.w/2,_=t.y+t.h/2,m=e>0?-t.w/2:t.w/2,g=i>0?-t.h/2:t.h/2,f=u+m*c+g*d,v=_+m*h+g*p,y=s-f,b=o-v;let w=(y*c+b*h)*(e>0?1:-1),k=(y*d+b*p)*(i>0?1:-1),$=null;if(n){const e=w/Math.max(t.w,a),i=k/Math.max(t.h,a);$=Math.max(a/Math.max(t.w,a),a/Math.max(t.h,a),e,i),w=t.w*$,k=t.h*$}if(r>0)if(n){const e=Math.max(1,Math.round(t.w/r)),i=Math.max(1,Math.round(t.h/r));if(Math.abs(t.w-e*r)<1e-6&&Math.abs(t.h-i*r)<1e-6){const s=(t,e)=>{let i=Math.abs(t),s=Math.abs(e);for(;s;)[i,s]=[s,i%s];return Math.max(1,i)},o=1/s(e,i),n=Math.max(a/t.w,a/t.h),r=Math.max(n,Math.round(($??1)/o)*o);w=t.w*r,k=t.h*r}else{t.w>=t.h?(w=Math.round(w/r)*r,k=w*(t.h/Math.max(t.w,a))):(k=Math.round(k/r)*r,w=k*(t.w/Math.max(t.h,a)))}}else w=Math.round(w/r)*r,k=Math.round(k/r)*r;w=Math.max(a,w),k=Math.max(a,k);return{x:f+(e>0?w/2:-w/2)*c+(i>0?k/2:-k/2)*d-w/2,y:v+(e>0?w/2:-w/2)*h+(i>0?k/2:-k/2)*p-k/2,w:w,h:k,angle:Zn(t.angle)||void 0}}const rr=(t,e,i)=>{const s=i[0]-e[0],o=i[1]-e[1],n=s*s+o*o;if(n<1e-12)return[...e];const r=Math.max(0,Math.min(1,((t[0]-e[0])*s+(t[1]-e[1])*o)/n));return[e[0]+s*r,e[1]+o*r]};const ar=15;function lr(t,e){return Number.isFinite(t)&&t>0&&Number.isFinite(e)&&e>0&&t*e<3}function cr(t){return Number.isFinite(t)?Math.max(1,Math.min(100,t)):1}function hr(t,e){return!Number.isFinite(t)||t<=0?"":String(e?Math.round(t/2.54*100)/100:Math.round(100*t)/100)}function dr(t,e,i){const s=Number(e)>0?Number(e):5;return cr(t)/s*i}function pr(t,e){return e>0&&Number.isFinite(t)?Math.round(t/e)*e:t}function ur(t,e){let i=e[0]-t[0],s=e[1]-t[1];const o=Math.hypot(i,s);return o<1e-12?[1,0]:(i/=o,s/=o,(i<-1e-12||Math.abs(i)<=1e-12&&s<0)&&(i=-i,s=-s),[i,s])}function _r(t,e,i){const s=pr((t[0]+e[0])/2,i),o=pr((t[1]+e[1])/2,i),[n,r]=ur(t,e);let a=Math.atan2(r,n);a<0&&(a+=Math.PI);const l=Math.round(1800*a)/1800,c=i>0&&i<.01?6:i<1?4:2;return`${s.toFixed(c)},${o.toFixed(c)}@${l.toFixed(4)}`}function mr(t,e,i,s){return 1===s?_r(t,e,i):_r([t[0]/s,t[1]/s],[e[0]/s,e[1]/s],i)}function gr(t,e){if(!Array.isArray(t.a)||!Array.isArray(t.b)||t.a.length<2||t.b.length<2)return null;const i=[Number(t.a[0]),Number(t.a[1]),Number(t.b[0]),Number(t.b[1])];if(!i.every(Number.isFinite))return null;const s=e>0?e:1;return[[i[0]*s,i[1]*s],[i[2]*s,i[3]*s]]}function fr(t,e,i,s,o){const n=o>0?o:1;return{key:mr(t,e,s,n),cm:cr(i),a:[t[0]/n,t[1]/n],b:[e[0]/n,e[1]/n]}}function vr(t,e){const i=e>0?e:1,s=[];for(const e of t){const t=e.key.lastIndexOf("@");if(t<0)continue;const[o,n]=e.key.slice(0,t).split(",").map(Number),r=Number(e.key.slice(t+1));[o,n,r].every(Number.isFinite)&&s.push({w:e,x:o*i,y:n*i,ang:r})}return s}function yr(t,e){const[i,s]=ur(t,e);let o=Math.atan2(s,i);return o<0&&(o+=Math.PI),o}function br(t,e){let i=Math.abs(t-e);return i>Math.PI/2&&(i=Math.PI-i),i<.02}function wr(t,e,i,s,o=1){if(!t?.length)return null;const n=mr(e,i,s,o),r=t.find(t=>t.key===n);if(r)return r;const a=o>0?o:1,l=(e[0]+i[0])/2,c=(e[1]+i[1])/2,h=yr(e,i),d=Math.max(.5*s,1e-9)*a;for(const e of vr(t,a))if(br(e.ang,h)&&Math.hypot(e.x-l,e.y-c)<=d)return e.w;return null}function kr(t,e,i,s,o=1){const n=wr(t,e,i,s,o);return n&&n.cm>0?cr(n.cm):0}function $r(t,e,i,s=1,o=[]){if(!t?.length)return[];const n=new Set,r=Xe(e);for(const t of r)n.add(mr([t[0],t[1]],[t[2],t[3]],i,s));const a=e||[],l=Math.max(i*s*.02,1e-9);for(let t=0;t(n.has(t.key)||(t=>{const e=gr(t,s);if(!e)return!1;const[i,n]=e,a=n[0]-i[0],c=n[1]-i[1],h=Math.hypot(a,c);if(h<=l)return!1;if(!r.some(t=>{const e=[t[0],t[1]],s=[t[2],t[3]];return br(yr(i,n),yr(e,s))&&Br(i[0],i[1],e[0],e[1],s[0],s[1])<=l&&Br(n[0],n[1],e[0],e[1],s[0],s[1])<=l}))return!1;const d=(o||[]).some(t=>{const e=[t[0],t[1]],s=[t[2],t[3]];if(!br(yr(i,n),yr(e,s)))return!1;const o=t=>Math.abs((t[0]-i[0])*c-(t[1]-i[1])*a)/h;if(o(e)>l||o(s)>l)return!1;const r=h*h,d=((e[0]-i[0])*a+(e[1]-i[1])*c)/r,p=((s[0]-i[0])*a+(s[1]-i[1])*c)/r;return Math.min(1,Math.max(d,p))-Math.max(0,Math.min(d,p))>l/h});return!d})(t))&&t.cm>=1&&t.cm<=100)}function xr(t,e,i,s=8){const[o,n]=ur(t,e);let r=Math.atan2(n,o);r<0&&(r+=Math.PI);let a=i*Math.PI/180%Math.PI;a<0&&(a+=Math.PI);let l=Math.abs(r-a);return l>Math.PI/2&&(l=Math.PI-l),l<=s*Math.PI/180}function Sr(t,e,i,s,o=1){if(!t?.length)return[];if(e.length!==i.length)return t.slice();const n=new Map;for(let t=0;t0?o:1,a=Math.max(.5*s,1e-9)*r,l=new Set,c=[];for(const o of t){let t="",h=null;const d=gr(o,r);if(d)for(let o=0;oa||Br(d[1][0],d[1][1],n[0],n[1],l[0],l[1])>a)continue;const u=l[0]-n[0],_=l[1]-n[1],m=u*u+_*_;if(m<1e-18)continue;const g=t=>{const e=Math.max(0,Math.min(1,((t[0]-n[0])*u+(t[1]-n[1])*_)/m));return[c[0]+(p[0]-c[0])*e,c[1]+(p[1]-c[1])*e]};h=[g(d[0]),g(d[1])],t=mr(h[0],h[1],s,r);break}if(d||(t=n.get(o.key)||""),!t){const n=vr([o],r)[0];if(n)for(let o=0;o1.000001)continue;if(Br(n.x,n.y,l[0],l[1],c[0],c[1])>a)continue;const g=h[0]+(d[0]-h[0])*Math.max(0,Math.min(1,m)),f=h[1]+(d[1]-h[1])*Math.max(0,Math.min(1,m)),[v,y]=ur(h,d),b=Math.max(s*r,1e-6);t=mr([g-v*b,f-y*b],[g+v*b,f+y*b],s,r);break}}t||(t=o.key),l.has(t)||(l.add(t),c.push(h?fr(h[0],h[1],o.cm,s,r):{...o,key:t,cm:cr(o.cm)}))}return c}function Mr(t,e,i,s,o,n=1){const r=mr(e,i,o,n),a=(t||[]).filter(t=>t.key!==r);return null==s||s<1?a:[...a,fr(e,i,s,o,n)]}function Cr(t,e,i,s,o,n=[],r=1){let a=t?t.slice():[];for(const t of function(t,e,i,s,o=1,n=[]){const r=Fr(t,e,i,s,o,n);if(!r)return[];const a=[];for(let t=0;t0)||!t||t.length<2)return"";if(i&&t.length>=3){let i=t;const s=t[t.length-1];if(t.length>=4&&Math.hypot(t[0][0]-s[0],t[0][1]-s[1])<1e-9&&(i=t.slice(0,-1)),i.length>=3){const t=i.map(()=>e),s=Kr(i,t),o=Ir(i,t);if(s&&o)return`${Er(s)} ${Er(Nr(o))}`}}let s="";for(let i=0;i.05)&&Br(r,a,t[0],t[1],t[2],t[3])<=n)return!0}return!1}function Pr(t,e){return Math.max(t*(e>0?e:1)*.04,1e-9)}function Rr(t,e){const i=t[e],s=t[(e+1)%t.length],o=s[0]-i[0],n=s[1]-i[1],r=Math.hypot(o,n)||1;let a=-n/r,l=o/r;const c=[(i[0]+s[0])/2,(i[1]+s[1])/2];return function(t,e){let i=!1;for(let s=0,o=e.length-1;st[1]!=l>t[1]&&t[0]<(a-n)*(t[1]-r)/(l-r+0)+n&&(i=!i)}return i}([c[0]+.001*a,c[1]+.001*l],t)||(a=-a,l=-l),[a,l]}function Ar(t,e){const i=t[0]*e[1]-t[1]*e[0],s=t[0]*e[0]+t[1]*e[1];return Math.abs(i)<1e-9&&s>0}function zr(t,e,i,s){const o=e[0]*s[1]-e[1]*s[0];if(Math.abs(o)<1e-12)return null;const n=[i[0]-t[0],i[1]-t[1]],r=(n[0]*s[1]-n[1]*s[0])/o;return[t[0]+r*e[0],t[1]+r*e[1]]}function Ir(t,e){const i=t?.length||0;if(i<3||e.length!==i)return null;if(e.every(t=>!(t>0)))return t.map(t=>[t[0],t[1]]);const s=[];for(let o=0;o0||d>0)){s.push([t[o][0],t[o][1]]);continue}if(Ar(b,w)){const e=t[o],i=[e[0]+p*h,e[1]+u*h],n=[e[0]+_*d,e[1]+m*d];s.push(i),Math.hypot(n[0]-i[0],n[1]-i[1])>1e-9&&s.push(n);continue}const x=zr(k,b,$,w),S=Math.max(h,d,1e-9);if(x){if(Math.hypot(x[0]-t[o][0],x[1]-t[o][1])<=4*S){s.push(x);continue}}h>0&&s.push([t[o][0]+p*h,t[o][1]+u*h]),d>0&&s.push([t[o][0]+_*d,t[o][1]+m*d]),h>0||d>0||s.push([t[o][0],t[o][1]])}return s.length>=3?s:null}function Er(t,e=!0){if(!t.length)return"";let i=`M ${t[0][0]} ${t[0][1]}`;for(let e=1;et?.id===e),a=Je(r);if(!a||a.length<3)return null;const l=Pr(s,o),c=[];for(const i of t||[]){if(!i||i.id===e)continue;const t=Je(i);if(t)for(const e of us(a,t,l))c.push([e[0],e[1]],[e[2],e[3]])}for(const t of i||[])c.push([t[0],t[1]],[t[2],t[3]]);for(const t of n||[]){const e=gr(t,o);e&&c.push(e[0],e[1])}const h=[],d=[];for(let t=0;tl)continue;const r=((t[0]-e[0])*(i[0]-e[0])+(t[1]-e[1])*(i[1]-e[1]))/(s*s);r<=o||r>=1-o||(n.some(t=>Math.abs(t-r)*s<=2*l)||n.push(r))}n.sort((t,e)=>t-e);for(const s of n)h.push([e[0]+(i[0]-e[0])*s,e[1]+(i[1]-e[1])*s]),d.push(t)}return{poly:h,parent:d,orig:a}}function Or(t,e,i,s){const o=e.poly.length,n=new Array(o).fill(0);if(!t?.length)return n;const r=new Set,a=[];for(let l=0;l0?(n[l]=cr(c.cm),r.add(c.key)):a.push(l)}if(!a.length)return n;const l=s>0?s:1,c=Math.max(.5*i,1e-9)*l,h=vr(t,l).filter(t=>t.w.cm>0),d=new Map;for(const t of a){const i=e.parent[t],s=d.get(i);s?s.push(t):d.set(i,[t])}for(const[t,i]of d){const s=e.orig[t],o=e.orig[(t+1)%e.orig.length],a=yr(s,o),d=(s[0]+o[0])/2,p=(s[1]+o[1])/2;let u=null;const _=Math.hypot(o[0]-s[0],o[1]-s[1]);for(const t of h){if(r.has(t.w.key))continue;if(!br(t.ang,a))continue;const e=gr(t.w,l);let i=!1,n=0;if(e){if(!br(yr(e[0],e[1]),a))continue;if(Br(s[0],s[1],e[0][0],e[0][1],e[1][0],e[1][1])>c||Br(o[0],o[1],e[0][0],e[0][1],e[1][0],e[1][1])>c)continue;i=!0,n=Math.max(0,Math.hypot(e[1][0]-e[0][0],e[1][1]-e[0][1])-_)}else{if(Br(t.x,t.y,s[0],s[1],o[0],o[1])>c)continue;n=Math.hypot(t.x-d,t.y-p)}(!u||i&&!u.exact||i===u.exact&&nt?.id===e),o=Je(s);if(!o)return[];const n=[];for(const s of t||[]){if(!s||s.id===e)continue;const t=Je(s);if(t)for(const e of us(o,t,i))n.push(e)}return n}(t,e,Pr(o,a)),h=function(t,e,i,s,o){const n=Pr(s,o),r=[];for(let a=0;aBr(h,d,t[0],t[1],t[2],t[3])<=n);r.push(p?"shared":"outer")}return r}(l.poly,c,s,o,a),d=Or(i,l,o,a),p=d.map((t,e)=>h[e]&&t>0?dr(t,n,r)/2:0);return{...l,kinds:h,cms:d,offsets:p}}function Hr(t,e,i,s,o,n,r=1){const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,o,n,r);if(c)for(let t=0;t0&&!l.has(c.key)&&(l.add(c.key),a.push(c));const c=[];for(const a of t||[]){if(!a?.id)continue;const l=Lr(t,a.id,e,i,s,o,n,r);if(l)for(let t=0;t0)||null===l.kinds[i]){t++;continue}let n=t;for(;n+10&&c.push({a:[h[0],h[1]],b:[d[0],d[1]],key:mr(h,d,s,r),cm:o,len:p}),t=n+1}}}c.sort((t,e)=>e.len-t.len||t.key.localeCompare(e.key));const h=[],d=new Set,p=new Set,u=4*Pr(s,r);for(const t of c){const e=a.filter(e=>!p.has(e.key)&&e.cm===t.cm&&br(yr(e.a,e.b),yr(t.a,t.b))&&Br(e.a[0],e.a[1],t.a[0],t.a[1],t.b[0],t.b[1])<=u&&Br(e.b[0],e.b[1],t.a[0],t.a[1],t.b[0],t.b[1])<=u);if(e.length){for(const t of e)p.add(t.key);d.has(t.key)||(d.add(t.key),h.push(fr(t.a,t.b,t.cm,s,r)))}}for(const t of a)p.has(t.key)||d.has(t.key)||(d.add(t.key),h.push(fr(t.a,t.b,t.cm,s,r)));return h}function Ur(t,e,i,s,o,n,r,a=1){const l=Pr(o,a),c=(s[0]+s[2])/2,h=(s[1]+s[3])/2,d=yr([s[0],s[1]],[s[2],s[3]]);let p=null;for(const s of Hr(t,e,i,o,n,r,a)){if(!br(yr(s.a,s.b),d))continue;const t=Br(c,h,s.a[0],s.a[1],s.b[0],s.b[1]);t>4*l||(!p||tt?.id===e),c=Je(l);if(!c||c.length<3)return null;if(!i?.length)return c.map(t=>[t[0],t[1]]);const h=Lr(t,e,i,s,o,n,r,a);return h&&h.offsets.some(t=>t>0)&&Ir(h.poly,h.offsets)||c.map(t=>[t[0],t[1]])}function Gr(t){const e=t.map(t=>[t[0],t[1]]);return e.push([t[0][0],t[0][1]]),[e]}function Wr(t,e,i,s=[],o,n,r,a=1,l=[]){if(!e?.length&&!l.length)return null;const c=[];let h=0;for(const s of t||[]){if(!s?.id)continue;const l=Lr(t,s.id,e,i,o,n,r,a);if(!l||l.poly.length<3||!l.offsets.some(t=>t>0))continue;for(const t of l.offsets)t>0&&(h=Math.max(h,2*t));const d=Kr(l.poly,l.offsets),p=Ir(l.poly,l.offsets);d&&c.push({outset:d,inset:p})}for(const t of l){const e=t.map(t=>t[0]),i=t.map(t=>t[1]);if(e.length){const s=Math.min(Math.max(...e)-Math.min(...e),Math.max(...i)-Math.min(...i)),o=Math.min(...t.map((e,i)=>{const s=t[(i+1)%t.length];return Math.hypot(s[0]-e[0],s[1]-e[1])}));h=Math.max(h,t.length>16?s:o)}}const d=function(t,e,i,s,o,n,r){if(!e?.length||!i?.length)return[];const a=4*Pr(s,r),l=new Map;for(const a of Hr(t,e,i,s,o,n,r))!a.open&&a.half>0&&!l.has(a.key)&&l.set(a.key,a);const c=[...l.values()];if(c.length<2)return[];const h=[];for(const t of i)for(const e of[[t[0],t[1]],[t[2],t[3]]])h.some(t=>Math.hypot(t[0]-e[0],t[1]-e[1])<=a)||h.push(e);const d=[],p=(t,e)=>{let i=0,s=0;if(Math.hypot(t.a[0]-e[0],t.a[1]-e[1])<=a)i=t.b[0]-t.a[0],s=t.b[1]-t.a[1];else{if(!(Math.hypot(t.b[0]-e[0],t.b[1]-e[1])<=a))return null;i=t.a[0]-t.b[0],s=t.a[1]-t.b[1]}const o=Math.hypot(i,s);return o>a?[i/o,s/o]:null};for(const t of h){const e=c.map(e=>({iv:e,u:p(e,t)})).filter(t=>!!t.u);for(let i=0;i4*_||d.push(r>0?[t.slice(),h,u,p]:[t.slice(),p,u,h])}}return d}(t,e,i,o,n,r,a),p=s.length?Jr(t,e,i,o,n,r,a):null;try{const t=t=>{const e=Gr(t.outset);return t.inset?Ge(e,Gr(t.inset)):e};let e=c.length?t(c[0]):null;for(let i=1;i0))continue;const i=Qr(p,t,!0);if(!i.negative&&!i.positive)continue;const s=t.angle*Math.PI/180,n=Math.cos(s),r=Math.sin(s),l=-r,c=n,d=t.length/2,u=1.25*Math.max(h,o*a),_=[[t.x-n*d-l*u,t.y-r*d-c*u],[t.x+n*d-l*u,t.y+r*d-c*u],[t.x+n*d+l*u,t.y+r*d+c*u],[t.x-n*d+l*u,t.y-r*d+c*u]];e&&(e=Ge(e,Gr(_)))}for(const t of l)t.length<3||(e=e?Be(e,Gr(t)):[Gr(t)]);return e?{geom:e,depthUnits:h}:null}catch{return null}}function Vr(t,e,i,s=[],o,n,r,a=1,l=[]){if(!e?.length&&!l.length)return null;const c=Wr(t,e,i,s,o,n,r,a,l),h=c?function(t){if(!t)return"";let e="";for(const i of t)if(Array.isArray(i))for(const t of i){if(!Array.isArray(t)||t.length<4)continue;const i=t.slice(0,t.length-1);i.length<3||(e+=(e?" ":"")+Er(i.map(t=>[t[0],t[1]])))}return e}(c.geom):"";if(c&&h)return{d:h,depthUnits:c.depthUnits,fillRule:"evenodd"};const d=function(t,e,i,s,o,n,r=1){if(!e?.length)return[];const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,o,n,r);if(!c||c.poly.length<3||!c.offsets.some(t=>t>0))continue;const h=Kr(c.poly,c.offsets),d=Ir(c.poly,c.offsets);if(!h||!d)continue;const p=`${Er(h)} ${Er(Nr(d))}`;let u="",_="outer",m=0,g=0;for(let t=0;t0){u=mr(c.poly[t],c.poly[(t+1)%c.poly.length],s,r),_=c.kinds[t]||"outer",m=c.cms[t],g=dr(m,o,n);break}a.push({d:p,key:u,kind:_,cm:m,depthUnits:g})}return a}(t,e,i,o,n,r,a),p=l.map(t=>Er(t)).join(" ");if(!d.length&&!p)return null;let u=c?.depthUnits||0;for(const t of d)u=Math.max(u,t.depthUnits);return{d:[d.map(t=>t.d).join(" "),p].filter(Boolean).join(" "),depthUnits:u,fillRule:"nonzero"}}function Kr(t,e){const i=t?.length||0;if(i<3||e.length!==i)return null;if(e.every(t=>!(t>0)))return t.map(t=>[t[0],t[1]]);Nr(t),e.slice().reverse();const s=[];for(let o=0;o0||a>0)){s.push([t[o][0],t[o][1]]);continue}if(Ar(b,w)){const e=t[o],i=[e[0]-l*r,e[1]-c*r],n=[e[0]-h*a,e[1]-d*a];s.push(i),Math.hypot(n[0]-i[0],n[1]-i[1])>1e-9&&s.push(n);continue}const x=zr(k,b,$,w),S=Math.max(r,a,1e-9);if(x){if(Math.hypot(x[0]-t[o][0],x[1]-t[o][1])<=4*S){s.push(x);continue}}r>0&&s.push([t[o][0]-l*r,t[o][1]-c*r]),a>0&&s.push([t[o][0]-h*a,t[o][1]-d*a])}return s.length>=3?s:null}function Yr(t,e,i,s,o,n,r=1){const a=[];for(const l of t||[]){const c=Je(l);if(c&&c.length>=3){const h=Lr(t,l.id,e,i,s,o,n,r),d=h&&h.offsets.some(t=>t>0)?Kr(h.poly,h.offsets):null,p=d||c;a.push({poly:p.map(t=>t.join(",")).join(" ")})}else l&&null!=l.x&&null!=l.y&&null!=l.w&&null!=l.h&&a.push({rect:{x:l.x,y:l.y,w:l.w,h:l.h,rx:.03*Math.min(l.w,l.h)}})}return a}function Jr(t,e,i,s,o,n,r=1){const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,o,n,r);if(!c)continue;const h=Math.abs(mi(c.poly));for(let t=0;t[Math.max(e,t.x0),Math.min(i,t.x1)]).filter(t=>t[1]-t[0]>s).sort((t,e)=>t[0]-e[0]||t[1]-e[1]);if(!o.length)return{coverage:0,full:!1};let n=o[0][0],r=o[0][1],a=0,l=n<=e+s;for(let t=1;t=i-s,{coverage:a,full:l}}function Xr(t,e){return Number(e.full)-Number(t.full)||t.faceDistance-e.faceDistance||t.area-e.area||t.roomId.localeCompare(e.roomId)}function Qr(t,e,i=!1){const s=Number(e?.x),o=Number(e?.y),n=Number(e?.angle),r=Number(e?.length);if(!([s,o,n,r].every(Number.isFinite)&&r>0))return{negative:null,positive:null};const a=n*Math.PI/180,l=Math.cos(a),c=Math.sin(a),h=-c,d=l,p=r/2,u=Math.max(1e-9,t.adjacencyEps),_=new Map;let m=0;for(const e of t.edges){if(i&&!(e.half>0))continue;if(!xr(e.a,e.b,n))continue;const[t,r]=ur(e.a,e.b);if(Math.abs((s-e.a[0])*r-(o-e.a[1])*t)>u)continue;const a=(e.a[0]-s)*l+(e.a[1]-o)*c,g=(e.b[0]-s)*l+(e.b[1]-o)*c,f=Math.max(-p,Math.min(a,g)),v=Math.min(p,Math.max(a,g));if(v-f<=u)continue;const y=e.inward[0]*h+e.inward[1]*d>=0?1:-1,b=((e.a[0]+e.b[0])/2-s)*h+((e.a[1]+e.b[1])/2-o)*d,w=Math.abs(b+y*e.half),k=`${y}|${e.roomId}`,$={x0:f,x1:v,half:e.half,cm:e.cm,key:e.key,axis:[t,r]},x=_.get(k);x?(x.pieces.push($),x.faceDistance=Math.min(x.faceDistance,w)):_.set(k,{roomId:e.roomId,side:y,order:m++,pieces:[$],faceDistance:w,area:e.area,coverage:0,full:!1})}for(const t of _.values()){const e=Zr(t.pieces,-p,p,u);t.coverage=e.coverage,t.full=e.full}const g=t=>{const e=[..._.values()].filter(e=>e.side===t&&e.coverage>u);return e.sort(Xr),e[0]||null};return{negative:g(-1),positive:g(1)}}function ta(t,e){const i=Qr(t,e),s=[i.negative,i.positive].filter(t=>!!t).sort((t,e)=>t.order-e.order);if(!s.length)return{ox:0,oy:0,cm:0,side:-1};const o=s[0],n=e.flip_v?-o.side:o.side,r=(-1===n?i.negative:i.positive)||o,a=[...r.pieces].sort((t,e)=>(t.x0<=0&&t.x1>=0?0:Math.min(Math.abs(t.x0),Math.abs(t.x1)))-(e.x0<=0&&e.x1>=0?0:Math.min(Math.abs(e.x0),Math.abs(e.x1)))||e.x1-e.x0-(t.x1-t.x0)||t.key.localeCompare(e.key))[0];if(!(a.half>0&&a.cm>0))return{ox:0,oy:0,cm:0,side:n};const l=e.angle*Math.PI/180,c=-Math.sin(l),h=Math.cos(l);return{ox:c*n*a.half,oy:h*n*a.half,cm:a.cm,side:n}}function ea(t,e){const i=1e-9,s=[];for(const t of[...e].filter(t=>t.x1>t.x0&&t.half>0).sort((t,e)=>t.x0-e.x0||t.x1-e.x1||t.half-e.half)){const e=s[s.length-1];e&&t.x0<=e.x1+i&&Math.abs(t.half-e.half)<=i?e.x1=Math.max(e.x1,t.x1):s.push({...t})}const o=[];for(let e=0;e=o-l?i.push([s,o]):(t>s+l&&i.push([s,Math.min(o,t)]),et[0]-e[0]||t[1]-e[1]),b=[];for(const t of y){const e=b[b.length-1];e&&t[0]<=e[1]+l?e[1]=Math.max(e[1],t[1]):b.push([t[0],t[1]])}s.set(g,b)}return a}function sa(t,e){const i=new Map;return e.map(e=>function(t,e,i){const s=Qr(t,e,!0),o=s.negative,n=s.positive;if(!o&&!n)return null;let r;if(o&&n)r=[{candidate:o,side:-1},{candidate:n,side:1}];else{const t=o||n;r=[{candidate:t,side:-1},{candidate:t,side:1}]}const a=r.map(({candidate:t,side:s})=>({candidate:t,side:s,pieces:ia(e,s,t.pieces,i)})),l=a.map(({candidate:t,side:e,pieces:i})=>({side:e,roomId:t.roomId,d:ea(e,i)})),c=a.flatMap(({pieces:t})=>t);if(!c.length)return null;const h=Math.max(...c.map(t=>t.half));return{faces:l,minY:-h,maxY:h,wallKey:[...new Set(c.map(t=>t.key))].sort().join("|")}}(t,e,i))}const oa=150;function na(t){return Number.isFinite(t)?Math.max(1,Math.min(oa,t)):1}function ra(t){return((Number.isFinite(Number(t))?Number(t):0)%90+90)%90}const aa=t=>{const e=t.map(t=>[t[0],t[1]]);return!e.length||e[0][0]===e[e.length-1][0]&&e[0][1]===e[e.length-1][1]||e.push([...e[0]]),[e]};function la(t){const e=[];for(const i of t||[])for(const t of i||[]){const i=(t||[]).filter(t=>Array.isArray(t)&&t.length>=2);i.length<4||e.push(`M ${i.slice(0,-1).map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`)}return e.join(" ")}function ca(t,e,i,s,o){const n=e[0]-t[0],r=e[1]-t[1],a=Math.hypot(n,r);if(!(a>1e-9))return null;const l=dr(i,s,o)/2,c=-r/a*l,h=n/a*l;return[[t[0]+c,t[1]+h],[e[0]+c,e[1]+h],[e[0]-c,e[1]-h],[t[0]-c,t[1]-h]]}function ha(t,e,i){const s=Number(e)>0?Number(e):5,o=na(t.cm)/s*i,n=t.center[0],r=t.center[1];if("circle"===t.shape){const t=o/2;return Array.from({length:96},(e,i)=>{const s=i/96*Math.PI*2;return[n+Math.cos(s)*t,r+Math.sin(s)*t]})}const a=o/2,l=ra(t.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l);return[[-a,-a],[a,-a],[a,a],[-a,a]].map(([t,e])=>[n+t*c-e*h,r+t*h+e*c])}function da(t,e,i){const s=[];for(let o=0;o+1t.length>=3).map(t=>aa(t));return e.length?Be(e[0],...e.slice(1)):null}catch{return null}}const ua=t=>`M ${t.map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`;function _a(t){const e=[];for(const i of t||[]){const t=[];for(const e of i||[]){const i=(e||[]).filter(t=>Array.isArray(t)&&t.length>=2);i.length<4||t.push(ua(i.slice(0,-1)))}t.length&&e.push(t.join(" "))}return e}function ma(t,e){const i=pa(t.filter(t=>t.length>=3)),s=pa(e.filter(t=>t.length>=3));if(!i)return[];if(!s)return _a(i);try{return _a(je(i,s))}catch{return _a(i)}}function ga(t,e){if(!e.length)return[aa(t)];try{const i=pa(e);if(i)return Ge(aa(t),i)}catch{}let i=[aa(t)];for(const t of e)if(!(t.length<3))try{i=Ge(i,aa(t))}catch{}return i}function fa(t){let e=0;for(const i of t||[])if(i?.length){e+=mi(i[0]||[]);for(let t=1;t=4&&e.push(t.slice(0,-1).map(t=>[t[0],t[1]]))}return e}function ya(t,e){let i=!1;for(let s=0,o=e.length-1;st[1]!=l>t[1]&&t[0]<(a-n)*(t[1]-r)/(l-r||1e-12)+n&&(i=!i)}return i}function ba(t,e,i){if(Math.hypot(t.center[0]-e.center[0],t.center[1]-e.center[1])>i)return!1;if(Math.abs(na(t.cm)-na(e.cm))>1e-6)return!1;if(t.shape!==e.shape)return!0;if("circle"===t.shape||"circle"===e.shape)return!0;const s=Math.abs(ra(t.angle)-ra(e.angle));return Math.min(s,90-s)<=1e-6}const wa=1e3;function ka(t,e){const i=Number(t),s=Number.isFinite(i)&&i>0?i:1,o=s>=1?e:e*s,n=s>=1?e/s:e;return{x:(e-o)/2,y:(e-n)/2,w:o,h:n}}const $a=.01,xa=100;function Sa(t,e=1e3){const i=ka(t?.plan_aspect,e),s=Number(t?.plan_scale),o=Number.isFinite(s)&&s>0?Math.min(xa,Math.max($a,s)):1,n=Number(t?.plan_scale_x),r=Number(t?.plan_scale_y),a=Number.isFinite(n)&&n>0?Math.min(xa,Math.max($a,n)):o,l=Number.isFinite(r)&&r>0?Math.min(xa,Math.max($a,r)):o,c=Number(t?.plan_x),h=Number(t?.plan_y),d=Zn(t?.plan_angle);return{x:i.x+(Number.isFinite(c)?Na(c):0)*e,y:i.y+(Number.isFinite(h)?Na(h):0)*e,w:i.w*a,h:i.h*l,...d?{angle:d}:{}}}function Ma(t){if(null==t.x||null==t.y)return{x:t.x,y:t.y,w:t.w,h:t.h};const e=Number(t.w)||0,i=Number(t.h)||0;return{x:e<0?t.x+e:t.x,y:i<0?t.y+i:t.y,w:Math.abs(e),h:Math.abs(i)}}function Ca(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=wa,i=function(t){return Array.isArray(t)&&4===t.length&&t.every(t=>Number.isFinite(t))&&t[2]>1e-6&&t[3]>1e-6?t:[0,0,1,1]}(t.view_box);return{id:t.id,title:t.title,vb:[i[0]*wa,i[1]*e,i[2]*wa,i[3]*e],bg:t.plan_url?{href:ys(t.plan_url),...Sa(t,wa)}:null,rooms:(t.rooms||[]).map(t=>{const i={...t,...Ma(t)};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*wa:void 0,y:null!=i.y?i.y*e:void 0,w:null!=i.w?i.w*wa:void 0,h:null!=i.h?i.h*e:void 0,poly:i.poly?i.poly.map(t=>[t[0]*wa,t[1]*e]):void 0}}),room_drafts:(t.room_drafts||[]).map(t=>({id:t.id,points:(t.points||[]).map(t=>[t[0]*wa,t[1]*e]),segments:(t.segments||[]).map(t=>({cm:Number(t.cm)}))})),partitions:(t.partitions||[]).map(t=>({id:t.id,a:[t.a[0]*wa,t.a[1]*e],b:[t.b[0]*wa,t.b[1]*e],cm:Number(t.cm)})),wall_columns:(t.wall_columns||[]).map(t=>({id:t.id,shape:"circle"===t.shape?"circle":"square",center:[t.center[0]*wa,t.center[1]*e],cm:Number(t.cm),..."circle"===t.shape?{}:{angle:ra(t.angle)}}))}}):[]}const Da=5e3,Ta=Da*wa,Pa=240,Ra=wa/Pa,Aa=1/Pa;function za(t){if(!Number.isFinite(t))return t;const e=Math.round(t*Pa/wa)*wa/Pa;return Math.abs(e-t)<=1e-9*Ra?t:e}function Ia(t){return{x:za(t.x),y:za(t.y)}}function Ea(t){return Number.isFinite(t)?Math.min(Ta,Math.max(-Ta,t)):0}function Na(t){return Number.isFinite(t)?Math.min(Da,Math.max(-Da,t)):0}const Fa=1/3,Oa=200;function La(t){let e=1/0,i=1/0,s=-1/0,o=-1/0;for(const n of t){const t=Number(n[0]),r=Number(n[1]);Number.isFinite(t)&&Number.isFinite(r)&&(ts&&(s=t),r>o&&(o=r))}return e>s?null:{minX:e,minY:i,maxX:s,maxY:o}}function Ha(t){return t.poly&&t.poly.length?La(t.poly):null==t.x||null==t.y?null:La([[t.x,t.y],[t.x+(t.w||0),t.y+(t.h||0)]])}function qa(t,e){const i=[];for(const e of t.rooms||[]){const t=Ha(e);t&&i.push(t)}if(t.bg){const e=La(sr(t.bg));e&&i.push(e)}for(const t of e||[])if(Array.isArray(t)){const e=La([t]);e&&i.push(e)}else i.push(t);return i}const Ua=t=>{if(!t.length)return 0;const e=[...t].sort((t,e)=>t-e),i=e.length>>1;return e.length%2?e[i]:(e[i-1]+e[i])/2},Ba=t=>{let e=1/0,i=1/0,s=-1/0,o=-1/0;for(const n of t)n.minXs&&(s=n.maxX),n.maxY>o&&(o=n.maxY);return e>s||i>o?null:{x:e,y:i,w:s-e,h:o-i}};function ja(t,e){let{x:i,y:s,w:o,h:n}=t;o<30&&(i=i+o/2-100,o=Oa),n<30&&(s=s+n/2-100,n=Oa);const r=Math.max(o,n)*e;return{x:i-r,y:s-r,w:o+2*r,h:n+2*r}}function Ga(t,e={}){const i=e.pad??.05,s=e.k??10,o=e.minSpread??50,n=t.filter(t=>Number.isFinite(t.minX)&&Number.isFinite(t.minY)&&Number.isFinite(t.maxX)&&Number.isFinite(t.maxY)&&Math.abs(t.minX)<=Ta&&Math.abs(t.maxX)<=Ta&&Math.abs(t.minY)<=Ta&&Math.abs(t.maxY)<=Ta);if(!n.length)return{core:null,all:null,outliers:0};const r=Ba(n);if(n.length<4){const t=ja(r,i);return{core:t,all:t,outliers:0}}const a=n.map(t=>(t.minX+t.maxX)/2),l=n.map(t=>(t.minY+t.maxY)/2),c=Ua(a),h=Ua(l),d=n.map((t,e)=>Math.max(Math.abs(a[e]-c),Math.abs(l[e]-h))),p=Math.max(((t,e)=>t.length?t[Math.min(t.length-1,Math.max(0,Math.round(e*(t.length-1))))]:0)([...d].sort((t,e)=>t-e),.75),o),u=d.map(t=>t>s*p),_=u.filter(Boolean).length,m=_&&_<=n.length*Fa?n.filter((t,e)=>!u[e]):n;return{core:ja(Ba(m)||r,i),all:ja(r,i),outliers:m===n?0:_}}function Wa(t,e,i=.05){const s=Ga(qa(t,e),{pad:i});if(s.core)return s.core;const o=t.vb&&4===t.vb.length&&t.vb[2]>0&&t.vb[3]>0?t.vb:[0,0,wa,wa];return{x:o[0],y:o[1],w:o[2],h:o[3]}}function Va(t){const e=Wa(t);return{x:e.x+e.w/2,y:e.y+e.h/2}}function Ka(t){const e=[];for(const i of t.rooms||[]){const t=Ha(i);t&&e.push(t)}const i=Ga(e,{pad:0}).core;return i?Math.max(wa,Math.min(Ta,Math.max(i.w,i.h))):wa}function Ya(t,e,i,s=1){const o=Number(i),n=Number.isFinite(s)&&s>0?s:1;return!Number.isFinite(o)||o<=0?t*n:t*Ka(e)*n/o}const Ja=[1,2,5,10,20,50,100,200,500,1e3];function Za(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Xa(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}const Qa=["furniture","appliance","sanitary","other"],tl=[{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]]}],el=new Map(tl.map(t=>[t.id,t]));function il(t){return t&&el.get(t)||null}function sl(t){return tl.filter(e=>e.group===t)}function ol(t,e,i=Ra,s=1e3){const o=Number(e)>0?Number(e):5;return(Number(t)||0)/o*i/s}const nl=5e-4,rl=Da;function al(t){return Number.isFinite(t)?Math.max(nl,Math.min(rl,t)):nl}const ll=t=>{const e=Math.round(1e3*t)/1e3;return Object.is(e,-0)?"0":String(e)};function cl(t,e,i){const s=il(t);if(!(s&&e>0&&i>0))return"";const o=t=>ll(t*e),n=t=>ll(t*i),r=[];for(const t of s.g)if("r"===t[0]){const[,e,i,s,a]=t;r.push(`M${o(e)} ${n(i)}H${o(e+s)}V${n(i+a)}H${o(e)}Z`)}else if("l"===t[0]){const[,e,i,s,a]=t;r.push(`M${o(e)} ${n(i)}L${o(s)} ${n(a)}`)}else if("e"===t[0]){const[,s,a,l,c]=t;r.push(`M${o(s-l)} ${n(a)}A${ll(l*e)} ${ll(c*i)} 0 0 1 ${o(s+l)} ${n(a)}A${ll(l*e)} ${ll(c*i)} 0 0 1 ${o(s-l)} ${n(a)}Z`)}else{const e=t.slice(1);if(e.length<4)continue;let i=`M${o(e[0])} ${n(e[1])}`;for(let t=2;t+1{let e=(t%360+360)%360;return e>180&&(e-=360),e};function dl(t,e,i,s,o,n=0){let r=null,a=o;for(const o of s){const[s,l,c,h]=o,d=c-s,p=h-l,u=d*d+p*p;if(!u)continue;const _=Math.sqrt(u);let m=((t-s)*d+(e-l)*p)/u;m=Math.max(0,Math.min(1,m));let g=s+m*d,f=l+m*p;const v=Math.hypot(t-g,e-f);if(!(v0){const t=Math.round(m*_/n)*n;g=s+t/_*d,f=l+t/_*p}r={cx:g+y*(i/2),cy:f+b*(i/2),angle:hl(180*Math.atan2(-y,b)/Math.PI),dist:v}}return r}function pl(t,e,i){const s=i;return{a:[t[0]/s,t[1]/s],b:[e[0]/s,e[1]/s]}}function ul(t,e){const i=e;return[t.a[0]*i,t.a[1]*i,t.b[0]*i,t.b[1]*i]}function _l(t){return Array.isArray(t)&&t.length>=2&&Number.isFinite(Number(t[0]))&&Number.isFinite(Number(t[1]))}function ml(t){if(!Array.isArray(t))return[];const e=[];for(const i of t){if(!i||"object"!=typeof i)continue;const t=i;if(!_l(t.a)||!_l(t.b))continue;const s=[Number(t.a[0]),Number(t.a[1])],o=[Number(t.b[0]),Number(t.b[1])];Math.hypot(o[0]-s[0],o[1]-s[1])<.001||e.push({a:s,b:o})}return e}function gl(t,e){const i=e[0],s=e[1],o=e[2]-i,n=e[3]-s,r=o*o+n*n;if(r<1e-18){return{q:[i,s],t:0,d:Math.hypot(t[0]-i,t[1]-s)}}let a=((t[0]-i)*o+(t[1]-s)*n)/r;a=Math.max(0,Math.min(1,a));const l=[i+a*o,s+a*n];return{q:l,t:a,d:Math.hypot(t[0]-l[0],t[1]-l[1])}}function fl(t,e){return gl(t,e).q}function vl(t,e,i,s,o){const n=gl(t,e).q;let r=n,a=1/0;for(const t of i){const e=Math.hypot(n[0]-t[0],n[1]-t[1]);e<=o&&e0?s:1,m=Math.round(u/_)*_,g=Math.max(0,Math.min(p,m))/p;return[l+h*g,c+d*g]}function yl(t,e){const i=[],s=(t||[]).filter(t=>t?.id);for(let t=0;tt?.id),r=ml(e);if(r.length)return Dl(r,t,i,s).map(t=>ul(t,i));if(!o)return[];const a=[],l=(t,e)=>(t.open_to||[]).includes(e.id)||(e.open_to||[]).includes(t.id);for(let t=0;tpl([t[0],t[1]],[t[2],t[3]],e))}function kl(t,e,i,s){for(const e of t||[])e.open_to&&delete e.open_to;if(!i.length)return;const o=(e||[]).filter(t=>t?.id);new Map(o.map(t=>[t.id,t]));const n=new Map((t||[]).filter(t=>t?.id).map(t=>[t.id,t])),r=(t,e)=>{const i=n.get(t),s=n.get(e);i&&s&&((i.open_to||[]).includes(e)||(i.open_to=[...i.open_to||[],e]),(s.open_to||[]).includes(t)||(s.open_to=[...s.open_to||[],t]))};for(let t=0;tSs(i,t)<4*s)){r(o[t].id,o[e].id);break}}}for(const e of t||[])e.open_to&&!e.open_to.length&&delete e.open_to}function $l(t,e,i,s=0){const o=e[2]-e[0],n=e[3]-e[1],r=Math.hypot(o,n);if(!(r>1e-9))return null;const a=o/r,l=n/r,c=(t[0]-e[0])*a+(t[1]-e[1])*l;if(c<-s||c>r+s)return null;return Math.abs((t[0]-e[0])*l-(t[1]-e[1])*a)>i?null:Ss(t,e)}function xl(t,e,i){const s=t[2]-t[0],o=t[3]-t[1],n=e[2]-e[0],r=e[3]-e[1],a=Math.hypot(s,o),l=Math.hypot(n,r);return a>1e-9&&l>1e-9&&(!(Math.abs(s/a*(r/l)-o/a*(n/l))>1e-6)&&(Ss([e[0],e[1]],t)<=4*i||Ss([t[0],t[1]],e)<=4*i))}function Sl(t,e,i){if(!t.length)return null;t.sort((t,e)=>t.distance-e.distance);const s=t[0];for(let o=1;oe)break;if(n.semantic!==s.semantic||!xl(s.seg,n.seg,i))return"ambiguous"}return s}function Ml(t,e,i,s,o=1,n=15){const r=function(t,e,i,s,o=1,n=15){const[r,a]=ur([e[0],e[1]],[e[2],e[3]]);let l=0,c=1/0;const h=[(e[0]+e[2])/2,(e[1]+e[3])/2];for(const e of i){const[i,n]=ur([e[0],e[1]],[e[2],e[3]]);if(Math.abs(r*n-a*i)>.05)continue;const d=kr(t,[e[0],e[1]],[e[2],e[3]],s,o);if(!(d>0))continue;const p=[(e[0]+e[2])/2,(e[1]+e[3])/2],u=Math.hypot(p[0]-h[0],p[1]-h[1]);u0?l:n}(t,e,i,s,o,n);return Mr(t,[e[0],e[1]],[e[2],e[3]],r,s,o)}function Cl(t,e,i){const s=[[t[0],t[1]],[t[2],t[3]]],o=[(t[0]+t[2])/2,(t[1]+t[3])/2];for(const n of e)if(!(Ss(o,n)>Math.hypot(t[2]-t[0],t[3]-t[1])&&Ss([n[0],n[1]],t)>i))for(const e of[[n[0],n[1]],[n[2],n[3]]])Ss(e,t)<=2*i&&s.push(e);return s}function Dl(t,e,i,s){const o=ml(t);if(!o.length)return[];const n=yl(e,s);if(!n.length)return[];const r=[],a=Math.max(4*s,1e-6);for(const t of o){const e=ul(t,i),o=e[0],l=e[1],c=e[2]-o,h=e[3]-l,d=Math.hypot(c,h);if(d4*s||n>4*s)continue;const r=(t[0]-o)*p+(t[1]-l)*u,c=(t[2]-o)*p+(t[3]-l)*u,h=Math.max(0,Math.min(r,c)),m=Math.min(d,Math.max(r,c));if(m-ht.lo-e.lo||t.hi-e.hi);const i=[];for(const t of e){const e=i[i.length-1];e&&t.lo<=e.hi+a?e.hi=Math.max(e.hi,t.hi):i.push({...t})}for(const e of i){const i=[o+p*e.lo,l+u*e.lo],s=[o+p*e.hi,l+u*e.hi];Math.hypot(s[0]-i[0],s[1]-i[1])i.pair===t&&Math.abs(i.ux*r-i.uy*n)<=1e-6&&Math.abs((e[0]-i.origin[0])*i.uy-(e[1]-i.origin[1])*i.ux)<=c&&Math.abs((e[2]-i.origin[0])*i.uy-(e[3]-i.origin[1])*i.ux)<=c);h||(h={pair:t,origin:[e[0],e[1]],ux:n,uy:r,ranges:[]},l.push(h));const d=(e[0]-h.origin[0])*h.ux+(e[1]-h.origin[1])*h.uy,p=(e[2]-h.origin[0])*h.ux+(e[3]-h.origin[1])*h.uy;h.ranges.push({lo:Math.min(d,p),hi:Math.max(d,p)})}const h=[];for(const t of l){t.ranges.sort((t,e)=>t.lo-e.lo||t.hi-e.hi);const e=[];for(const i of t.ranges){const t=e[e.length-1];t&&i.lo<=t.hi+a?t.hi=Math.max(t.hi,i.hi):e.push({...i})}for(const s of e){const e=[t.origin[0]+t.ux*s.lo,t.origin[1]+t.uy*s.lo],o=[t.origin[0]+t.ux*s.hi,t.origin[1]+t.uy*s.hi];Math.hypot(o[0]-e[0],o[1]-e[1])>=a&&h.push(pl(e,o,i))}}return h}function Tl(t,e,i,s){const o=ml(t);if(!o.length)return[];if(e.length!==i.length)return o;const n=[];for(const t of o){const o=ul(t,s),r=[o[0],o[1]],a=[o[2],o[3]];let l=r,c=a;for(let t=0;t{const e=gl(t,[s[0],s[1],o[0],o[1]]);if(e.d>.001)return t;const i=h[0]-n[0],r=h[1]-n[1];return[n[0]+i*e.t,n[1]+r*e.t]};if(gl([(r[0]+a[0])/2,(r[1]+a[1])/2],[s[0],s[1],o[0],o[1]]).d<.01){l=d(r),c=d(a);break}}n.push(pl(l,c,s))}return n}class Pl{constructor(t,e=()=>Date.now()){this.onUpdate=t,this.now=e,this.signed={},this.queued=new Set,this.inFlight=new Map,this.retry=new Map,this.disposed=!1}start(t,e){this.disposed=!1,this.stopTimer(),this.resignTimer=setInterval(()=>this.resign(t(),e()),288e5)}dispose(){this.disposed=!0,this.stopTimer(),clearTimeout(this.batchTimer),this.queued.clear(),this.inFlight.clear()}stopTimer(){void 0!==this.resignTimer&&clearInterval(this.resignTimer),this.resignTimer=void 0}display(t,e){const i=ys(e);if(!i.startsWith("/api/houseplan/content/"))return i;const s=this.signed[i],o=s?this.now()-s.at:1/0;return o{const e=[...this.queued];this.queued.clear(),this.sign(t,e)},30))}sign(t,e){if(e.length&&t?.callWS)for(const i of function(t,e){const i=Math.max(1,Math.floor(e)),s=[];for(let e=0;e{if(this.disposed)return;const e=this.now(),s={...this.signed};let o=0;for(const n of i){const i=t?.urls?.[n];"string"==typeof i&&i?(s[n]={url:i,at:e},this.retry.delete(n),o++):this.backOff(n)}o&&(this.signed=s,this.onUpdate())}).catch(()=>{for(const t of i)this.backOff(t)}).finally(()=>{for(const t of i)this.inFlight.get(t)===e&&this.inFlight.delete(t)})}}backOff(t){const e=this.retry.get(t)?.delay||0,i=Math.min(6e4,e?2*e:2e3);this.retry.set(t,{notBefore:this.now()+i,delay:i})}resign(t,e){const i=this.now(),s={};for(const[t,o]of Object.entries(this.signed))e.has(t)&&i-o.at{if(null==t||""===t)return null;const e=Number(t);return Number.isFinite(e)?e:null},Il=t=>{const e=t,i=zl(Array.isArray(e)?e[0]:e?.x),s=zl(Array.isArray(e)?e[1]:e?.y);return null==i||null==s?null:[i,s]};function El(t){if(!Array.isArray(t))return null;const e=[];for(const i of t){const t=Il(i);if(!t)return null;e.push(t)}if(e.length>1){const t=e[0],i=e[e.length-1];t[0]===i[0]&&t[1]===i[1]&&e.pop()}if(e.length<3)return null;let i=0,s=0,o=0;for(let t=0;tt+e[0],0)/e.length,e.reduce((t,e)=>t+e[1],0)/e.length]:[s/(3*i),o/(3*i)]}function Nl(t){if(!Array.isArray(t))return null;const e=[];for(const i of t){const t=Il(i);if(!t)return null;e.push(t)}if(e.length>1){const t=e[0],i=e[e.length-1];t[0]===i[0]&&t[1]===i[1]&&e.pop()}const i=El(e);if(!i)return null;const s=e.map(t=>t[0]),o=e.map(t=>t[1]);return{centroid:i,bbox:[Math.min(...s),Math.min(...o),Math.max(...s),Math.max(...o)]}}function Fl(t){let e=t;if(e&&"object"==typeof e&&!Array.isArray(e)){const t=e;e=t.path??t.points??e}if(!Array.isArray(e)||!e.length)return[];let i=(e.some(t=>(t=>Array.isArray(t)?t.length>=2&&!Array.isArray(t[0])&&("object"!=typeof t[0]||null==t[0]):!!t&&"object"==typeof t&&("x"in t||"y"in t))(t))?[e]:e.filter(Array.isArray)).map(t=>t.map(Il).filter(t=>null!=t)).filter(t=>t.length>=2);i.length>64&&(i=i.slice(-64));if(i.reduce((t,e)=>t+e.length,0)<=4e3)return i.map(t=>t.slice());const s=4e3-2*i.length,o=i.map(t=>t.length-2),n=o.reduce((t,e)=>t+e,0),r=o.map(t=>s*t/n),a=r.map(Math.floor);let l=s-a.reduce((t,e)=>t+e,0);const c=r.map((t,e)=>({index:e,fraction:t-Math.floor(t)})).sort((t,e)=>e.fraction-t.fraction||t.index-e.index);for(let t=0;tfunction(t,e){if(e>=t.length)return t.slice();if(e<=2)return[t[0],t[t.length-1]];const i=[];for(let s=0;st.slice()),i=e[e.length-1];return i?(i.length>2?i.pop():e.pop(),e):[]}function Hl(t){if(!t)return null;const e=t.vacuum_position||t.robot_position||null,i=e&&null!=zl(e.x)&&null!=zl(e.y)?{x:zl(e.x),y:zl(e.y),a:zl(e.a??e.angle??e.theta)}:null,s=Fl(t.path?.path??t.path?.points??t.path),o=[],n=t.rooms,r=Array.isArray(n)?n.map((t,e)=>[String(t?.id??e),t]):n&&"object"==typeof n?Object.entries(n):[];for(const[t,e]of r){if(!e||"object"!=typeof e)continue;const i=String(e.name??e.label??"").trim(),s=[[zl(e.cx),zl(e.cy)],[zl(e.center?.x),zl(e.center?.y)],[zl(e.x),zl(e.y)]].find(([t,e])=>null!=t&&null!=e);let n=s?.[0]??null,r=s?.[1]??null;const a=Nl(e.outline),l=zl(e.x0),c=zl(e.y0),h=zl(e.x1),d=zl(e.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)||a?.bbox||null;if(null!=n&&null!=r||!a||([n,r]=a.centroid),null!=n&&null!=r||!p||(n=(p[0]+p[2])/2,r=(p[1]+p[3])/2),i&&null!=n&&null!=r){const e={id:t,name:i,cx:n,cy:r};p&&([e.x0,e.y0,e.x1,e.y1]=p),o.push(e)}}const a=function(t){return String(t.map_name??t.current_map??t.map_index??t.selected_map??"default")}(t);return i||o.length||s.length?{pos:i,path:s,rooms:o,mapId:a}:null}function ql(t,e,i){if(!t||!e)return null;const s=e.attributes||{},o=t.split(".")[0]||"",n=null!=i?.platform?String(i.platform):null,r=!!Il(s.vacuum_position??s.robot_position),a=!(!s.rooms||"object"!=typeof s.rooms),l=Fl(s.path?.path??s.path?.points??s.path).length>0,c=[s.map_name,s.current_map,s.map_index,s.selected_map].some(t=>null!=t),h="xiaomi_cloud_map_extractor"===n,d=a||l||c;let p=null;if(r?p="compatible":h?p="known_xcme_incomplete":d?p="partial":"camera"===o&&(p="camera"),!p)return null;const u=r?"camera"===o?300:200:h?100:d?50:0;return{entityId:t,name:String(s.friendly_name||t),platform:n,category:p,hasPosition:r,hasRooms:a,hasPath:l,hasMapId:c,score:u}}const Ul=t=>t.toLowerCase().replace(/[\s_\-.,]+/g,"");function Bl(t,e){const i=new Map(e.map(t=>[Ul(t.name),t])),s=[],o=[];for(const e of t){const t=i.get(Ul(e.name));t&&(s.push([[e.cx,e.cy],[t.cx,t.cy]]),o.push(e.name))}if(s.length<3)return null;const n=function(t){if(t.length<3)return null;let e=0,i=0,s=0,o=0,n=0,r=0,a=0,l=0,c=0,h=0,d=0,p=0;for(const[[u,_],[m,g]]of t){if(![u,_,m,g].every(Number.isFinite))return null;e+=u*u,i+=u*_,s+=u,o+=_*_,n+=_,r+=1,a+=u*m,l+=_*m,c+=m,h+=u*g,d+=_*g,p+=g}const u=[e,i,s,i,o,n,s,n,r],_=t=>{const[e,i,s,o,n,r,a,l,c]=u,h=e*(n*c-r*l)-i*(o*c-r*a)+s*(o*l-n*a);if(!Number.isFinite(h)||Math.abs(h)<1e-9)return null;const d=[(n*c-r*l)/h,(s*l-i*c)/h,(i*r-s*n)/h,(r*a-o*c)/h,(e*c-s*a)/h,(s*o-e*r)/h,(o*l-n*a)/h,(i*a-e*l)/h,(e*n-i*o)/h];return[d[0]*t[0]+d[1]*t[1]+d[2]*t[2],d[3]*t[0]+d[4]*t[1]+d[5]*t[2],d[6]*t[0]+d[7]*t[1]+d[8]*t[2]]},m=_([a,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 n?{matrix:n,matched:o,residual:Al(n,s)}:null}function jl(t,e,i){const s=t[t.length-1];if(s&&s[0]===e[0]&&s[1]===e[1])return t;if(t.push(e),t.length<=600)return t;let o=function(t,e){if(t.length<3)return t.slice();const i=new Uint8Array(t.length);i[0]=i[t.length-1]=1;const s=[[0,t.length-1]];for(;s.length;){const[o,n]=s.pop(),[r,a]=t[o],[l,c]=t[n],h=l-r,d=c-a,p=Math.hypot(h,d)||1e-9;let u=0,_=-1;for(let e=o+1;eu&&(u=i,_=e)}_>0&&u>e&&(i[_]=1,s.push([o,_],[_,n]))}const o=[];for(let e=0;e600&&(o=o.filter((t,e)=>e%2==0||e===o.length-1)),o}function Gl(t){return"cleaning"===t||"returning"===t||"on"===t}const Wl={0:[1,0],90:[0,1],180:[-1,0],270:[0,-1]};function Vl(t){const[e,i]=Wl[t.rot]||[1,0],s=t.mir?-1:1;return[t.s*e*s,-t.s*i,t.ox,t.s*i*s,t.s*e,t.oy]}function Kl(t){const e=t[0]*t[4]-t[1]*t[3];if(!Number.isFinite(e)||Math.abs(e)<1e-12)return null;const i=e<0,s=Math.sqrt(Math.abs(e));let o=180*Math.atan2(-t[1],t[4])/Math.PI;return o=(90*Math.round(o/90)%360+360)%360,{ox:t[2],oy:t[5],s:s,rot:o,mir:i}}function Yl(t,e,i,s){const[o,n]=Rl(Vl(e),i,s),r=Vl({...t,ox:0,oy:0}),[a,l]=Rl(r,i,s);return{...t,ox:o-a,oy:n-l}}function Jl(t){const e=t?.trail_mode;return"never"===e||"cleaning"===e||"always"===e?e:!1===t?.trail?"never":"cleaning"}const Zl=1e-5;function Xl(t){const e=t.map(()=>[]);for(let i=0;i=1-1e-9||p<=1e-9||p>=1-1e-9||(e[i].push(d),e[r].push(p))}}const i=[];for(let s=0;st-e),1];for(let t=1;t{const i=e[2]-e[0],s=e[3]-e[1],o=i*i+s*s;if(!(o>0))return Math.hypot(t[0]-e[0],t[1]-e[1]);const n=Math.max(0,Math.min(1,((t[0]-e[0])*i+(t[1]-e[1])*s)/o));return Math.hypot(t[0]-(e[0]+n*i),t[1]-(e[1]+n*s))},ec=(t,e,i,s)=>{const o=s[2]-s[0],n=s[3]-s[1],r=e*n-i*o;if(Math.abs(r)<1e-12)return 1/0;const a=s[0]-t[0],l=s[1]-t[1],c=(a*n-l*o)/r;if(!(c>1e-9))return 1/0;const h=(a*i-l*e)/r;return h<-1e-9||h>1+1e-9?1/0:c};function ic(t,e,i,s=96){if(!(e>0&&Number.isFinite(t[0])&&Number.isFinite(t[1])))return[];const o=[];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=tc(t,s);i>e||i<1e-7||o.push(s)}const n=[],r=Math.max(12,Math.round(s));for(let t=0;tt-e);const a=[];let l=Number.NEGATIVE_INFINITY;for(const i of n){if(i-l<1e-9)continue;l=i;const s=Math.cos(i),n=Math.sin(i);let r=e;for(const e of o){const i=ec(t,s,n,e);i=3?a:[]}class sc extends ct{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return hn(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[],e=this._lang;return[{name:"title",selector:{text:{}}},t.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:t}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:[{value:"",label:dn(e,"editor.lang_auto")},{value:"en",label:dn(e,"editor.lang_en")},{value:"ru",label:dn(e,"editor.lang_ru")}]}}},{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 W;this._loadSpaces();const t=this._lang,e={title:dn(t,"editor.title"),default_floor:dn(t,"editor.default_floor"),language:dn(t,"editor.language"),icon_size:dn(t,"editor.icon_size"),show_temperature:dn(t,"editor.show_temperature"),live_states:dn(t,"editor.live_states"),show_signal:dn(t,"editor.show_signal"),kiosk:dn(t,"editor.kiosk"),cycle:dn(t,"editor.cycle")};return B`[t[0]-e[0],t[1]-e[1]],_o=(t,e)=>[t[0]+e[0],t[1]+e[1]],mo=(t,e)=>t[0]*e[0]+t[1]*e[1],go=t=>Math.hypot(t[0],t[1]);function fo(t){let e=0;for(let i=0;it[1]!=l>t[1]&&t[0]<(a-o)*(t[1]-r)/(l-r)+o&&(i=!i)}return i}([a[0]+r[0]*l,a[1]+r[1]*l],t)&&(r=[-r[0],-r[1]]),r}function bo(t,e,i,s){const n=s||yo(t,e),o=(e+1)%t.length;return t.map((t,s)=>s===e||s===o?[t[0]+n[0]*i,t[1]+n[1]*i]:[...t])}function wo(t,e,i,s){const n=[],o=uo(i,e),r=go(o);if(rs||h>s)continue;const d=mo(uo(o,e),a),p=mo(uo(l,e),a),u=Math.max(0,Math.min(d,p)),_=Math.min(r,Math.max(d,p));_-u>s&&n.push([[e[0]+a[0]*u,e[1]+a[1]*u],[e[0]+a[0]*_,e[1]+a[1]*_]])}return n}function ko(t,e,i,s,n){const o=wo(t,e,i,n);if(!o.length)return null;const r=t=>o.some(([e,i])=>vo(t,e,i)<=n),a=t.length,l=[];for(let o=0;on||f>n)continue;const v=mo(uo(e,c),u),y=mo(uo(i,c),u),b=Math.max(0,Math.min(v,y)),w=Math.min(p,Math.max(v,y));if(!(w-b<=n)){if(b>n&&bn&&wgo(uo(i,t[(s+1)%t.length]))>e);for(let t=0;t<2;t++)i=i.filter((t,s)=>{const n=i[(s-1+i.length)%i.length],o=i[(s+1)%i.length],r=(t[0]-n[0])*(o[1]-n[1])-(t[1]-n[1])*(o[0]-n[0]),a=go(uo(o,n))||1;return Math.abs(r)/a>e});return i.length>=3?i:t}function xo(t){const e=t.length;if(e<3)return!1;for(let i=0;i(t[0]-n[0])*a[1]-(t[1]-n[1])*a[0],c=t=>(t[0]-n[0])*a[0]+(t[1]-n[1])*a[1],h=i,d=r-i;for(const e of t){const t=Math.abs(l(e));if(t<=i)continue;const n=c(e);n<=h||n>=d||tMath.abs(r+(t-p)/g*(a-r));s=Math.min(s,f(_),f(m))}}return s}function Mo(t,e,i){if(ui(t,e,i))return!0;if(pi(t,e,i)||pi(e,t,i))return!1;let s=0;try{const i=We([[...t.map(t=>[t[0],t[1]]),[t[0][0],t[0][1]]]],[[...e.map(t=>[t[0],t[1]]),[e[0][0],e[0][1]]]]);for(const t of i)t?.[0]&&(s+=mi(t[0]))}catch{return!1}return s>Math.max(1e-7,i*i)}function Co(t,e,i,s,n){const o=[i.n[0]*s,i.n[1]*s],r={polys:{},openings:{},movedSpans:{}};if(Math.abs(s)<1e-9)return r;for(const e of t){if(e.id===i.roomId){r.polys[e.id]=bo(e.poly,i.edge,s,i.n),r.movedSpans[e.id]=[[_o(i.a,o),_o(i.b,o)]];continue}const t=wo(e.poly,i.a,i.b,n);if(!t.length)continue;const a=ko(e.poly,i.a,i.b,o,n);a&&(r.polys[e.id]=a,r.movedSpans[e.id]=t.map(([t,e])=>[_o(t,o),_o(e,o)]))}for(const t of e)vo([t.x,t.y],i.a,i.b)<=n&&(r.openings[t.id]=[t.x+o[0],t.y+o[1]]);return r}function Do(t,e,i){for(const s of e)for(let e=0;ei)continue;const c=(t.x-n[0])*l[0]+(t.y-n[1])*l[1];if(c-t.length/2>=-i&&c+t.length/2<=a+i)return!0}return!1}function To(t,e,i){return t.filter(t=>e.some(e=>{for(let s=0;sa.polys[t.id]||t.poly;for(const e of l){const s=t.find(t=>t.id===e),n=a.polys[e];if(!xo(n))return!1;const l=fo(s.poly),h=fo(n);if(Math.abs(h)t.find(t=>t.id===e).poly),d=t.map(c);for(const t of To(e,h,2*r)){const e=a.openings[t.id];if(!Do(e?{...t,x:e[0],y:e[1]}:t,d,2*r))return!1}return!0}function Po(t,e,i,s,n,o){const r=t=>[s[0]+(t[0]-s[0])*n,s[1]+(t[1]-s[1])*n],a={poly:t.poly.map(r),openings:{}};for(const s of e){let e=!1;for(let i=0;i{for(let e=0;et.id===i);if(!l)return!1;const c=t.filter(t=>t.id!==i).map(t=>t.poly),h=Po(l,e,c,s,n,2*a),d=h.poly,p=function(t){const e=function(t){const e=[...t].sort((t,e)=>t[0]-e[0]||t[1]-e[1]);if(e.length<3)return e;const i=(t,e,i)=>(e[0]-t[0])*(i[1]-t[1])-(e[1]-t[1])*(i[0]-t[0]),s=[];for(const t of e){for(;s.length>=2&&i(s[s.length-2],s[s.length-1],t)<=0;)s.pop();s.push(t)}const n=[];for(let t=e.length-1;t>=0;t--){const s=e[t];for(;n.length>=2&&i(n[n.length-2],n[n.length-1],s)<=0;)n.pop();n.push(s)}return s.pop(),n.pop(),s.concat(n)}(t);if(e.length<3)return 0;let i=1/0;for(let t=0;tt.id===i?d:t.poly);for(const t of To(e,[l.poly],2*a)){const e=h.openings[t.id];if(!Do(e?{...t,x:e[0],y:e[1]}:t,u,2*a))return!1}return!0}function zo(t,e,i){const s=i/e;return mi(t)*s*s/1e4}function Io(t,e){return e?`${Math.round(10.7639*t)} ft²`:`${(Math.round(10*t)/10).toFixed(1)} m²`}const Eo=[[-90,"#070c14"],[-12,"#070c14"],[-4,"#131a28"],[0,"#4a3527"],[10,"#e8ddcf"],[30,"#ffffff"],[90,"#ffffff"]],Fo=t=>Math.min(1,Math.max(0,t));function No(t){const e=Math.min(90,Math.max(-90,Number(t)||0));let i=Eo[Eo.length-1][1];for(let t=1;t{const o=[t.x+n[0]*i*s,t.y+n[1]*i*s];return e.find(t=>t.poly.length>=3&&si(o,t.poly))||null},r=o(1),a=o(-1);return r&&a?null:r||a?r?{normal:[-n[0],-n[1]],roomId:r.id}:{normal:n,roomId:a.id}:null}function Lo(t,e,i){return i>0&&t[0]*e[0]+t[1]*e[1]>.05}function Ho(t,e,i,s){return[[t[0],t[1]],[e[0],e[1]],[e[0]+i[0]*s,e[1]+i[1]*s],[t[0]+i[0]*s,t[1]+i[1]*s]]}function qo(t,e){try{const i=We([[...t.map(t=>[t[0],t[1]]),[t[0][0],t[0][1]]]],[[...e.map(t=>[t[0],t[1]]),[e[0][0],e[0][1]]]]),s=[];for(const t of i){const e=t?.[0];!Array.isArray(e)||e.length<4||s.push(e.slice(0,e.length-1).map(t=>[t[0],t[1]]))}return s}catch{return[]}}function Uo(t,e,i,s,n,o,r){if(!(s>0))return[];const a=function(t,e){const i=function(t,e){return function(t){const e=t%360;return e<0?e+360:e}(t-e)}(t,e)*Math.PI/180;return[Math.sin(i),-Math.cos(i)]}(i,n),l=[-a[0],-a[1]],c=function(t){const e=Math.min(90,Math.max(0,t));return.7*(.8+1.7*Math.pow(1-e/90,1.6))}(s),h=[];for(const i of e){if(!(i.length>0))continue;const e=Oo(i,t);if(!e||!Lo(e.normal,a,s))continue;const n=t.find(t=>t.id===e.roomId);if(!n)continue;const d=o&&o[e.roomId]||n.poly,p=i.angle*Math.PI/180,u=i.length/2,_=[-e.normal[0],-e.normal[1]],m=Math.max(0,r?.[i.id]||0),g=i.x+_[0]*m/2,f=i.y+_[1]*m/2,v=Math.cos(p)*u,y=Math.sin(p)*u,b=[g-v,f-y],w=[g+v,f+y],k=c*i.length,$=qo(Ho(b,w,l,k),d);if(!$.length)continue;const x=l[0]*_[0]+l[1]*_[1];h.push({openingId:i.id,roomId:e.roomId,polys:$,a:b,b:w,dir:l,len:k,normal:_,depth:k*x})}return h}function Bo(t){return Math.round(10*(Number(t)||0))/10}const Wo=t=>"number"==typeof t&&Number.isInteger(t)&&t>=0&&t<=359?t:null;function jo(t,e){const i=Wo(e?.north_deg);return null!==i?i:Wo(t?.north_deg)}function Go(t,e){const i=t=>"static"===t||"daynight"===t?t:null;return i(e?.bg_mode)??i(t?.bg_mode)??"static"}function Vo(t,e){const i=e?.sun_rays;return"boolean"==typeof i?i:!0===t?.sun_rays}function Ko(t){const e=t?.states?.["sun.sun"]?.attributes,i=Number(e?.azimuth),s=Number(e?.elevation);return Number.isFinite(i)&&Number.isFinite(s)?{azimuth:i,elevation:s}:null}const Yo={color:"#607d8b",opacity:1,widthCm:3.6,fill:!1,fillColor:"#607d8b",fillOpacity:.25},Jo=(t,e=1)=>{const i=Number(t);return Number.isFinite(i)?Math.min(1,Math.max(0,i)):e},Zo=t=>{let e=Number(t);return Number.isFinite(e)?(e=(e%360+360)%360,e>180?e-360:e):0};function Xo(t,e,i){return Zo(e)?[t.x,t.y]:i([t.x,t.y])}const Qo=(t,e,i)=>{const s=Number(t),n=Number.isFinite(e)&&e>0?e:5;return Number.isFinite(s)&&s>0?s/n*i:0},tr=(t,e,i)=>{const s=Number(t),n=Number.isFinite(e)&&e>0?e:5;return Number.isFinite(s)&&i>0?s/i*n:0},er=(t,e,i,s=Yo.widthCm)=>{const n=Number(t?.width_cm);if(Number.isFinite(n)&&n>0)return n;const o=Number(t?.width);return Number.isFinite(o)&&o>0?tr(o,e,i):s};function ir(t,e){return{color:t.color,opacity:Jo(t.opacity),width_cm:Math.max(.1,Math.min(100,Number(t.widthCm)||.1)),...e?{fill:t.fill,fill_color:t.fillColor,fill_opacity:Jo(t.fillOpacity,.25)}:{}}}function sr(t){const e=t.x+t.w/2,i=t.y+t.h/2,s=Zo(t.angle)*Math.PI/180,n=Math.cos(s),o=Math.sin(s),r=(t,s)=>{const r=t-e,a=s-i;return[e+r*n-a*o,i+r*o+a*n]};return[r(t.x,t.y),r(t.x+t.w,t.y),r(t.x+t.w,t.y+t.h),r(t.x,t.y+t.h)]}function nr(t){const e=sr(t),i=(t,e)=>[(t[0]+e[0])/2,(t[1]+e[1])/2],s=[(e[0][0]+e[2][0])/2,(e[0][1]+e[2][1])/2];return{points:[...e,i(e[0],e[1]),i(e[1],e[2]),i(e[2],e[3]),i(e[3],e[0]),s],segments:e.map((t,i)=>({a:t,b:e[(i+1)%4]}))}}function or(t,e,i,s,n,o,r,a){const l=Zo(t.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l),d=-Math.sin(l),p=Math.cos(l),u=t.x+t.w/2,_=t.y+t.h/2,m=e>0?-t.w/2:t.w/2,g=i>0?-t.h/2:t.h/2,f=u+m*c+g*d,v=_+m*h+g*p,y=s-f,b=n-v;let w=(y*c+b*h)*(e>0?1:-1),k=(y*d+b*p)*(i>0?1:-1),$=null;if(o){const e=w/Math.max(t.w,a),i=k/Math.max(t.h,a);$=Math.max(a/Math.max(t.w,a),a/Math.max(t.h,a),e,i),w=t.w*$,k=t.h*$}if(r>0)if(o){const e=Math.max(1,Math.round(t.w/r)),i=Math.max(1,Math.round(t.h/r));if(Math.abs(t.w-e*r)<1e-6&&Math.abs(t.h-i*r)<1e-6){const s=(t,e)=>{let i=Math.abs(t),s=Math.abs(e);for(;s;)[i,s]=[s,i%s];return Math.max(1,i)},n=1/s(e,i),o=Math.max(a/t.w,a/t.h),r=Math.max(o,Math.round(($??1)/n)*n);w=t.w*r,k=t.h*r}else{t.w>=t.h?(w=Math.round(w/r)*r,k=w*(t.h/Math.max(t.w,a))):(k=Math.round(k/r)*r,w=k*(t.w/Math.max(t.h,a)))}}else w=Math.round(w/r)*r,k=Math.round(k/r)*r;w=Math.max(a,w),k=Math.max(a,k);return{x:f+(e>0?w/2:-w/2)*c+(i>0?k/2:-k/2)*d-w/2,y:v+(e>0?w/2:-w/2)*h+(i>0?k/2:-k/2)*p-k/2,w:w,h:k,angle:Zo(t.angle)||void 0}}const rr=(t,e,i)=>{const s=i[0]-e[0],n=i[1]-e[1],o=s*s+n*n;if(o<1e-12)return[...e];const r=Math.max(0,Math.min(1,((t[0]-e[0])*s+(t[1]-e[1])*n)/o));return[e[0]+s*r,e[1]+n*r]};const ar=15;function lr(t,e){return Number.isFinite(t)&&t>0&&Number.isFinite(e)&&e>0&&t*e<3}function cr(t){return Number.isFinite(t)?Math.max(1,Math.min(100,t)):1}function hr(t,e){return!Number.isFinite(t)||t<=0?"":String(e?Math.round(t/2.54*100)/100:Math.round(100*t)/100)}function dr(t,e,i){const s=Number(e)>0?Number(e):5;return cr(t)/s*i}function pr(t,e){return e>0&&Number.isFinite(t)?Math.round(t/e)*e:t}function ur(t,e){let i=e[0]-t[0],s=e[1]-t[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 _r(t,e,i){const s=pr((t[0]+e[0])/2,i),n=pr((t[1]+e[1])/2,i),[o,r]=ur(t,e);let a=Math.atan2(r,o);a<0&&(a+=Math.PI);const l=Math.round(1800*a)/1800,c=i>0&&i<.01?6:i<1?4:2;return`${s.toFixed(c)},${n.toFixed(c)}@${l.toFixed(4)}`}function mr(t,e,i,s){return 1===s?_r(t,e,i):_r([t[0]/s,t[1]/s],[e[0]/s,e[1]/s],i)}function gr(t,e){if(!Array.isArray(t.a)||!Array.isArray(t.b)||t.a.length<2||t.b.length<2)return null;const i=[Number(t.a[0]),Number(t.a[1]),Number(t.b[0]),Number(t.b[1])];if(!i.every(Number.isFinite))return null;const s=e>0?e:1;return[[i[0]*s,i[1]*s],[i[2]*s,i[3]*s]]}function fr(t,e,i,s,n){const o=n>0?n:1;return{key:mr(t,e,s,o),cm:cr(i),a:[t[0]/o,t[1]/o],b:[e[0]/o,e[1]/o]}}function vr(t,e){const i=e>0?e:1,s=[];for(const e of t){const t=e.key.lastIndexOf("@");if(t<0)continue;const[n,o]=e.key.slice(0,t).split(",").map(Number),r=Number(e.key.slice(t+1));[n,o,r].every(Number.isFinite)&&s.push({w:e,x:n*i,y:o*i,ang:r})}return s}function yr(t,e){const[i,s]=ur(t,e);let n=Math.atan2(s,i);return n<0&&(n+=Math.PI),n}function br(t,e){let i=Math.abs(t-e);return i>Math.PI/2&&(i=Math.PI-i),i<.02}function wr(t,e,i,s,n=1){if(!t?.length)return null;const o=mr(e,i,s,n),r=t.find(t=>t.key===o);if(r)return r;const a=n>0?n:1,l=(e[0]+i[0])/2,c=(e[1]+i[1])/2,h=yr(e,i),d=Math.max(.5*s,1e-9)*a;for(const e of vr(t,a))if(br(e.ang,h)&&Math.hypot(e.x-l,e.y-c)<=d)return e.w;return null}function kr(t,e,i,s,n=1){const o=wr(t,e,i,s,n);return o&&o.cm>0?cr(o.cm):0}function $r(t,e,i,s=1,n=[]){if(!t?.length)return[];const o=new Set,r=Xe(e);for(const t of r)o.add(mr([t[0],t[1]],[t[2],t[3]],i,s));const a=e||[],l=Math.max(i*s*.02,1e-9);for(let t=0;t(o.has(t.key)||(t=>{const e=gr(t,s);if(!e)return!1;const[i,o]=e,a=o[0]-i[0],c=o[1]-i[1],h=Math.hypot(a,c);if(h<=l)return!1;if(!r.some(t=>{const e=[t[0],t[1]],s=[t[2],t[3]];return br(yr(i,o),yr(e,s))&&Br(i[0],i[1],e[0],e[1],s[0],s[1])<=l&&Br(o[0],o[1],e[0],e[1],s[0],s[1])<=l}))return!1;const d=(n||[]).some(t=>{const e=[t[0],t[1]],s=[t[2],t[3]];if(!br(yr(i,o),yr(e,s)))return!1;const n=t=>Math.abs((t[0]-i[0])*c-(t[1]-i[1])*a)/h;if(n(e)>l||n(s)>l)return!1;const r=h*h,d=((e[0]-i[0])*a+(e[1]-i[1])*c)/r,p=((s[0]-i[0])*a+(s[1]-i[1])*c)/r;return Math.min(1,Math.max(d,p))-Math.max(0,Math.min(d,p))>l/h});return!d})(t))&&t.cm>=1&&t.cm<=100)}function xr(t,e,i,s=8){const[n,o]=ur(t,e);let r=Math.atan2(o,n);r<0&&(r+=Math.PI);let a=i*Math.PI/180%Math.PI;a<0&&(a+=Math.PI);let l=Math.abs(r-a);return l>Math.PI/2&&(l=Math.PI-l),l<=s*Math.PI/180}function Sr(t,e,i,s,n=1){if(!t?.length)return[];if(e.length!==i.length)return t.slice();const o=new Map;for(let t=0;t0?n:1,a=Math.max(.5*s,1e-9)*r,l=new Set,c=[];for(const n of t){let t="",h=null;const d=gr(n,r);if(d)for(let n=0;na||Br(d[1][0],d[1][1],o[0],o[1],l[0],l[1])>a)continue;const u=l[0]-o[0],_=l[1]-o[1],m=u*u+_*_;if(m<1e-18)continue;const g=t=>{const e=Math.max(0,Math.min(1,((t[0]-o[0])*u+(t[1]-o[1])*_)/m));return[c[0]+(p[0]-c[0])*e,c[1]+(p[1]-c[1])*e]};h=[g(d[0]),g(d[1])],t=mr(h[0],h[1],s,r);break}if(d||(t=o.get(n.key)||""),!t){const o=vr([n],r)[0];if(o)for(let n=0;n1.000001)continue;if(Br(o.x,o.y,l[0],l[1],c[0],c[1])>a)continue;const g=h[0]+(d[0]-h[0])*Math.max(0,Math.min(1,m)),f=h[1]+(d[1]-h[1])*Math.max(0,Math.min(1,m)),[v,y]=ur(h,d),b=Math.max(s*r,1e-6);t=mr([g-v*b,f-y*b],[g+v*b,f+y*b],s,r);break}}t||(t=n.key),l.has(t)||(l.add(t),c.push(h?fr(h[0],h[1],n.cm,s,r):{...n,key:t,cm:cr(n.cm)}))}return c}function Mr(t,e,i,s,n,o=1){const r=mr(e,i,n,o),a=(t||[]).filter(t=>t.key!==r);return null==s||s<1?a:[...a,fr(e,i,s,n,o)]}function Cr(t,e,i,s,n,o=[],r=1){let a=t?t.slice():[];for(const t of function(t,e,i,s,n=1,o=[]){const r=Nr(t,e,i,s,n,o);if(!r)return[];const a=[];for(let t=0;t0)||!t||t.length<2)return"";if(i&&t.length>=3){let i=t;const s=t[t.length-1];if(t.length>=4&&Math.hypot(t[0][0]-s[0],t[0][1]-s[1])<1e-9&&(i=t.slice(0,-1)),i.length>=3){const t=i.map(()=>e),s=Kr(i,t),n=Ir(i,t);if(s&&n)return`${Er(s)} ${Er(Fr(n))}`}}let s="";for(let i=0;i.05)&&Br(r,a,t[0],t[1],t[2],t[3])<=o)return!0}return!1}function Rr(t,e){return Math.max(t*(e>0?e:1)*.04,1e-9)}function Pr(t,e){const i=t[e],s=t[(e+1)%t.length],n=s[0]-i[0],o=s[1]-i[1],r=Math.hypot(n,o)||1;let a=-o/r,l=n/r;const c=[(i[0]+s[0])/2,(i[1]+s[1])/2];return function(t,e){let i=!1;for(let s=0,n=e.length-1;st[1]!=l>t[1]&&t[0]<(a-o)*(t[1]-r)/(l-r+0)+o&&(i=!i)}return i}([c[0]+.001*a,c[1]+.001*l],t)||(a=-a,l=-l),[a,l]}function Ar(t,e){const i=t[0]*e[1]-t[1]*e[0],s=t[0]*e[0]+t[1]*e[1];return Math.abs(i)<1e-9&&s>0}function zr(t,e,i,s){const n=e[0]*s[1]-e[1]*s[0];if(Math.abs(n)<1e-12)return null;const o=[i[0]-t[0],i[1]-t[1]],r=(o[0]*s[1]-o[1]*s[0])/n;return[t[0]+r*e[0],t[1]+r*e[1]]}function Ir(t,e){const i=t?.length||0;if(i<3||e.length!==i)return null;if(e.every(t=>!(t>0)))return t.map(t=>[t[0],t[1]]);const s=[];for(let n=0;n0||d>0)){s.push([t[n][0],t[n][1]]);continue}if(Ar(b,w)){const e=t[n],i=[e[0]+p*h,e[1]+u*h],o=[e[0]+_*d,e[1]+m*d];s.push(i),Math.hypot(o[0]-i[0],o[1]-i[1])>1e-9&&s.push(o);continue}const x=zr(k,b,$,w),S=Math.max(h,d,1e-9);if(x){if(Math.hypot(x[0]-t[n][0],x[1]-t[n][1])<=4*S){s.push(x);continue}}h>0&&s.push([t[n][0]+p*h,t[n][1]+u*h]),d>0&&s.push([t[n][0]+_*d,t[n][1]+m*d]),h>0||d>0||s.push([t[n][0],t[n][1]])}return s.length>=3?s:null}function Er(t,e=!0){if(!t.length)return"";let i=`M ${t[0][0]} ${t[0][1]}`;for(let e=1;et?.id===e),a=Je(r);if(!a||a.length<3)return null;const l=Rr(s,n),c=[];for(const i of t||[]){if(!i||i.id===e)continue;const t=Je(i);if(t)for(const e of us(a,t,l))c.push([e[0],e[1]],[e[2],e[3]])}for(const t of i||[])c.push([t[0],t[1]],[t[2],t[3]]);for(const t of o||[]){const e=gr(t,n);e&&c.push(e[0],e[1])}const h=[],d=[];for(let t=0;tl)continue;const r=((t[0]-e[0])*(i[0]-e[0])+(t[1]-e[1])*(i[1]-e[1]))/(s*s);r<=n||r>=1-n||(o.some(t=>Math.abs(t-r)*s<=2*l)||o.push(r))}o.sort((t,e)=>t-e);for(const s of o)h.push([e[0]+(i[0]-e[0])*s,e[1]+(i[1]-e[1])*s]),d.push(t)}return{poly:h,parent:d,orig:a}}function Or(t,e,i,s){const n=e.poly.length,o=new Array(n).fill(0);if(!t?.length)return o;const r=new Set,a=[];for(let l=0;l0?(o[l]=cr(c.cm),r.add(c.key)):a.push(l)}if(!a.length)return o;const l=s>0?s:1,c=Math.max(.5*i,1e-9)*l,h=vr(t,l).filter(t=>t.w.cm>0),d=new Map;for(const t of a){const i=e.parent[t],s=d.get(i);s?s.push(t):d.set(i,[t])}for(const[t,i]of d){const s=e.orig[t],n=e.orig[(t+1)%e.orig.length],a=yr(s,n),d=(s[0]+n[0])/2,p=(s[1]+n[1])/2;let u=null;const _=Math.hypot(n[0]-s[0],n[1]-s[1]);for(const t of h){if(r.has(t.w.key))continue;if(!br(t.ang,a))continue;const e=gr(t.w,l);let i=!1,o=0;if(e){if(!br(yr(e[0],e[1]),a))continue;if(Br(s[0],s[1],e[0][0],e[0][1],e[1][0],e[1][1])>c||Br(n[0],n[1],e[0][0],e[0][1],e[1][0],e[1][1])>c)continue;i=!0,o=Math.max(0,Math.hypot(e[1][0]-e[0][0],e[1][1]-e[0][1])-_)}else{if(Br(t.x,t.y,s[0],s[1],n[0],n[1])>c)continue;o=Math.hypot(t.x-d,t.y-p)}(!u||i&&!u.exact||i===u.exact&&ot?.id===e),n=Je(s);if(!n)return[];const o=[];for(const s of t||[]){if(!s||s.id===e)continue;const t=Je(s);if(t)for(const e of us(n,t,i))o.push(e)}return o}(t,e,Rr(n,a)),h=function(t,e,i,s,n){const o=Rr(s,n),r=[];for(let a=0;aBr(h,d,t[0],t[1],t[2],t[3])<=o);r.push(p?"shared":"outer")}return r}(l.poly,c,s,n,a),d=Or(i,l,n,a),p=d.map((t,e)=>h[e]&&t>0?dr(t,o,r)/2:0);return{...l,kinds:h,cms:d,offsets:p}}function Hr(t,e,i,s,n,o,r=1){const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,n,o,r);if(c)for(let t=0;t0&&!l.has(c.key)&&(l.add(c.key),a.push(c));const c=[];for(const a of t||[]){if(!a?.id)continue;const l=Lr(t,a.id,e,i,s,n,o,r);if(l)for(let t=0;t0)||null===l.kinds[i]){t++;continue}let o=t;for(;o+10&&c.push({a:[h[0],h[1]],b:[d[0],d[1]],key:mr(h,d,s,r),cm:n,len:p}),t=o+1}}}c.sort((t,e)=>e.len-t.len||t.key.localeCompare(e.key));const h=[],d=new Set,p=new Set,u=4*Rr(s,r);for(const t of c){const e=a.filter(e=>!p.has(e.key)&&e.cm===t.cm&&br(yr(e.a,e.b),yr(t.a,t.b))&&Br(e.a[0],e.a[1],t.a[0],t.a[1],t.b[0],t.b[1])<=u&&Br(e.b[0],e.b[1],t.a[0],t.a[1],t.b[0],t.b[1])<=u);if(e.length){for(const t of e)p.add(t.key);d.has(t.key)||(d.add(t.key),h.push(fr(t.a,t.b,t.cm,s,r)))}}for(const t of a)p.has(t.key)||d.has(t.key)||(d.add(t.key),h.push(fr(t.a,t.b,t.cm,s,r)));return h}function Ur(t,e,i,s,n,o,r,a=1){const l=Rr(n,a),c=(s[0]+s[2])/2,h=(s[1]+s[3])/2,d=yr([s[0],s[1]],[s[2],s[3]]);let p=null;for(const s of Hr(t,e,i,n,o,r,a)){if(!br(yr(s.a,s.b),d))continue;const t=Br(c,h,s.a[0],s.a[1],s.b[0],s.b[1]);t>4*l||(!p||tt?.id===e),c=Je(l);if(!c||c.length<3)return null;if(!i?.length)return c.map(t=>[t[0],t[1]]);const h=Lr(t,e,i,s,n,o,r,a);return h&&h.offsets.some(t=>t>0)&&Ir(h.poly,h.offsets)||c.map(t=>[t[0],t[1]])}function jr(t){const e=t.map(t=>[t[0],t[1]]);return e.push([t[0][0],t[0][1]]),[e]}function Gr(t,e,i,s=[],n,o,r,a=1,l=[]){if(!e?.length&&!l.length)return null;const c=[];let h=0;for(const s of t||[]){if(!s?.id)continue;const l=Lr(t,s.id,e,i,n,o,r,a);if(!l||l.poly.length<3||!l.offsets.some(t=>t>0))continue;for(const t of l.offsets)t>0&&(h=Math.max(h,2*t));const d=Kr(l.poly,l.offsets),p=Ir(l.poly,l.offsets);d&&c.push({outset:d,inset:p})}for(const t of l){const e=t.map(t=>t[0]),i=t.map(t=>t[1]);if(e.length){const s=Math.min(Math.max(...e)-Math.min(...e),Math.max(...i)-Math.min(...i)),n=Math.min(...t.map((e,i)=>{const s=t[(i+1)%t.length];return Math.hypot(s[0]-e[0],s[1]-e[1])}));h=Math.max(h,t.length>16?s:n)}}const d=function(t,e,i,s,n,o,r){if(!e?.length||!i?.length)return[];const a=4*Rr(s,r),l=new Map;for(const a of Hr(t,e,i,s,n,o,r))!a.open&&a.half>0&&!l.has(a.key)&&l.set(a.key,a);const c=[...l.values()];if(c.length<2)return[];const h=[];for(const t of i)for(const e of[[t[0],t[1]],[t[2],t[3]]])h.some(t=>Math.hypot(t[0]-e[0],t[1]-e[1])<=a)||h.push(e);const d=[],p=(t,e)=>{let i=0,s=0;if(Math.hypot(t.a[0]-e[0],t.a[1]-e[1])<=a)i=t.b[0]-t.a[0],s=t.b[1]-t.a[1];else{if(!(Math.hypot(t.b[0]-e[0],t.b[1]-e[1])<=a))return null;i=t.a[0]-t.b[0],s=t.a[1]-t.b[1]}const n=Math.hypot(i,s);return n>a?[i/n,s/n]:null};for(const t of h){const e=c.map(e=>({iv:e,u:p(e,t)})).filter(t=>!!t.u);for(let i=0;i4*_||d.push(r>0?[t.slice(),h,u,p]:[t.slice(),p,u,h])}}return d}(t,e,i,n,o,r,a),p=s.length?Jr(t,e,i,n,o,r,a):null;try{const t=t=>{const e=jr(t.outset);return t.inset?je(e,jr(t.inset)):e};let e=c.length?t(c[0]):null;for(let i=1;i0))continue;const i=Qr(p,t,!0);if(!i.negative&&!i.positive)continue;const s=t.angle*Math.PI/180,o=Math.cos(s),r=Math.sin(s),l=-r,c=o,d=t.length/2,u=1.25*Math.max(h,n*a),_=[[t.x-o*d-l*u,t.y-r*d-c*u],[t.x+o*d-l*u,t.y+r*d-c*u],[t.x+o*d+l*u,t.y+r*d+c*u],[t.x-o*d+l*u,t.y-r*d+c*u]];e&&(e=je(e,jr(_)))}for(const t of l)t.length<3||(e=e?Be(e,jr(t)):[jr(t)]);return{geom:e||[],depthUnits:h}}catch{return null}}function Vr(t,e,i,s=[],n,o,r,a=1,l=[]){if(!e?.length&&!l.length)return null;const c=Gr(t,e,i,s,n,o,r,a,l),h=c?function(t){if(!t)return"";let e="";for(const i of t)if(Array.isArray(i))for(const t of i){if(!Array.isArray(t)||t.length<4)continue;const i=t.slice(0,t.length-1);i.length<3||(e+=(e?" ":"")+Er(i.map(t=>[t[0],t[1]])))}return e}(c.geom):"";if(c&&h)return{d:h,depthUnits:c.depthUnits,fillRule:"evenodd"};if(c)return null;const d=function(t,e,i,s,n,o,r=1){if(!e?.length)return[];const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,n,o,r);if(!c||c.poly.length<3||!c.offsets.some(t=>t>0))continue;const h=Kr(c.poly,c.offsets),d=Ir(c.poly,c.offsets);if(!h||!d)continue;const p=`${Er(h)} ${Er(Fr(d))}`;let u="",_="outer",m=0,g=0;for(let t=0;t0){u=mr(c.poly[t],c.poly[(t+1)%c.poly.length],s,r),_=c.kinds[t]||"outer",m=c.cms[t],g=dr(m,n,o);break}a.push({d:p,key:u,kind:_,cm:m,depthUnits:g})}return a}(t,e,i,n,o,r,a),p=l.map(t=>Er(t)).join(" ");if(!d.length&&!p)return null;let u=0;for(const t of d)u=Math.max(u,t.depthUnits);return{d:[d.map(t=>t.d).join(" "),p].filter(Boolean).join(" "),depthUnits:u,fillRule:"nonzero"}}function Kr(t,e){const i=t?.length||0;if(i<3||e.length!==i)return null;if(e.every(t=>!(t>0)))return t.map(t=>[t[0],t[1]]);Fr(t),e.slice().reverse();const s=[];for(let n=0;n0||a>0)){s.push([t[n][0],t[n][1]]);continue}if(Ar(b,w)){const e=t[n],i=[e[0]-l*r,e[1]-c*r],o=[e[0]-h*a,e[1]-d*a];s.push(i),Math.hypot(o[0]-i[0],o[1]-i[1])>1e-9&&s.push(o);continue}const x=zr(k,b,$,w),S=Math.max(r,a,1e-9);if(x){if(Math.hypot(x[0]-t[n][0],x[1]-t[n][1])<=4*S){s.push(x);continue}}r>0&&s.push([t[n][0]-l*r,t[n][1]-c*r]),a>0&&s.push([t[n][0]-h*a,t[n][1]-d*a])}return s.length>=3?s:null}function Yr(t,e,i,s,n,o,r=1){const a=[];for(const l of t||[]){const c=Je(l);if(c&&c.length>=3){const h=Lr(t,l.id,e,i,s,n,o,r),d=h&&h.offsets.some(t=>t>0)?Kr(h.poly,h.offsets):null,p=d||c;a.push({poly:p.map(t=>t.join(",")).join(" ")})}else l&&null!=l.x&&null!=l.y&&null!=l.w&&null!=l.h&&a.push({rect:{x:l.x,y:l.y,w:l.w,h:l.h,rx:.03*Math.min(l.w,l.h)}})}return a}function Jr(t,e,i,s,n,o,r=1){const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,n,o,r);if(!c)continue;const h=Math.abs(mi(c.poly));for(let t=0;t[Math.max(e,t.x0),Math.min(i,t.x1)]).filter(t=>t[1]-t[0]>s).sort((t,e)=>t[0]-e[0]||t[1]-e[1]);if(!n.length)return{coverage:0,full:!1};let o=n[0][0],r=n[0][1],a=0,l=o<=e+s;for(let t=1;t=i-s,{coverage:a,full:l}}function Xr(t,e){return Number(e.full)-Number(t.full)||t.faceDistance-e.faceDistance||t.area-e.area||t.roomId.localeCompare(e.roomId)}function Qr(t,e,i=!1){const s=Number(e?.x),n=Number(e?.y),o=Number(e?.angle),r=Number(e?.length);if(!([s,n,o,r].every(Number.isFinite)&&r>0))return{negative:null,positive:null};const a=o*Math.PI/180,l=Math.cos(a),c=Math.sin(a),h=-c,d=l,p=r/2,u=Math.max(1e-9,t.adjacencyEps),_=new Map;let m=0;for(const e of t.edges){if(i&&!(e.half>0))continue;if(!xr(e.a,e.b,o))continue;const[t,r]=ur(e.a,e.b);if(Math.abs((s-e.a[0])*r-(n-e.a[1])*t)>u)continue;const a=(e.a[0]-s)*l+(e.a[1]-n)*c,g=(e.b[0]-s)*l+(e.b[1]-n)*c,f=Math.max(-p,Math.min(a,g)),v=Math.min(p,Math.max(a,g));if(v-f<=u)continue;const y=e.inward[0]*h+e.inward[1]*d>=0?1:-1,b=((e.a[0]+e.b[0])/2-s)*h+((e.a[1]+e.b[1])/2-n)*d,w=Math.abs(b+y*e.half),k=`${y}|${e.roomId}`,$={x0:f,x1:v,half:e.half,cm:e.cm,key:e.key,axis:[t,r]},x=_.get(k);x?(x.pieces.push($),x.faceDistance=Math.min(x.faceDistance,w)):_.set(k,{roomId:e.roomId,side:y,order:m++,pieces:[$],faceDistance:w,area:e.area,coverage:0,full:!1})}for(const t of _.values()){const e=Zr(t.pieces,-p,p,u);t.coverage=e.coverage,t.full=e.full}const g=t=>{const e=[..._.values()].filter(e=>e.side===t&&e.coverage>u);return e.sort(Xr),e[0]||null};return{negative:g(-1),positive:g(1)}}function ta(t,e){const i=Qr(t,e),s=[i.negative,i.positive].filter(t=>!!t).sort((t,e)=>t.order-e.order);if(!s.length)return{ox:0,oy:0,cm:0,side:-1};const n=s[0],o=e.flip_v?-n.side:n.side,r=(-1===o?i.negative:i.positive)||n,a=[...r.pieces].sort((t,e)=>(t.x0<=0&&t.x1>=0?0:Math.min(Math.abs(t.x0),Math.abs(t.x1)))-(e.x0<=0&&e.x1>=0?0:Math.min(Math.abs(e.x0),Math.abs(e.x1)))||e.x1-e.x0-(t.x1-t.x0)||t.key.localeCompare(e.key))[0];if(!(a.half>0&&a.cm>0))return{ox:0,oy:0,cm:0,side:o};const l=e.angle*Math.PI/180,c=-Math.sin(l),h=Math.cos(l);return{ox:c*o*a.half,oy:h*o*a.half,cm:a.cm,side:o}}function ea(t,e){const i=1e-9,s=[];for(const t of[...e].filter(t=>t.x1>t.x0&&t.half>0).sort((t,e)=>t.x0-e.x0||t.x1-e.x1||t.half-e.half)){const e=s[s.length-1];e&&t.x0<=e.x1+i&&Math.abs(t.half-e.half)<=i?e.x1=Math.max(e.x1,t.x1):s.push({...t})}const n=[];for(let e=0;e=n-l?i.push([s,n]):(t>s+l&&i.push([s,Math.min(n,t)]),et[0]-e[0]||t[1]-e[1]),b=[];for(const t of y){const e=b[b.length-1];e&&t[0]<=e[1]+l?e[1]=Math.max(e[1],t[1]):b.push([t[0],t[1]])}s.set(g,b)}return a}function sa(t,e){const i=new Map;return e.map(e=>function(t,e,i){const s=Qr(t,e,!0),n=s.negative,o=s.positive;if(!n&&!o)return null;let r;if(n&&o)r=[{candidate:n,side:-1},{candidate:o,side:1}];else{const t=n||o;r=[{candidate:t,side:-1},{candidate:t,side:1}]}const a=r.map(({candidate:t,side:s})=>({candidate:t,side:s,pieces:ia(e,s,t.pieces,i)})),l=a.map(({candidate:t,side:e,pieces:i})=>({side:e,roomId:t.roomId,d:ea(e,i)})),c=a.flatMap(({pieces:t})=>t);if(!c.length)return null;const h=Math.max(...c.map(t=>t.half));return{faces:l,minY:-h,maxY:h,wallKey:[...new Set(c.map(t=>t.key))].sort().join("|")}}(t,e,i))}const na=150;function oa(t){return Number.isFinite(t)?Math.max(1,Math.min(na,t)):1}function ra(t){return((Number.isFinite(Number(t))?Number(t):0)%90+90)%90}const aa=t=>{const e=t.map(t=>[t[0],t[1]]);return!e.length||e[0][0]===e[e.length-1][0]&&e[0][1]===e[e.length-1][1]||e.push([...e[0]]),[e]};function la(t){const e=[];for(const i of t||[])for(const t of i||[]){const i=(t||[]).filter(t=>Array.isArray(t)&&t.length>=2);i.length<4||e.push(`M ${i.slice(0,-1).map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`)}return e.join(" ")}function ca(t,e,i,s,n){const o=e[0]-t[0],r=e[1]-t[1],a=Math.hypot(o,r);if(!(a>1e-9))return null;const l=dr(i,s,n)/2,c=-r/a*l,h=o/a*l;return[[t[0]+c,t[1]+h],[e[0]+c,e[1]+h],[e[0]-c,e[1]-h],[t[0]-c,t[1]-h]]}function ha(t,e,i){const s=Number(e)>0?Number(e):5,n=oa(t.cm)/s*i,o=t.center[0],r=t.center[1];if("circle"===t.shape){const t=n/2;return Array.from({length:96},(e,i)=>{const s=i/96*Math.PI*2;return[o+Math.cos(s)*t,r+Math.sin(s)*t]})}const a=n/2,l=ra(t.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l);return[[-a,-a],[a,-a],[a,a],[-a,a]].map(([t,e])=>[o+t*c-e*h,r+t*h+e*c])}function da(t,e,i){const s=[];for(let n=0;n+1t.length>=3).map(t=>aa(t));return e.length?Be(e[0],...e.slice(1)):null}catch{return null}}const ua=t=>`M ${t.map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`;function _a(t,e){const i=pa(t.filter(t=>t.length>=3)),s=pa(e.filter(t=>t.length>=3));if(!i)return[];if(!s)return[];try{return function(t){const e=[];for(const i of t||[]){if(ga([i])<=1e-6)continue;const t=[];for(const e of i||[]){const i=(e||[]).filter(t=>Array.isArray(t)&&t.length>=2);i.length<4||t.push(ua(i.slice(0,-1)))}t.length&&e.push(t.join(" "))}return e}(We(i,s))}catch{return[]}}function ma(t,e){if(!e.length)return[aa(t)];try{const i=pa(e);if(i)return je(aa(t),i)}catch{}let i=[aa(t)];for(const t of e)if(!(t.length<3))try{i=je(i,aa(t))}catch{}return i}function ga(t){let e=0;for(const i of t||[])if(i?.length){e+=mi(i[0]||[]);for(let t=1;t=4&&e.push(t.slice(0,-1).map(t=>[t[0],t[1]]))}return e}function va(t,e){let i=!1;for(let s=0,n=e.length-1;st[1]!=l>t[1]&&t[0]<(a-o)*(t[1]-r)/(l-r||1e-12)+o&&(i=!i)}return i}function ya(t,e,i){if(Math.hypot(t.center[0]-e.center[0],t.center[1]-e.center[1])>i)return!1;if(Math.abs(oa(t.cm)-oa(e.cm))>1e-6)return!1;if(t.shape!==e.shape)return!0;if("circle"===t.shape||"circle"===e.shape)return!0;const s=Math.abs(ra(t.angle)-ra(e.angle));return Math.min(s,90-s)<=1e-6}const ba=1e3;function wa(t,e){const i=Number(t),s=Number.isFinite(i)&&i>0?i:1,n=s>=1?e:e*s,o=s>=1?e/s:e;return{x:(e-n)/2,y:(e-o)/2,w:n,h:o}}const ka=.01,$a=100;function xa(t,e=1e3){const i=wa(t?.plan_aspect,e),s=Number(t?.plan_scale),n=Number.isFinite(s)&&s>0?Math.min($a,Math.max(ka,s)):1,o=Number(t?.plan_scale_x),r=Number(t?.plan_scale_y),a=Number.isFinite(o)&&o>0?Math.min($a,Math.max(ka,o)):n,l=Number.isFinite(r)&&r>0?Math.min($a,Math.max(ka,r)):n,c=Number(t?.plan_x),h=Number(t?.plan_y),d=Zo(t?.plan_angle);return{x:i.x+(Number.isFinite(c)?Ea(c):0)*e,y:i.y+(Number.isFinite(h)?Ea(h):0)*e,w:i.w*a,h:i.h*l,...d?{angle:d}:{}}}function Sa(t){if(null==t.x||null==t.y)return{x:t.x,y:t.y,w:t.w,h:t.h};const e=Number(t.w)||0,i=Number(t.h)||0;return{x:e<0?t.x+e:t.x,y:i<0?t.y+i:t.y,w:Math.abs(e),h:Math.abs(i)}}function Ma(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=ba,i=function(t){return Array.isArray(t)&&4===t.length&&t.every(t=>Number.isFinite(t))&&t[2]>1e-6&&t[3]>1e-6?t:[0,0,1,1]}(t.view_box);return{id:t.id,title:t.title,vb:[i[0]*ba,i[1]*e,i[2]*ba,i[3]*e],bg:t.plan_url?{href:ys(t.plan_url),...xa(t,ba)}:null,rooms:(t.rooms||[]).map(t=>{const i={...t,...Sa(t)};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*ba:void 0,y:null!=i.y?i.y*e:void 0,w:null!=i.w?i.w*ba:void 0,h:null!=i.h?i.h*e:void 0,poly:i.poly?i.poly.map(t=>[t[0]*ba,t[1]*e]):void 0}}),room_drafts:(t.room_drafts||[]).map(t=>({id:t.id,points:(t.points||[]).map(t=>[t[0]*ba,t[1]*e]),segments:(t.segments||[]).map(t=>({cm:Number(t.cm)}))})),partitions:(t.partitions||[]).map(t=>({id:t.id,a:[t.a[0]*ba,t.a[1]*e],b:[t.b[0]*ba,t.b[1]*e],cm:Number(t.cm)})),wall_columns:(t.wall_columns||[]).map(t=>({id:t.id,shape:"circle"===t.shape?"circle":"square",center:[t.center[0]*ba,t.center[1]*e],cm:Number(t.cm),..."circle"===t.shape?{}:{angle:ra(t.angle)}}))}}):[]}const Ca=5e3,Da=Ca*ba,Ta=240,Ra=ba/Ta,Pa=1/Ta;function Aa(t){if(!Number.isFinite(t))return t;const e=Math.round(t*Ta/ba)*ba/Ta;return Math.abs(e-t)<=1e-9*Ra?t:e}function za(t){return{x:Aa(t.x),y:Aa(t.y)}}function Ia(t){return Number.isFinite(t)?Math.min(Da,Math.max(-Da,t)):0}function Ea(t){return Number.isFinite(t)?Math.min(Ca,Math.max(-Ca,t)):0}const Fa=1/3,Na=200;function Oa(t){let e=1/0,i=1/0,s=-1/0,n=-1/0;for(const o of t){const t=Number(o[0]),r=Number(o[1]);Number.isFinite(t)&&Number.isFinite(r)&&(ts&&(s=t),r>n&&(n=r))}return e>s?null:{minX:e,minY:i,maxX:s,maxY:n}}function La(t){return t.poly&&t.poly.length?Oa(t.poly):null==t.x||null==t.y?null:Oa([[t.x,t.y],[t.x+(t.w||0),t.y+(t.h||0)]])}function Ha(t,e){const i=[];for(const e of t.rooms||[]){const t=La(e);t&&i.push(t)}if(t.bg){const e=Oa(sr(t.bg));e&&i.push(e)}for(const t of e||[])if(Array.isArray(t)){const e=Oa([t]);e&&i.push(e)}else i.push(t);return i}const qa=t=>{if(!t.length)return 0;const e=[...t].sort((t,e)=>t-e),i=e.length>>1;return e.length%2?e[i]:(e[i-1]+e[i])/2},Ua=t=>{let e=1/0,i=1/0,s=-1/0,n=-1/0;for(const o of t)o.minXs&&(s=o.maxX),o.maxY>n&&(n=o.maxY);return e>s||i>n?null:{x:e,y:i,w:s-e,h:n-i}};function Ba(t,e){let{x:i,y:s,w:n,h:o}=t;n<30&&(i=i+n/2-100,n=Na),o<30&&(s=s+o/2-100,o=Na);const r=Math.max(n,o)*e;return{x:i-r,y:s-r,w:n+2*r,h:o+2*r}}function Wa(t,e={}){const i=e.pad??.05,s=e.k??10,n=e.minSpread??50,o=t.filter(t=>Number.isFinite(t.minX)&&Number.isFinite(t.minY)&&Number.isFinite(t.maxX)&&Number.isFinite(t.maxY)&&Math.abs(t.minX)<=Da&&Math.abs(t.maxX)<=Da&&Math.abs(t.minY)<=Da&&Math.abs(t.maxY)<=Da);if(!o.length)return{core:null,all:null,outliers:0};const r=Ua(o);if(o.length<4){const t=Ba(r,i);return{core:t,all:t,outliers:0}}const a=o.map(t=>(t.minX+t.maxX)/2),l=o.map(t=>(t.minY+t.maxY)/2),c=qa(a),h=qa(l),d=o.map((t,e)=>Math.max(Math.abs(a[e]-c),Math.abs(l[e]-h))),p=Math.max(((t,e)=>t.length?t[Math.min(t.length-1,Math.max(0,Math.round(e*(t.length-1))))]:0)([...d].sort((t,e)=>t-e),.75),n),u=d.map(t=>t>s*p),_=u.filter(Boolean).length,m=_&&_<=o.length*Fa?o.filter((t,e)=>!u[e]):o;return{core:Ba(Ua(m)||r,i),all:Ba(r,i),outliers:m===o?0:_}}function ja(t,e,i=.05){const s=Wa(Ha(t,e),{pad:i});if(s.core)return s.core;const n=t.vb&&4===t.vb.length&&t.vb[2]>0&&t.vb[3]>0?t.vb:[0,0,ba,ba];return{x:n[0],y:n[1],w:n[2],h:n[3]}}function Ga(t){const e=ja(t);return{x:e.x+e.w/2,y:e.y+e.h/2}}function Va(t){const e=[];for(const i of t.rooms||[]){const t=La(i);t&&e.push(t)}const i=Wa(e,{pad:0}).core;return i?Math.max(ba,Math.min(Da,Math.max(i.w,i.h))):ba}function Ka(t,e,i,s=1){const n=Number(i),o=Number.isFinite(s)&&s>0?s:1;return!Number.isFinite(n)||n<=0?t*o:t*Va(e)*o/n}const Ya=[1,2,5,10,20,50,100,200,500,1e3];function Ja(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),n=Math.min(...i);return{x:s,y:n,w:Math.max(...e)-s,h:Math.max(...i)-n}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Za(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}const Xa=["furniture","appliance","sanitary","other"],Qa=[{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]]}],tl=new Map(Qa.map(t=>[t.id,t]));function el(t){return t&&tl.get(t)||null}function il(t){return Qa.filter(e=>e.group===t)}function sl(t,e,i=Ra,s=1e3){const n=Number(e)>0?Number(e):5;return(Number(t)||0)/n*i/s}const nl=5e-4,ol=Ca;function rl(t){return Number.isFinite(t)?Math.max(nl,Math.min(ol,t)):nl}const al=t=>{const e=Math.round(1e3*t)/1e3;return Object.is(e,-0)?"0":String(e)};function ll(t,e,i){const s=el(t);if(!(s&&e>0&&i>0))return"";const n=t=>al(t*e),o=t=>al(t*i),r=[];for(const t of s.g)if("r"===t[0]){const[,e,i,s,a]=t;r.push(`M${n(e)} ${o(i)}H${n(e+s)}V${o(i+a)}H${n(e)}Z`)}else if("l"===t[0]){const[,e,i,s,a]=t;r.push(`M${n(e)} ${o(i)}L${n(s)} ${o(a)}`)}else if("e"===t[0]){const[,s,a,l,c]=t;r.push(`M${n(s-l)} ${o(a)}A${al(l*e)} ${al(c*i)} 0 0 1 ${n(s+l)} ${o(a)}A${al(l*e)} ${al(c*i)} 0 0 1 ${n(s-l)} ${o(a)}Z`)}else{const e=t.slice(1);if(e.length<4)continue;let i=`M${n(e[0])} ${o(e[1])}`;for(let t=2;t+1{let e=(t%360+360)%360;return e>180&&(e-=360),e};function hl(t,e,i,s,n,o=0){let r=null,a=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=((t-s)*d+(e-l)*p)/u;m=Math.max(0,Math.min(1,m));let g=s+m*d,f=l+m*p;const v=Math.hypot(t-g,e-f);if(!(v0){const t=Math.round(m*_/o)*o;g=s+t/_*d,f=l+t/_*p}r={cx:g+y*(i/2),cy:f+b*(i/2),angle:cl(180*Math.atan2(-y,b)/Math.PI),dist:v}}return r}function dl(t,e,i){const s=i;return{a:[t[0]/s,t[1]/s],b:[e[0]/s,e[1]/s]}}function pl(t,e){const i=e;return[t.a[0]*i,t.a[1]*i,t.b[0]*i,t.b[1]*i]}function ul(t){return Array.isArray(t)&&t.length>=2&&Number.isFinite(Number(t[0]))&&Number.isFinite(Number(t[1]))}function _l(t){if(!Array.isArray(t))return[];const e=[];for(const i of t){if(!i||"object"!=typeof i)continue;const t=i;if(!ul(t.a)||!ul(t.b))continue;const s=[Number(t.a[0]),Number(t.a[1])],n=[Number(t.b[0]),Number(t.b[1])];Math.hypot(n[0]-s[0],n[1]-s[1])<.001||e.push({a:s,b:n})}return e}function ml(t,e){const i=e[0],s=e[1],n=e[2]-i,o=e[3]-s,r=n*n+o*o;if(r<1e-18){return{q:[i,s],t:0,d:Math.hypot(t[0]-i,t[1]-s)}}let a=((t[0]-i)*n+(t[1]-s)*o)/r;a=Math.max(0,Math.min(1,a));const l=[i+a*n,s+a*o];return{q:l,t:a,d:Math.hypot(t[0]-l[0],t[1]-l[1])}}function gl(t,e){return ml(t,e).q}function fl(t,e,i,s,n){const o=ml(t,e).q;let r=o,a=1/0;for(const t of i){const e=Math.hypot(o[0]-t[0],o[1]-t[1]);e<=n&&e0?s:1,m=Math.round(u/_)*_,g=Math.max(0,Math.min(p,m))/p;return[l+h*g,c+d*g]}function vl(t,e){const i=[],s=(t||[]).filter(t=>t?.id);for(let t=0;tt?.id),r=_l(e);if(r.length)return Cl(r,t,i,s).map(t=>pl(t,i));if(!n)return[];const a=[],l=(t,e)=>(t.open_to||[]).includes(e.id)||(e.open_to||[]).includes(t.id);for(let t=0;tdl([t[0],t[1]],[t[2],t[3]],e))}function wl(t,e,i,s){for(const e of t||[])e.open_to&&delete e.open_to;if(!i.length)return;const n=(e||[]).filter(t=>t?.id);new Map(n.map(t=>[t.id,t]));const o=new Map((t||[]).filter(t=>t?.id).map(t=>[t.id,t])),r=(t,e)=>{const i=o.get(t),s=o.get(e);i&&s&&((i.open_to||[]).includes(e)||(i.open_to=[...i.open_to||[],e]),(s.open_to||[]).includes(t)||(s.open_to=[...s.open_to||[],t]))};for(let t=0;tSs(i,t)<4*s)){r(n[t].id,n[e].id);break}}}for(const e of t||[])e.open_to&&!e.open_to.length&&delete e.open_to}function kl(t,e,i,s=0){const n=e[2]-e[0],o=e[3]-e[1],r=Math.hypot(n,o);if(!(r>1e-9))return null;const a=n/r,l=o/r,c=(t[0]-e[0])*a+(t[1]-e[1])*l;if(c<-s||c>r+s)return null;return Math.abs((t[0]-e[0])*l-(t[1]-e[1])*a)>i?null:Ss(t,e)}function $l(t,e,i){const s=t[2]-t[0],n=t[3]-t[1],o=e[2]-e[0],r=e[3]-e[1],a=Math.hypot(s,n),l=Math.hypot(o,r);return a>1e-9&&l>1e-9&&(!(Math.abs(s/a*(r/l)-n/a*(o/l))>1e-6)&&(Ss([e[0],e[1]],t)<=4*i||Ss([t[0],t[1]],e)<=4*i))}function xl(t,e,i){if(!t.length)return null;t.sort((t,e)=>t.distance-e.distance);const s=t[0];for(let n=1;ne)break;if(o.semantic!==s.semantic||!$l(s.seg,o.seg,i))return"ambiguous"}return s}function Sl(t,e,i,s,n=1,o=15){const r=function(t,e,i,s,n=1,o=15){const[r,a]=ur([e[0],e[1]],[e[2],e[3]]);let l=0,c=1/0;const h=[(e[0]+e[2])/2,(e[1]+e[3])/2];for(const e of i){const[i,o]=ur([e[0],e[1]],[e[2],e[3]]);if(Math.abs(r*o-a*i)>.05)continue;const d=kr(t,[e[0],e[1]],[e[2],e[3]],s,n);if(!(d>0))continue;const p=[(e[0]+e[2])/2,(e[1]+e[3])/2],u=Math.hypot(p[0]-h[0],p[1]-h[1]);u0?l:o}(t,e,i,s,n,o);return Mr(t,[e[0],e[1]],[e[2],e[3]],r,s,n)}function Ml(t,e,i){const s=[[t[0],t[1]],[t[2],t[3]]],n=[(t[0]+t[2])/2,(t[1]+t[3])/2];for(const o of e)if(!(Ss(n,o)>Math.hypot(t[2]-t[0],t[3]-t[1])&&Ss([o[0],o[1]],t)>i))for(const e of[[o[0],o[1]],[o[2],o[3]]])Ss(e,t)<=2*i&&s.push(e);return s}function Cl(t,e,i,s){const n=_l(t);if(!n.length)return[];const o=vl(e,s);if(!o.length)return[];const r=[],a=Math.max(4*s,1e-6);for(const t of n){const e=pl(t,i),n=e[0],l=e[1],c=e[2]-n,h=e[3]-l,d=Math.hypot(c,h);if(d4*s||o>4*s)continue;const r=(t[0]-n)*p+(t[1]-l)*u,c=(t[2]-n)*p+(t[3]-l)*u,h=Math.max(0,Math.min(r,c)),m=Math.min(d,Math.max(r,c));if(m-ht.lo-e.lo||t.hi-e.hi);const i=[];for(const t of e){const e=i[i.length-1];e&&t.lo<=e.hi+a?e.hi=Math.max(e.hi,t.hi):i.push({...t})}for(const e of i){const i=[n+p*e.lo,l+u*e.lo],s=[n+p*e.hi,l+u*e.hi];Math.hypot(s[0]-i[0],s[1]-i[1])i.pair===t&&Math.abs(i.ux*r-i.uy*o)<=1e-6&&Math.abs((e[0]-i.origin[0])*i.uy-(e[1]-i.origin[1])*i.ux)<=c&&Math.abs((e[2]-i.origin[0])*i.uy-(e[3]-i.origin[1])*i.ux)<=c);h||(h={pair:t,origin:[e[0],e[1]],ux:o,uy:r,ranges:[]},l.push(h));const d=(e[0]-h.origin[0])*h.ux+(e[1]-h.origin[1])*h.uy,p=(e[2]-h.origin[0])*h.ux+(e[3]-h.origin[1])*h.uy;h.ranges.push({lo:Math.min(d,p),hi:Math.max(d,p)})}const h=[];for(const t of l){t.ranges.sort((t,e)=>t.lo-e.lo||t.hi-e.hi);const e=[];for(const i of t.ranges){const t=e[e.length-1];t&&i.lo<=t.hi+a?t.hi=Math.max(t.hi,i.hi):e.push({...i})}for(const s of e){const e=[t.origin[0]+t.ux*s.lo,t.origin[1]+t.uy*s.lo],n=[t.origin[0]+t.ux*s.hi,t.origin[1]+t.uy*s.hi];Math.hypot(n[0]-e[0],n[1]-e[1])>=a&&h.push(dl(e,n,i))}}return h}function Dl(t,e,i,s){const n=_l(t);if(!n.length)return[];if(e.length!==i.length)return n;const o=[];for(const t of n){const n=pl(t,s),r=[n[0],n[1]],a=[n[2],n[3]];let l=r,c=a;for(let t=0;t{const e=ml(t,[s[0],s[1],n[0],n[1]]);if(e.d>.001)return t;const i=h[0]-o[0],r=h[1]-o[1];return[o[0]+i*e.t,o[1]+r*e.t]};if(ml([(r[0]+a[0])/2,(r[1]+a[1])/2],[s[0],s[1],n[0],n[1]]).d<.01){l=d(r),c=d(a);break}}o.push(dl(l,c,s))}return o}const Tl=15e3,Rl=new WeakMap;class Pl{constructor(t,e=()=>Date.now()){this.onUpdate=t,this.now=e,this.fallbackAuthority={},this.referenceOwner={},this.referenced=new Set,this.queued=new Set,this.disposed=!1,this.sharedUpdate=()=>{this.disposed||this.onUpdate()}}bind(t){const e=((t,e)=>{const i=t?.connection||t;return!i||"object"!=typeof i&&"function"!=typeof i?e:i})(t,this.fallbackAuthority);return this.shared&&this.authority===e||(this.shared&&(this.shared.listeners.delete(this.sharedUpdate),this.shared.references.delete(this.referenceOwner)),this.authority=e,this.shared=(t=>{let e=Rl.get(t);return e||(e={signed:{},queued:new Set,inFlight:new Map,retry:new Map,listeners:new Set,references:new Map},Rl.set(t,e)),e})(e),this.disposed||(this.shared.listeners.add(this.sharedUpdate),this.shared.references.set(this.referenceOwner,new Set(this.referenced)))),this.shared}start(t,e){this.disposed=!1,this.referenced=new Set(e());const i=this.bind(t());i.listeners.add(this.sharedUpdate),i.references.set(this.referenceOwner,new Set(this.referenced)),this.stopTimer(),this.resignTimer=setInterval(()=>this.resign(t(),e()),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 t of this.queued)this.shared.queued.delete(t)}this.queued.clear()}stopTimer(){void 0!==this.resignTimer&&clearInterval(this.resignTimer),this.resignTimer=void 0}display(t,e){const i=ys(e);if(!i.startsWith("/api/houseplan/content/"))return i;const s=this.bind(t);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 e=[...this.queued];this.queued.clear();for(const t of e)i.queued.delete(t);this.sign(t,e)},30))}sign(t,e){if(!e.length||!t?.callWS)return;const i=this.bind(t);for(const s of function(t,e){const i=Math.max(1,Math.floor(e)),s=[];for(let e=0;e{const s=i.inFlight.get(t);return!(void 0!==s&&e-s{const e=this.now();let s=0,o=!1;for(const r of n){const n=t?.urls?.[r];if("string"==typeof n&&n){const t=i.signed[r];t?.loaded&&t.url!==n?(t.pending={url:n,at:e},this.preloadReplacement(i,r,n,e)):(i.signed[r]={url:n,at:e,loaded:t?.url===n&&!!t.loaded},o=!0),i.retry.delete(r),s++}else this.backOff(i,r)}if(s&&(this.trimShared(i),o))for(const t of[...i.listeners])t()}).catch(()=>{for(const t of n)this.backOff(i,t)}).finally(()=>{for(const t of n)i.inFlight.get(t)===e&&i.inFlight.delete(t)})}}preloadReplacement(t,e,i,s){const n=()=>{const n=t.signed[e];if(n?.pending?.url===i){t.signed[e]={url:i,at:s,loaded:!0},t.retry.delete(e),this.trimShared(t);for(const e of[...t.listeners])e()}},o=()=>{const s=t.signed[e];s?.pending?.url===i&&(delete s.pending,this.backOff(t,e))};if("undefined"==typeof Image)return void n();const r=new Image;r.onload=()=>{("function"==typeof r.decode?r.decode():Promise.resolve()).then(n).catch(o)},r.onerror=o,r.src=i}preloadCurrentImage(t,e,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,r=o=>{const r=t.signed[e];if(r===i&&delete r.preload,o&&r===i&&r.url===s){r.loaded=!0;for(const e of[...t.listeners])e();n(!0)}else o||r!==i||this.backOff(t,e),n(!1)};o.onload=()=>{("function"==typeof o.decode?o.decode():Promise.resolve()).then(()=>r(!0)).catch(()=>r(!1))},o.onerror=()=>r(!1),o.src=s}),i.preload}prepareImage(t,e){const i=ys(e);if(!i.startsWith("/api/houseplan/content/"))return Promise.resolve(!0);const s=this.bind(t),n=()=>{const e=s.signed[i];return e&&this.now()-e.at{let e=!1,i=null;const o=i=>{e||(e=!0,clearTimeout(a),s.listeners.delete(r),t(i))},r=()=>{e||i||(i=n(),i&&i.then(o).catch(()=>o(!1)))},a=setTimeout(()=>o(!1),Tl);s.listeners.add(r),r()})}backOff(t,e){const i=t.retry.get(e)?.delay||0,s=Math.min(6e4,i?2*i:2e3);t.retry.set(e,{notBefore:this.now()+s,delay:s})}trimShared(t){const e=Object.entries(t.signed);e.length<=96||(e.sort((t,e)=>e[1].at-t[1].at),t.signed=Object.fromEntries(e.slice(0,96)))}resign(t,e){const i=this.bind(t);this.referenced=new Set(e),i.references.set(this.referenceOwner,new Set(this.referenced));const s=this.now(),n=new Set;for(const t of i.references.values())for(const e of t)n.add(e);const o={};for(const[t,e]of Object.entries(i.signed))n.has(t)&&s-e.at!!o[t]&&!o[t].pending))}markLoaded(t,e){const i=ys(e);if(!i.startsWith("/api/houseplan/content/"))return;const s=this.bind(t).signed[i];if(s&&!s.loaded){s.loaded=!0;for(const e of[...this.bind(t).listeners])e()}}isReady(t,e){const i=ys(e);if(!i.startsWith("/api/houseplan/content/"))return!0;const s=this.bind(t).signed[i];return!!s&&this.now()-s.at{if(null==t||""===t)return null;const e=Number(t);return Number.isFinite(e)?e:null},El=t=>{const e=t,i=Il(Array.isArray(e)?e[0]:e?.x),s=Il(Array.isArray(e)?e[1]:e?.y);return null==i||null==s?null:[i,s]};function Fl(t){if(!Array.isArray(t))return null;const e=[];for(const i of t){const t=El(i);if(!t)return null;e.push(t)}if(e.length>1){const t=e[0],i=e[e.length-1];t[0]===i[0]&&t[1]===i[1]&&e.pop()}if(e.length<3)return null;let i=0,s=0,n=0;for(let t=0;tt+e[0],0)/e.length,e.reduce((t,e)=>t+e[1],0)/e.length]:[s/(3*i),n/(3*i)]}function Nl(t){if(!Array.isArray(t))return null;const e=[];for(const i of t){const t=El(i);if(!t)return null;e.push(t)}if(e.length>1){const t=e[0],i=e[e.length-1];t[0]===i[0]&&t[1]===i[1]&&e.pop()}const i=Fl(e);if(!i)return null;const s=e.map(t=>t[0]),n=e.map(t=>t[1]);return{centroid:i,bbox:[Math.min(...s),Math.min(...n),Math.max(...s),Math.max(...n)]}}function Ol(t){let e=t;if(e&&"object"==typeof e&&!Array.isArray(e)){const t=e;e=t.path??t.points??e}if(!Array.isArray(e)||!e.length)return[];let i=(e.some(t=>(t=>Array.isArray(t)?t.length>=2&&!Array.isArray(t[0])&&("object"!=typeof t[0]||null==t[0]):!!t&&"object"==typeof t&&("x"in t||"y"in t))(t))?[e]:e.filter(Array.isArray)).map(t=>t.map(El).filter(t=>null!=t)).filter(t=>t.length>=2);i.length>64&&(i=i.slice(-64));if(i.reduce((t,e)=>t+e.length,0)<=4e3)return i.map(t=>t.slice());const s=4e3-2*i.length,n=i.map(t=>t.length-2),o=n.reduce((t,e)=>t+e,0),r=n.map(t=>s*t/o),a=r.map(Math.floor);let l=s-a.reduce((t,e)=>t+e,0);const c=r.map((t,e)=>({index:e,fraction:t-Math.floor(t)})).sort((t,e)=>e.fraction-t.fraction||t.index-e.index);for(let t=0;tfunction(t,e){if(e>=t.length)return t.slice();if(e<=2)return[t[0],t[t.length-1]];const i=[];for(let s=0;st.slice()),i=e[e.length-1];return i?(i.length>2?i.pop():e.pop(),e):[]}function ql(t){if(!t)return null;const e=t.vacuum_position||t.robot_position||null,i=e&&null!=Il(e.x)&&null!=Il(e.y)?{x:Il(e.x),y:Il(e.y),a:Il(e.a??e.angle??e.theta)}:null,s=Ol(t.path?.path??t.path?.points??t.path),n=[],o=t.rooms,r=Array.isArray(o)?o.map((t,e)=>[String(t?.id??e),t]):o&&"object"==typeof o?Object.entries(o):[];for(const[t,e]of r){if(!e||"object"!=typeof e)continue;const i=String(e.name??e.label??"").trim(),s=[[Il(e.cx),Il(e.cy)],[Il(e.center?.x),Il(e.center?.y)],[Il(e.x),Il(e.y)]].find(([t,e])=>null!=t&&null!=e);let o=s?.[0]??null,r=s?.[1]??null;const a=Nl(e.outline),l=Il(e.x0),c=Il(e.y0),h=Il(e.x1),d=Il(e.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)||a?.bbox||null;if(null!=o&&null!=r||!a||([o,r]=a.centroid),null!=o&&null!=r||!p||(o=(p[0]+p[2])/2,r=(p[1]+p[3])/2),i&&null!=o&&null!=r){const e={id:t,name:i,cx:o,cy:r};p&&([e.x0,e.y0,e.x1,e.y1]=p),n.push(e)}}const a=function(t){return String(t.map_name??t.current_map??t.map_index??t.selected_map??"default")}(t);return i||n.length||s.length?{pos:i,path:s,rooms:n,mapId:a}:null}function Ul(t,e,i){if(!t||!e)return null;const s=e.attributes||{},n=t.split(".")[0]||"",o=null!=i?.platform?String(i.platform):null,r=!!El(s.vacuum_position??s.robot_position),a=!(!s.rooms||"object"!=typeof s.rooms),l=Ol(s.path?.path??s.path?.points??s.path).length>0,c=[s.map_name,s.current_map,s.map_index,s.selected_map].some(t=>null!=t),h="xiaomi_cloud_map_extractor"===o,d=a||l||c;let p=null;if(r?p="compatible":h?p="known_xcme_incomplete":d?p="partial":"camera"===n&&(p="camera"),!p)return null;const u=r?"camera"===n?300:200:h?100:d?50:0;return{entityId:t,name:String(s.friendly_name||t),platform:o,category:p,hasPosition:r,hasRooms:a,hasPath:l,hasMapId:c,score:u}}const Bl=t=>t.toLowerCase().replace(/[\s_\-.,]+/g,"");function Wl(t,e){const i=new Map(e.map(t=>[Bl(t.name),t])),s=[],n=[];for(const e of t){const t=i.get(Bl(e.name));t&&(s.push([[e.cx,e.cy],[t.cx,t.cy]]),n.push(e.name))}if(s.length<3)return null;const o=function(t){if(t.length<3)return null;let e=0,i=0,s=0,n=0,o=0,r=0,a=0,l=0,c=0,h=0,d=0,p=0;for(const[[u,_],[m,g]]of t){if(![u,_,m,g].every(Number.isFinite))return null;e+=u*u,i+=u*_,s+=u,n+=_*_,o+=_,r+=1,a+=u*m,l+=_*m,c+=m,h+=u*g,d+=_*g,p+=g}const u=[e,i,s,i,n,o,s,o,r],_=t=>{const[e,i,s,n,o,r,a,l,c]=u,h=e*(o*c-r*l)-i*(n*c-r*a)+s*(n*l-o*a);if(!Number.isFinite(h)||Math.abs(h)<1e-9)return null;const d=[(o*c-r*l)/h,(s*l-i*c)/h,(i*r-s*o)/h,(r*a-n*c)/h,(e*c-s*a)/h,(s*n-e*r)/h,(n*l-o*a)/h,(i*a-e*l)/h,(e*o-i*n)/h];return[d[0]*t[0]+d[1]*t[1]+d[2]*t[2],d[3]*t[0]+d[4]*t[1]+d[5]*t[2],d[6]*t[0]+d[7]*t[1]+d[8]*t[2]]},m=_([a,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:zl(o,s)}:null}function jl(t,e,i){const s=t[t.length-1];if(s&&s[0]===e[0]&&s[1]===e[1])return t;if(t.push(e),t.length<=600)return t;let n=function(t,e){if(t.length<3)return t.slice();const i=new Uint8Array(t.length);i[0]=i[t.length-1]=1;const s=[[0,t.length-1]];for(;s.length;){const[n,o]=s.pop(),[r,a]=t[n],[l,c]=t[o],h=l-r,d=c-a,p=Math.hypot(h,d)||1e-9;let u=0,_=-1;for(let e=n+1;eu&&(u=i,_=e)}_>0&&u>e&&(i[_]=1,s.push([n,_],[_,o]))}const n=[];for(let e=0;e600&&(n=n.filter((t,e)=>e%2==0||e===n.length-1)),n}function Gl(t){return"cleaning"===t||"returning"===t||"on"===t}const Vl={0:[1,0],90:[0,1],180:[-1,0],270:[0,-1]};function Kl(t){const[e,i]=Vl[t.rot]||[1,0],s=t.mir?-1:1;return[t.s*e*s,-t.s*i,t.ox,t.s*i*s,t.s*e,t.oy]}function Yl(t){const e=t[0]*t[4]-t[1]*t[3];if(!Number.isFinite(e)||Math.abs(e)<1e-12)return null;const i=e<0,s=Math.sqrt(Math.abs(e));let n=180*Math.atan2(-t[1],t[4])/Math.PI;return n=(90*Math.round(n/90)%360+360)%360,{ox:t[2],oy:t[5],s:s,rot:n,mir:i}}function Jl(t,e,i,s){const[n,o]=Al(Kl(e),i,s),r=Kl({...t,ox:0,oy:0}),[a,l]=Al(r,i,s);return{...t,ox:n-a,oy:o-l}}function Zl(t){const e=t?.trail_mode;return"never"===e||"cleaning"===e||"always"===e?e:!1===t?.trail?"never":"cleaning"}const Xl=1e-5;function Ql(t){const e=t.map(()=>[]);for(let i=0;i=1-1e-9||p<=1e-9||p>=1-1e-9||(e[i].push(d),e[r].push(p))}}const i=[];for(let s=0;st-e),1];for(let t=1;t{const i=e[2]-e[0],s=e[3]-e[1],n=i*i+s*s;if(!(n>0))return Math.hypot(t[0]-e[0],t[1]-e[1]);const o=Math.max(0,Math.min(1,((t[0]-e[0])*i+(t[1]-e[1])*s)/n));return Math.hypot(t[0]-(e[0]+o*i),t[1]-(e[1]+o*s))},ic=(t,e,i,s)=>{const n=s[2]-s[0],o=s[3]-s[1],r=e*o-i*n;if(Math.abs(r)<1e-12)return 1/0;const a=s[0]-t[0],l=s[1]-t[1],c=(a*o-l*n)/r;if(!(c>1e-9))return 1/0;const h=(a*i-l*e)/r;return h<-1e-9||h>1+1e-9?1/0:c};function sc(t,e,i,s=96){if(!(e>0&&Number.isFinite(t[0])&&Number.isFinite(t[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=ec(t,s);if(i<1e-7)return[];i>e||n.push(s)}const o=[],r=Math.max(12,Math.round(s));for(let t=0;tt-e);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 r=e;for(const e of n){const i=ic(t,s,o,e);i=3?l:[]}class nc extends ct{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return co(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[],e=this._lang;return[{name:"title",selector:{text:{}}},t.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:t}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:[{value:"",label:ho(e,"editor.lang_auto")},{value:"en",label:ho(e,"editor.lang_en")},{value:"ru",label:ho(e,"editor.lang_ru")}]}}},{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 t=this._lang,e={title:ho(t,"editor.title"),default_floor:ho(t,"editor.default_floor"),language:ho(t,"editor.language"),icon_size:ho(t,"editor.icon_size"),show_temperature:ho(t,"editor.show_temperature"),live_states:ho(t,"editor.live_states"),show_signal:ho(t,"editor.show_signal"),kiosk:ho(t,"editor.kiosk"),cycle:ho(t,"editor.cycle")};return B`e[t.name]||t.name} @value-changed=${this._valueChanged} - >`}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}function oc(t){const e=Ca(t.cfg),i={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(i[t.area]=e.id);const s=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):Et,o=Ft(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:Nt);return Fo({hass:t.hass,registry:t.registry,areaToSpace:i,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:s,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>dn(t.lang,e),iconRules:o})}function nc(t){const e=Ca(t.cfg).find(e=>e.id===t.spaceId);if(!e)return null;const i=Yi(t.cfg.spaces.find(e=>e.id===t.spaceId)),s=Xi(t.cfg.settings),o=t.iconSize??2.5,n=o>8?2.5:o,r=t.registry?no(t.hass,t.registry):t.hass,a=(t.devices||oc(t)).filter(e=>e.space===t.spaceId),l=a.filter(t=>!t.hidden),c=function(t,e,i){const s={},o=i/100*Ka(e)*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const n=Za(i),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,d=l/Math.max(Math.ceil(e.length/c),1),p=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));ki(p,n,o,.5*r),e.forEach((t,e)=>s[t.id]=Ia(p[e]))}return s}(a,e,n),h=[];for(const e of l){const i=t.layout[e.id];if(i&&i.s===t.spaceId){const t=i.x*wa,e=i.y*wa;h.push({minX:t,minY:e,maxX:t,maxY:e})}}const d=t.cfg.spaces.find(e=>e.id===t.spaceId)||{},p=Array.isArray(d.walls)?d.walls:[],u=Number(d.cell_cm)>0?Number(d.cell_cm):5,_=function(t,e,i){const s=[];for(const o of t.partitions||[]){const t=ca(o.a,o.b,o.cm,e,i);t&&s.push(t)}for(const o of t.room_drafts||[])s.push(...da(o,e,i));for(const o of t.wall_columns||[])s.push(ha(o,e,i));return s}(e,u,Ra);for(const t of _){const e=t.map(t=>t[0]),i=t.map(t=>t[1]);e.length&&h.push({minX:Math.min(...e),minY:Math.min(...i),maxX:Math.max(...e),maxY:Math.max(...i)})}const m=Wa(e,h),g=[m.x,m.y,m.w,m.h],f=new Map(e.rooms.map(t=>{const e=bs(i.fill,t);return[t,ts(e,"lqi"===e&&t.area?Ho(r,a,t.area):null,"light"===e?Mo(xo(r,a,t)):"none","temp"===e&&t.area?qo(r,a,t.area):null,i.tempMin,i.tempMax,s,Ki(i.customFill,t))]})),v=e.rooms.filter(t=>t.area||i.showBorders||"none"!==bs(i.fill,t)).map(t=>{let s="room "+(e.bg?"overlay":"yard"),o="";const n=bs(i.fill,t);if(i.showBorders||"none"!==n){s+=" styled";const e=[`--room-stroke:${i.color}`,`--room-stroke-op:${i.showBorders?i.opacity:0}`],n=f.get(t)||null;n?(s+=" filled",e.push(`--room-fill:${n.color}`,`--room-fill-op:${n.opacity.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),o=e.join(";")}const r=t.id||W,a=t.area||W,l=t.poly?j``:j``;return l}),y=e.rooms.filter(t=>{const e=f.get(t);return ws(i.glow,t)&&(!e||e.opacity<=0)}).map(t=>t.poly?j``}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}function oc(t){const e=Ma(t.cfg),i={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(i[t.area]=e.id);const s=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):Et,n=Nt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:Ft);return Nn({hass:t.hass,registry:t.registry,areaToSpace:i,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:s,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>ho(t.lang,e),iconRules:n})}function rc(t){const e=Ma(t.cfg).find(e=>e.id===t.spaceId);if(!e)return null;const i=Yi(t.cfg.spaces.find(e=>e.id===t.spaceId)),s=Xi(t.cfg.settings),n=t.iconSize??2.5,o=n>8?2.5:n,r=t.registry?rn(t.hass,t.registry):t.hass,a=(t.devices||oc(t)).filter(e=>e.space===t.spaceId),l=a.filter(t=>!t.hidden),c=function(t,e,i){const s={},n=i/100*Va(e)*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const o=Ja(i),r=.1*Math.min(o.w,o.h),a=o.w-2*r,l=o.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,d=l/Math.max(Math.ceil(e.length/c),1),p=e.map((t,e)=>({x:o.x+r+h*(e%c+.5),y:o.y+r+d*(Math.floor(e/c)+.5)}));ki(p,o,n,.5*r),e.forEach((t,e)=>s[t.id]=za(p[e]))}return s}(a,e,o),h=[];for(const e of l){const i=t.layout[e.id];if(i&&i.s===t.spaceId){const t=i.x*ba,e=i.y*ba;h.push({minX:t,minY:e,maxX:t,maxY:e})}}const d=t.cfg.spaces.find(e=>e.id===t.spaceId)||{},p=Array.isArray(d.walls)?d.walls:[],u=Number(d.cell_cm)>0?Number(d.cell_cm):5,_=function(t,e,i){const s=[];for(const n of t.partitions||[]){const t=ca(n.a,n.b,n.cm,e,i);t&&s.push(t)}for(const n of t.room_drafts||[])s.push(...da(n,e,i));for(const n of t.wall_columns||[])s.push(ha(n,e,i));return s}(e,u,Ra);for(const t of _){const e=t.map(t=>t[0]),i=t.map(t=>t[1]);e.length&&h.push({minX:Math.min(...e),minY:Math.min(...i),maxX:Math.max(...e),maxY:Math.max(...i)})}const m=ja(e,h),g=[m.x,m.y,m.w,m.h],f=new Map(e.rooms.map(t=>{const e=bs(i.fill,t);return[t,ts(e,"lqi"===e&&t.area?Hn(r,a,t.area):null,"light"===e?Mn(xn(r,a,t)):"none","temp"===e&&t.area?qn(r,a,t.area):null,i.tempMin,i.tempMax,s,Ki(i.customFill,t))]})),v=e.rooms.filter(t=>t.area||i.showBorders||"none"!==bs(i.fill,t)).map(t=>{let s="room "+(e.bg?"overlay":"yard"),n="";const o=bs(i.fill,t);if(i.showBorders||"none"!==o){s+=" styled";const e=[`--room-stroke:${i.color}`,`--room-stroke-op:${i.showBorders?i.opacity:0}`],o=f.get(t)||null;o?(s+=" filled",e.push(`--room-fill:${o.color}`,`--room-fill-op:${o.opacity.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),n=e.join(";")}const r=t.id||G,a=t.area||G,l=t.poly?W``:W``;return l}),y=e.rooms.filter(t=>{const e=f.get(t);return ws(i.glow,t)&&(!e||e.opacity<=0)}).map(t=>t.poly?W``:j``:G}`}if("ellipse"===n.kind){const i=(n.x+n.w/2)*t,h=(n.y+n.h/2)*e,d=Zo(n.angle);return W` - ${s?j``:W}`}if("furniture"===o.kind){const i=o.w*t,h=o.h*e,d=cl(o.symbol,i,h);if(!d)return W;const p=Number(o.angle)||0,u=o.x*t+i/2,_=o.y*e+h/2,m=`${p?`rotate(${p} ${u} ${_}) `:""}translate(${o.x*t} ${o.y*e})`;return j``:G}`}if("furniture"===n.kind){const i=n.w*t,h=n.h*e,d=ll(n.symbol,i,h);if(!d)return G;const p=Number(n.angle)||0,u=n.x*t+i/2,_=n.y*e+h/2,m=`${p?`rotate(${p} ${u} ${_}) `:""}translate(${n.x*t} ${n.y*e})`;return W` - ${s?j``:W}`}if("text"===o.kind){const i=this._decorTextUnits(o),s=function(t){return String(t??"").replace(/\r\n?/g,"\n").split("\n")}(function(t,e,i,s=()=>!0){const o=t??"";let n=!1;const r=o.replace(/\{([^{}\r\n]+)\}/g,(t,e)=>{const o=Ni(e);return o?(n=!0,s(o.entity||"")?Hi(i,o):Ii):t});if(n)return r;const a=(e?.entity||"").trim();if(!a)return o;if(!s(a)){const t=o.indexOf(Ei);return t>=0?o.slice(0,t)+Ii+o.slice(t+2):o?`${o} ${Ii}`:Ii}const l=Hi(i,e),c=o.indexOf(Ei);return c>=0?o.slice(0,c)+l+o.slice(c+2):o?`${o} ${l}`:l}(o.text,o,this.hass,t=>this._planEntityAvailable(t))),a=o.x*t,h=o.y*e,d=Number(o.angle)||0,p=h-(s.length-1)*i*1.2/2;return j``:G}`}if("text"===n.kind){const i=this._decorTextUnits(n),s=function(t){return String(t??"").replace(/\r\n?/g,"\n").split("\n")}(function(t,e,i,s=()=>!0){const n=t??"";let o=!1;const r=n.replace(/\{([^{}\r\n]+)\}/g,(t,e)=>{const n=Fi(e);return n?(o=!0,s(n.entity||"")?Hi(i,n):Ii):t});if(o)return r;const a=(e?.entity||"").trim();if(!a)return n;if(!s(a)){const t=n.indexOf(Ei);return t>=0?n.slice(0,t)+Ii+n.slice(t+2):n?`${n} ${Ii}`:Ii}const l=Hi(i,e),c=n.indexOf(Ei);return c>=0?n.slice(0,c)+l+n.slice(c+2):n?`${n} ${l}`:l}(n.text,n,this.hass,t=>this._planEntityAvailable(t))),a=n.x*t,h=n.y*e,d=Number(n.angle)||0,p=h-(s.length-1)*i*1.2/2;return W`${s.map((t,e)=>j`${t}`)}`}return W});let n=W;const r=this._decorDraft;if(r){const t=this._decorStyle,e=Qn(t.widthCm,this._cellCm,this._gridPitch);if("line"===r.kind)n=j``;else{const i=Math.min(r.a[0],r.b[0]),s=Math.min(r.a[1],r.b[1]),o=Math.abs(r.b[0]-r.a[0]),a=Math.abs(r.b[1]-r.a[1]);n="rect"===r.kind?j``:j``}}return j`${o}${n}`}get _editorToolbarGroups(){return[]}_renderEditorGroupLauncher(t){return this._editorSecondary.renderGroupLauncher(t,this._editorToolbarGroups,this._editorSecondaryCopy)}get _editorSecondaryContextId(){const t=`editor:${this._mode}:${this._space}:${this._cfgEpoch}`,e=this._editorSecondary.activeGroup(this._editorToolbarGroups);if(e)return`${t}:group:${e.id}:${this._editorSecondary.groupGeneration}`;if("plan"===this._mode){const e=this._physicalSel;return e?`${t}:selection:${e.kind}:${e.id}:${e.segment??""}`:`${t}:tool:${this._tool}:${this._path.length}:${this._openWallAnchor?1:0}`}return"decor"===this._mode?"furniture"===this._decorTool?`${t}:palette:furniture:${this._furnPalette?.symbol||"none"}`:"select"===this._decorTool&&this._decorSel?`${t}:selection:decor:${this._decorSel}`:`${t}:tool:${this._decorTool}:${this._bdMoved?1:0}:${this._bdDrag?1:0}`:`${t}:none`}_runEditorContext(t,e){this._editorSecondary.runContext(t,this._editorSecondaryContextId,e)}_renderEditorGroupModel(t){return this._editorSecondary.renderGroupModel(t,this._editorSecondaryContextId,this._editorSecondaryCopy)}_renderDrawWallControl(){return B``:G} + `:G} ${this._kiosk&&this._kioskDots&&this._model.length>1?B`
${this._model.map(t=>B``)} -
`:W} - ${this._kioskDialog?this._renderKioskDialog():W} + `:G} + ${this._kioskDialog?this._renderKioskDialog():G} ${this._vacFit?B`
${this._t("vac.fit_hint")} -
`:W} + `:G} ${this._tapConfirm?B`this._tapConfirm=null}>

${this._tapConfirm.text}

@@ -4681,30 +4751,30 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 ${this._t("btn.run")} -
`:W} - ${this._toast?B``:W} + `:G} + ${this._toast?B``:G} - `}_vacCandidateStatus(t,e){const i=this._bindingStatus("entity:"+t);if("ha_disabled"===i.kind)return"disabled";if("orphaned"===i.kind)return"missing";if("unverified"===i.kind)return"unverified";const s=this.hass?.states?.[t];return"unavailable"===s?.state?"unavailable":e?.hasPosition?"ok":"unsupported"}_vacOpenAllCameras(t){const e=this._haRegistry.entities||{},i=[];for(const[t,s]of Object.entries(this.hass?.states||{})){if(!t.startsWith("camera."))continue;const o=ql(t,s,e[t]);o&&i.push(o)}this._vacAllCameraCache={devId:t.id,candidates:i},this._vacAllCamerasFor=t.id}_vacSourceResolution(t,e=!1){const i=t.marker?.vacuum,s="string"==typeof i?.source&&!!i.source,o=new Set(t.entities||[]),n=new Set(o);s&&n.add(i.source);const r=this._haRegistry.entities||{},a=[];for(const t of n){const e=this.hass?.states?.[t],o=ql(t,e,r[t]);o?a.push(o):s&&t===i.source&&a.push({entityId:t,name:String(e?.attributes?.friendly_name||t),platform:r[t]?.platform?String(r[t].platform):null,category:t.startsWith("camera.")?"camera":"partial",hasPosition:!1,hasRooms:!1,hasPath:!1,hasMapId:!1,score:0})}if(e&&this._vacAllCameraCache?.devId===t.id){const t=new Set(a.map(t=>t.entityId));for(const e of this._vacAllCameraCache.candidates)t.has(e.entityId)||a.push(e)}const l={};for(const t of a)l[t.entityId]=this._vacCandidateStatus(t.entityId,t);return function(t,e,i,s){const o="string"==typeof t&&t.length>0,n=new Set(e),r=Array.from(i).sort((e,i)=>o&&e.entityId===t?-1:o&&i.entityId===t?1:i.score-e.score||e.entityId.localeCompare(i.entityId));if(o){const e=r.find(e=>e.entityId===t);return{entityId:t,status:s[t]||(e?.hasPosition?"ok":"unverified"),pinned:!0,candidates:r}}const a=r.find(t=>n.has(t.entityId)&&t.hasPosition&&"ok"===s[t.entityId]);return a?{entityId:a.entityId,status:"ok",pinned:!1,candidates:r}:{entityId:null,status:"none",pinned:!1,candidates:r}}(i?.source,o,a,l)}_vacSource(t){if(!1===t.marker?.vacuum?.live)return null;const e=this._vacSourceResolution(t);return"ok"===e.status||"unsupported"===e.status?e.entityId:null}_vacEntity(t){return t.primary?.startsWith("vacuum.")?t.primary:(t.entities||[]).find(t=>t.startsWith("vacuum."))||null}_isVacDev(t){return!!this._vacEntity(t)}_activitySourceKey(t){return Yo(this._planHass,t,!1!==this._config?.show_temperature)}_activitySnapshot(t){const e=Wo(this._planHass,t);return{samples:e.samples,sourceKey:Yo(this._planHass,t,!1!==this._config?.show_temperature,e)}}_stampActivity(t,e,i){let s=this._activityRt.get(t);s||(s={sources:i||"",last:{},flashTs:0,flashKind:null,timer:0,gen:0},this._activityRt.set(t,s)),null!=i&&(s.sources=i),s.flashTs&&Date.now()-s.flashTs<3300&&"event"===s.flashKind&&"transition"===e||(s.flashTs=Date.now(),s.flashKind=e,s.gen++,clearTimeout(s.timer),s.timer=window.setTimeout(()=>this.requestUpdate(),3360))}_syncActivityRuntime(){const t=new Map;if(!this.hass)return t;const e=new Set;for(const i of this._devices){if(i.hidden)continue;if("static_icon"===Si(i.marker?.display))continue;e.add(i.id);const s=this._activitySnapshot(i);t.set(i.id,s);const{samples:o,sourceKey:n}=s;let r=this._activityRt.get(i.id);if(r){if(r.sources!==n){clearTimeout(r.timer),r.sources=n,r.last={},r.flashTs=0,r.flashKind=null;for(const t of o)r.last[t.eid]=t.state}}else{r={sources:n,last:{},flashTs:0,flashKind:null,timer:0,gen:0};for(const t of o)r.last[t.eid]=t.state;this._activityRt.set(i.id,r)}}for(const[t,i]of this._activityRt)e.has(t)||(clearTimeout(i.timer),this._activityRt.delete(t));return t}_activityTick(){if(!this.hass)return;const t=this._syncActivityRuntime();for(const e of this._devices){if(e.hidden)continue;if("static_icon"===Si(e.marker?.display))continue;const i=t.get(e.id)||this._activitySnapshot(e),{samples:s,sourceKey:o}=i,n=this._activityRt.get(e.id);if(!n||n.sources!==o)continue;"transition"===n.flashKind&&s.some(t=>"transition"===t.activity)&&(clearTimeout(n.timer),n.flashTs=0,n.flashKind=null);let r=null;for(const t of s){const e=Bs(n.last[t.eid],t);("event"===e||!r&&e)&&(r=e),n.last[t.eid]=t.state}r&&this._stampActivity(e.id,r,o)}}_vacTick(){if(this.hass)for(const t of this._devices){if(t.hidden||!this._isVacDev(t))continue;if("static_icon"===Si(t.marker?.display)){this._vacRt.delete(t.id);continue}const e=this._vacSource(t);if(!e)continue;const i=this._vacEntity(t),s=Gl(this.hass.states[i||""]?.state),o=Hl(this.hass.states[e]?.attributes);let n=this._vacRt.get(t.id);n||(n={trail:[],lastKey:"",lastTs:0,moving:!1,jump:!1,endedTs:0,lastPos:null},this._vacRt.set(t.id,n)),s&&!n.moving&&(n.trail=[],n.lastPos=null);const r="never"!==Jl(t.marker?.vacuum)&&!o?.path.length;!s&&n.moving&&(n.endedTs=Date.now(),r&&n.lastPos&&(n.trail=jl(n.trail,n.lastPos,40)),n.lastPos=null),n.moving=s;const a=o?.pos;if(s&&a){const t=a.x+":"+a.y;if(t!==n.lastKey){const e=Date.now();n.jump=n.lastTs>0&&e-n.lastTs>1e4,n.lastKey=t,n.lastTs=e,r&&n.lastPos&&(n.trail=jl(n.trail,n.lastPos,40)),n.lastPos=[a.x,a.y]}}}}_vacEnsureMarker(t){const e=this._serverCfg;if(!e)return null;e.markers=e.markers||[];const i=e.markers.find(e=>e.id===t.id);if(i)return i;if("device"!==t.bindingKind&&"entity"!==t.bindingKind||!t.bindingRef)return null;const s={id:t.id,binding:t.bindingKind+":"+t.bindingRef,space:t.space||null,area:t.area||null,hidden:!!t.hidden};return e.markers.push(s),s}_renderVacSection(t){const e=this._devices.find(e=>e.id===t.devId);if(!e||!this._isVacDev(e))return W;const i=e.marker?.vacuum||{},s=this._vacAllCamerasFor===e.id,o=this._vacSourceResolution(e,s),n=o.entityId,r=o.candidates.find(t=>t.entityId===n)||null,a=n?Hl(this.hass?.states[n]?.attributes):null,l=!1!==i.live&&("ok"===o.status||"unsupported"===o.status),c=this._vacPlanRoomAnchors(e.space),h=a?function(t,e){const i=new Set(e.map(Ul).filter(Boolean)),s=new Set;for(const e of t){const t=Ul(e.name||"");t&&i.has(t)&&s.add(t)}return s.size}(a.rooms,c.map(t=>t.name)):0,d=!!(l&&a&&h>=3),p=Object.keys(i.calibration||{}),u=t=>{const i=this._vacEnsureMarker(e);i&&(i.vacuum={...i.vacuum||{},...t},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate())},_=new Set(e.entities||[]),m=o.candidates.filter(t=>_.has(t.entityId)||t.entityId===i.source),g=s?o.candidates.filter(t=>t.entityId.startsWith("camera.")&&!m.some(e=>e.entityId===t.entityId)):[],f=t=>[t.hasPosition?this._t("vac.cap_position"):"",t.hasRooms?this._t("vac.cap_rooms_short"):"",t.hasPath?this._t("vac.cap_path"):"",t.hasMapId?this._t("vac.cap_map"):""].filter(Boolean).join(" · ")||this._t("vac.cap_none"),v=t=>B` - `,y=o.candidates.some(t=>_.has(t.entityId)&&"known_xcme_incomplete"===t.category)||!!o.pinned&&!!n?.startsWith("camera.")&&!r?.hasPosition;return B` + `,y=n.candidates.some(t=>_.has(t.entityId)&&"known_xcme_incomplete"===t.category)||!!n.pinned&&!!o?.startsWith("camera.")&&!r?.hasPosition;return B`
-
${this._t("vac.diag_source")}${n||this._t("vac.source_none")}
- ${r?.platform?B`
${this._t("vac.diag_platform")}${r.platform}
`:W} -
${this._t("vac.diag_status")}${this._t(`vac.source_status_${o.status}`)}
+
${this._t("vac.diag_source")}${o||this._t("vac.source_none")}
+ ${r?.platform?B`
${this._t("vac.diag_platform")}${r.platform}
`:G} +
${this._t("vac.diag_status")}${this._t(`vac.source_status_${n.status}`)}
${this._t("vac.diag_position")}${a?.pos?this._t("common.yes"):this._t("common.no")}
${this._t("vac.diag_rooms")}${Bi(this._t("vac.diag_rooms_value"),{total:String(a?.rooms.length||0),matched:String(h),readiness:this._t(h>=3?"vac.autocal_ready":"vac.autocal_not_ready")})}
${this._t("vac.diag_path")}${a?.path.length?this._t("common.yes"):this._t("common.no")}
${this._t("vac.diag_map")}${a?.mapId??"default"}
- ${"missing"===o.status||"disabled"===o.status||"unverified"===o.status?B`
- ${this._t(`vac.source_banner_${o.status}`)} + ${"missing"===n.status||"disabled"===n.status||"unverified"===n.status?B`
+ ${this._t(`vac.source_banner_${n.status}`)} -
`:W} +
`:G} ${y?B`
${this._t("vac.xcme_hint")}
attributes:
@@ -4712,11 +4782,11 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
   - rooms
   - path
   - map_name
-
`:W} +
`:G}
${this._t("vac.choose_source")}
- @@ -4725,13 +4795,13 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 @toggle=${t=>{t.currentTarget.open?this._vacOpenAllCameras(e):(this._vacAllCamerasFor=null,this._vacAllCameraCache=null)}}> ${this._t("vac.all_cameras")}
${this._t("vac.all_cameras_warn")}
- ${s?g.length?g.map(v):B`
${this._t("vac.all_cameras_empty")}
`:W} + ${s?g.length?g.map(v):B`
${this._t("vac.all_cameras_empty")}
`:G}
@@ -4744,35 +4814,35 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 - ${p.length?B`
${Bi(this._t("vac.cal_maps"),{maps:p.join(", ")})}
`:W} - `:W} - `}_vacMapId(t,e){const i=this._vacEntity(t),s=i?this.hass?.states[i]?.attributes?.selected_map:null;return o=e.mapId,n=s,"default"!==o?o:null!=n?String(n):"default";var o,n}_vacSaveMatrix(t,e,i,s){const o=this._devices.find(e=>e.id===t),n=o?this._vacEnsureMarker(o):this._serverCfg?.markers?.find(e=>e.id===t);if(!n)return!1;const r={...n.vacuum||{}};return r.source=e,r.calibration={...r.calibration||{},[i]:s.map(t=>Number(t.toFixed(6)))},n.vacuum=r,this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate(),!0}_vacPlanRoomAnchors(t){return(this._spaceModel(t||void 0)?.rooms||[]).map(t=>({room:t,poly:Je(t)})).filter(({room:t,poly:e})=>t.name&&e).map(({room:t,poly:e})=>{const i=El(e);return i?{name:String(t.name),cx:i[0],cy:i[1]}:null}).filter(Boolean)}_vacAutoCalibrate(t){const e=this._vacSource(t),i=e?Hl(this.hass?.states[e]?.attributes):null;if(!e||!i||i.rooms.length<3)return void this._showToast(this._t("vac.autocal_no_rooms"));const s=this._vacPlanRoomAnchors(t.space),o=Bl(i.rooms,s);if(!o)return void this._showToast(this._t("vac.autocal_no_match"));const n=this._serverCfg?.spaces?.find(e=>e.id===t.space),r=Number(n?.cell_cm),a=Number.isFinite(r)&&r>0?r:5,l=function(t,e,i){return![t,e,i].every(Number.isFinite)||t<0||e<=0||i<=0?Number.POSITIVE_INFINITY:t/e*i}(o.residual,this._gridPitch,a),c=this._vacMapId(t,i);l>40?this._vacCalConfirm={markerId:t.id,source:e,mapId:c,matrix:o.matrix,rooms:o.matched.length,error:Ke(l,"mi"===this.hass?.config?.unit_system?.length)}:this._vacSaveMatrix(t.id,e,c,o.matrix)&&this._showToast(Bi(this._t("vac.autocal_done"),{rooms:String(o.matched.length)}))}_vacApplyCalibrationProposal(t){const e=this._vacCalConfirm;if(e){if(this._vacCalConfirm=null,t){const t=this._devices.find(t=>t.id===e.markerId),i=Kl(e.matrix);if(!t||!i)return;return this._markerDialog=null,t.space!==this._space&&(this._space=t.space),void(this._vacFit={markerId:e.markerId,source:e.source,mapId:e.mapId,p:i,drag:null})}this._vacSaveMatrix(e.markerId,e.source,e.mapId,e.matrix)&&this._showToast(Bi(this._t("vac.autocal_done"),{rooms:String(e.rooms)}))}}_vacStartFit(t){const e=this._vacSource(t),i=e?Hl(this.hass?.states[e]?.attributes):null;if(!e||!i)return void this._showToast(this._t("vac.cal_need_pos"));const s=this._vacMapId(t,i),o=t.marker?.vacuum?.calibration?.[s],n=this._spaceModel(t.space),r=n?.vb||[0,0,eh,eh],a=o&&6===o.length&&Kl(o)||function(t,e){const i=[],s=[];for(const e of t)null!=e.x0?(i.push(e.x0,e.x1),s.push(e.y0,e.y1)):(i.push(e.cx),s.push(e.cy));if(!i.length)return{ox:e[0]+e[2]/2,oy:e[1]+e[3]/2,s:e[2]/1e4,rot:0,mir:!0};const o=Math.min(...i),n=Math.max(...i),r=Math.min(...s),a=Math.max(...s),l=Math.max(n-o,a-r)||1,c={ox:0,oy:0,s:.6*Math.min(e[2],e[3])/l,rot:0,mir:!0},h=Vl(c),[d,p]=Rl(h,(o+n)/2,(r+a)/2);return c.ox=e[0]+e[2]/2-d,c.oy=e[1]+e[3]/2-p,c}(i.rooms,r);this._markerDialog=null,t.space!==this._space&&(this._space=t.space),this._vacFit={markerId:t.id,source:e,mapId:s,p:a,drag:null}}_vacFitSave(){const t=this._vacFit;if(!t)return;const e=this._vacSaveMatrix(t.markerId,t.source,t.mapId,Vl(t.p));this._vacFit=null,e&&this._showToast(this._t("vac.cal_done"))}_vacFitTurn(t){const e=this._vacFit;if(!e)return;const i=Hl(this.hass?.states[e.source]?.attributes),s=this._vacGhostCentre(i?.rooms||[]),o={...e.p,...t};this._vacFit={...e,p:Yl(o,e.p,s[0],s[1])}}_vacGhostCentre(t){const e=[],i=[];for(const s of t)e.push(s.x0??s.cx,s.x1??s.cx),i.push(s.y0??s.cy,s.y1??s.cy);return e.length?[(Math.min(...e)+Math.max(...e))/2,(Math.min(...i)+Math.max(...i))/2]:[0,0]}_vacDelta(t,e,i){const s=this._stageEl,o=s?.clientWidth||1,n=s?.clientHeight||1;return[e/o*t.w,i/n*t.h]}_vacFitPointer(t,e){const i=this._vacFit;if(!i)return;if(t.stopPropagation(),"pointerdown"===t.type){const e=t.target,s=e.getAttribute?.("data-corner");try{t.currentTarget.setPointerCapture?.(t.pointerId)}catch{}return void(this._vacFit={...i,drag:s?{kind:"scale",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:Number(s.split(",")[0]),fy:Number(s.split(",")[1])}:{kind:"move",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:0,fy:0}})}const s=i.drag;if(s){if("pointermove"===t.type){const[o,n]=this._vacDelta(e,t.clientX-s.sx,t.clientY-s.sy);if("move"===s.kind)this._vacFit={...i,p:{...s.p0,ox:s.p0.ox+o,oy:s.p0.oy+n}};else{const t=Hl(this.hass?.states[i.source]?.attributes),e=this._vacGhostCentre(t?.rooms||[]),r=Vl(s.p0),[a,l]=Rl(r,e[0],e[1]),[c,h]=Rl(r,s.fx,s.fy),d=Math.hypot(a-c,l-h)||1,[p,u]=[2*a-c,2*l-h],_=Math.hypot(p+2*o-c,u+2*n-h)/2,m=Math.max(.05,_/d),g={...s.p0,s:s.p0.s*m};this._vacFit={...i,p:Yl(g,s.p0,s.fx,s.fy)}}return}"pointerup"!==t.type&&"pointercancel"!==t.type||(this._vacFit={...i,drag:null})}}_renderVacFit(t){const e=this._vacFit;if(!e)return W;const i=Hl(this.hass?.states[e.source]?.attributes);if(!i)return W;const s=Vl(e.p),o=[],n=[],r=[];for(const t of i.rooms){if(null==t.x0)continue;const e=[[t.x0,t.y0],[t.x1,t.y0],[t.x1,t.y1],[t.x0,t.y1]].map(([t,e])=>Rl(s,t,e));e.forEach(([t,e])=>{n.push(t),r.push(e)});const[i,a]=Rl(s,t.cx,t.cy);o.push(j` - ${t.name}`)}let a=W;if(i.pos){const[e,o]=Rl(s,i.pos.x,i.pos.y);a=j``}const l=[];if(n.length){const e=(()=>{const t=s[0]*s[4]-s[1]*s[3];return(e,i)=>[(s[4]*(e-s[2])-s[1]*(i-s[5]))/t,(-s[3]*(e-s[2])+s[0]*(i-s[5]))/t]})(),i=Math.min(...n),o=Math.max(...n),a=Math.min(...r),c=Math.max(...r),h=.022*t.w,d=h/4;for(const[t,s,n,r]of[[i,a,o,c],[o,a,i,c],[o,c,i,a],[i,c,o,a]]){const i=e(n,r);l.push(j`${Bi(this._t("vac.cal_maps"),{maps:p.join(", ")})}`:G} + `:G} + `}_vacMapId(t,e){const i=this._vacEntity(t),s=i?this.hass?.states[i]?.attributes?.selected_map:null;return n=e.mapId,o=s,"default"!==n?n:null!=o?String(o):"default";var n,o}_vacSaveMatrix(t,e,i,s){const n=this._devices.find(e=>e.id===t),o=n?this._vacEnsureMarker(n):this._serverCfg?.markers?.find(e=>e.id===t);if(!o)return!1;const r={...o.vacuum||{}};return r.source=e,r.calibration={...r.calibration||{},[i]:s.map(t=>Number(t.toFixed(6)))},o.vacuum=r,this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate(),!0}_vacPlanRoomAnchors(t){return(this._spaceModel(t||void 0)?.rooms||[]).map(t=>({room:t,poly:Je(t)})).filter(({room:t,poly:e})=>t.name&&e).map(({room:t,poly:e})=>{const i=Fl(e);return i?{name:String(t.name),cx:i[0],cy:i[1]}:null}).filter(Boolean)}_vacAutoCalibrate(t){const e=this._vacSource(t),i=e?ql(this.hass?.states[e]?.attributes):null;if(!e||!i||i.rooms.length<3)return void this._showToast(this._t("vac.autocal_no_rooms"));const s=this._vacPlanRoomAnchors(t.space),n=Wl(i.rooms,s);if(!n)return void this._showToast(this._t("vac.autocal_no_match"));const o=this._serverCfg?.spaces?.find(e=>e.id===t.space),r=Number(o?.cell_cm),a=Number.isFinite(r)&&r>0?r:5,l=function(t,e,i){return![t,e,i].every(Number.isFinite)||t<0||e<=0||i<=0?Number.POSITIVE_INFINITY:t/e*i}(n.residual,this._gridPitch,a),c=this._vacMapId(t,i);l>40?this._vacCalConfirm={markerId:t.id,source:e,mapId:c,matrix:n.matrix,rooms:n.matched.length,error:Ke(l,"mi"===this.hass?.config?.unit_system?.length)}:this._vacSaveMatrix(t.id,e,c,n.matrix)&&this._showToast(Bi(this._t("vac.autocal_done"),{rooms:String(n.matched.length)}))}_vacApplyCalibrationProposal(t){const e=this._vacCalConfirm;if(e){if(this._vacCalConfirm=null,t){const t=this._devices.find(t=>t.id===e.markerId),i=Yl(e.matrix);if(!t||!i)return;return this._markerDialog=null,t.space!==this._space&&(this._space=t.space),void(this._vacFit={markerId:e.markerId,source:e.source,mapId:e.mapId,p:i,drag:null})}this._vacSaveMatrix(e.markerId,e.source,e.mapId,e.matrix)&&this._showToast(Bi(this._t("vac.autocal_done"),{rooms:String(e.rooms)}))}}_vacStartFit(t){const e=this._vacSource(t),i=e?ql(this.hass?.states[e]?.attributes):null;if(!e||!i)return void this._showToast(this._t("vac.cal_need_pos"));const s=this._vacMapId(t,i),n=t.marker?.vacuum?.calibration?.[s],o=this._spaceModel(t.space),r=o?.vb||[0,0,oh,oh],a=n&&6===n.length&&Yl(n)||function(t,e){const i=[],s=[];for(const e of t)null!=e.x0?(i.push(e.x0,e.x1),s.push(e.y0,e.y1)):(i.push(e.cx),s.push(e.cy));if(!i.length)return{ox:e[0]+e[2]/2,oy:e[1]+e[3]/2,s:e[2]/1e4,rot:0,mir:!0};const n=Math.min(...i),o=Math.max(...i),r=Math.min(...s),a=Math.max(...s),l=Math.max(o-n,a-r)||1,c={ox:0,oy:0,s:.6*Math.min(e[2],e[3])/l,rot:0,mir:!0},h=Kl(c),[d,p]=Al(h,(n+o)/2,(r+a)/2);return c.ox=e[0]+e[2]/2-d,c.oy=e[1]+e[3]/2-p,c}(i.rooms,r);this._markerDialog=null,t.space!==this._space&&(this._space=t.space),this._vacFit={markerId:t.id,source:e,mapId:s,p:a,drag:null}}_vacFitSave(){const t=this._vacFit;if(!t)return;const e=this._vacSaveMatrix(t.markerId,t.source,t.mapId,Kl(t.p));this._vacFit=null,e&&this._showToast(this._t("vac.cal_done"))}_vacFitTurn(t){const e=this._vacFit;if(!e)return;const i=ql(this.hass?.states[e.source]?.attributes),s=this._vacGhostCentre(i?.rooms||[]),n={...e.p,...t};this._vacFit={...e,p:Jl(n,e.p,s[0],s[1])}}_vacGhostCentre(t){const e=[],i=[];for(const s of t)e.push(s.x0??s.cx,s.x1??s.cx),i.push(s.y0??s.cy,s.y1??s.cy);return e.length?[(Math.min(...e)+Math.max(...e))/2,(Math.min(...i)+Math.max(...i))/2]:[0,0]}_vacDelta(t,e,i){const s=this._stageEl,n=s?.clientWidth||1,o=s?.clientHeight||1;return[e/n*t.w,i/o*t.h]}_vacFitPointer(t,e){const i=this._vacFit;if(!i)return;if(t.stopPropagation(),"pointerdown"===t.type){const e=t.target,s=e.getAttribute?.("data-corner");try{t.currentTarget.setPointerCapture?.(t.pointerId)}catch{}return void(this._vacFit={...i,drag:s?{kind:"scale",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:Number(s.split(",")[0]),fy:Number(s.split(",")[1])}:{kind:"move",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:0,fy:0}})}const s=i.drag;if(s){if("pointermove"===t.type){const[n,o]=this._vacDelta(e,t.clientX-s.sx,t.clientY-s.sy);if("move"===s.kind)this._vacFit={...i,p:{...s.p0,ox:s.p0.ox+n,oy:s.p0.oy+o}};else{const t=ql(this.hass?.states[i.source]?.attributes),e=this._vacGhostCentre(t?.rooms||[]),r=Kl(s.p0),[a,l]=Al(r,e[0],e[1]),[c,h]=Al(r,s.fx,s.fy),d=Math.hypot(a-c,l-h)||1,[p,u]=[2*a-c,2*l-h],_=Math.hypot(p+2*n-c,u+2*o-h)/2,m=Math.max(.05,_/d),g={...s.p0,s:s.p0.s*m};this._vacFit={...i,p:Jl(g,s.p0,s.fx,s.fy)}}return}"pointerup"!==t.type&&"pointercancel"!==t.type||(this._vacFit={...i,drag:null})}}_renderVacFit(t){const e=this._vacFit;if(!e)return G;const i=ql(this.hass?.states[e.source]?.attributes);if(!i)return G;const s=Kl(e.p),n=[],o=[],r=[];for(const t of i.rooms){if(null==t.x0)continue;const e=[[t.x0,t.y0],[t.x1,t.y0],[t.x1,t.y1],[t.x0,t.y1]].map(([t,e])=>Al(s,t,e));e.forEach(([t,e])=>{o.push(t),r.push(e)});const[i,a]=Al(s,t.cx,t.cy);n.push(W` + ${t.name}`)}let a=G;if(i.pos){const[e,n]=Al(s,i.pos.x,i.pos.y);a=W``}const l=[];if(o.length){const e=(()=>{const t=s[0]*s[4]-s[1]*s[3];return(e,i)=>[(s[4]*(e-s[2])-s[1]*(i-s[5]))/t,(-s[3]*(e-s[2])+s[0]*(i-s[5]))/t]})(),i=Math.min(...o),n=Math.max(...o),a=Math.min(...r),c=Math.max(...r),h=.022*t.w,d=h/4;for(const[t,s,o,r]of[[i,a,n,c],[n,a,i,c],[n,c,i,a],[i,c,n,a]]){const i=e(o,r);l.push(W` `)}}return B`this._vacFitPointer(e,t)} @pointermove=${e=>this._vacFitPointer(e,t)} @pointerup=${e=>this._vacFitPointer(e,t)} - @pointercancel=${e=>this._vacFitPointer(e,t)}>${o}${a}${l}`}_vacRafLoop(){this._vacRaf=requestAnimationFrame(()=>{const t=this.renderRoot,e=this._stageEl,i=this._vacLastView,s=t?.querySelectorAll?.(".vacpuck")||[];if(!e||!i||!s.length)return void(this._vacRaf=0);const o=e.getBoundingClientRect();for(const e of s){const s=e.getAttribute("data-mid"),n=e.getBoundingClientRect(),r=i.x+(n.left+n.width/2-o.left)/o.width*i.w,a=i.y+(n.top+n.height/2-o.top)/o.height*i.h;for(const e of t.querySelectorAll(`line.tip[data-mid="${s}"]`))e.setAttribute("x2",r.toFixed(1)),e.setAttribute("y2",a.toFixed(1))}this._vacRafLoop()})}_renderVacuums(t,e){if(this._markup||"decor"===this._mode)return W;const i=this._space+"|"+e.x+"|"+e.y+"|"+e.w+"|"+e.h,s=this._vacJumpOnce||i!==this._vacViewKey;this._vacViewKey=i,this._vacJumpOnce=!1;const o=[],n=[];for(const i of t){if(i.hidden||!this._isVacDev(i))continue;if("static_icon"===Si(i.marker?.display))continue;const t=this._vacSource(i);if(!t)continue;const r=Hl(this.hass?.states[t]?.attributes);if(!r)continue;const a=i.marker?.vacuum?.calibration?.[this._vacMapId(i,r)];if(!a||6!==a.length)continue;const l=this._vacRt.get(i.id),c=l?.moving??!1,h=Jl(i.marker?.vacuum),d="always"===h||"cleaning"===h&&c,p=this._vacSrvTrails[i.id],u=this._vacMapId(i,r),_=p?.current?.map_id===u&&Array.isArray(p.current.points)?p.current:null,m=p?.previous?.map_id===u&&Array.isArray(p.previous.points)?p.previous:null;if("always"===h&&m&&m.points.length>1){const t=m.points.map(([t,e])=>{const[i,s]=Rl(a,t,e);return i.toFixed(1)+","+s.toFixed(1)}).join(" ");n.push(j``)}if(d){const t=Ol(r,_,l?.trail||[]),e=!c||"integration"!==t.source&&"server"!==t.source?t.path:Ll(t.path);if(e.length){const t=e.map(t=>t.map(([t,e],i)=>{const[s,o]=Rl(a,t,e);return`${i?"L":"M"} ${s.toFixed(1)} ${o.toFixed(1)}`}).join(" ")).join(" ");n.push(j``);const s=e[e.length-1];if(c&&s?.length>=2){const t=s[s.length-1],[e,o]=Rl(a,t[0],t[1]),r=e.toFixed(1),l=o.toFixed(1);n.push(j``)}}}if(!c||!r.pos)continue;const[g,f]=Rl(a,r.pos.x,r.pos.y),v=(g-e.x)/e.w*100,y=(f-e.y)/e.h*100,b=l&&l.lastTs>0&&Date.now()-l.lastTs>6e4,w=i.marker?.icon||i.icon||"mdi:robot-vacuum";o.push(B`
this._vacFitPointer(e,t)}>${n}${a}${l}`}_vacRafLoop(){this._vacRaf=requestAnimationFrame(()=>{const t=this.renderRoot,e=this._stageEl,i=this._vacLastView,s=t?.querySelectorAll?.(".vacpuck")||[];if(!e||!i||!s.length)return void(this._vacRaf=0);const n=e.getBoundingClientRect();for(const e of s){const s=e.getAttribute("data-mid"),o=e.getBoundingClientRect(),r=i.x+(o.left+o.width/2-n.left)/n.width*i.w,a=i.y+(o.top+o.height/2-n.top)/n.height*i.h;for(const e of t.querySelectorAll(`line.tip[data-mid="${s}"]`))e.setAttribute("x2",r.toFixed(1)),e.setAttribute("y2",a.toFixed(1))}this._vacRafLoop()})}_renderVacuums(t,e){if(this._markup||"decor"===this._mode)return G;const i=this._space+"|"+e.x+"|"+e.y+"|"+e.w+"|"+e.h,s=this._vacJumpOnce||i!==this._vacViewKey;this._vacViewKey=i,this._vacJumpOnce=!1;const n=[],o=[];for(const i of t){if(i.hidden||!this._isVacDev(i))continue;if("static_icon"===Si(i.marker?.display))continue;const t=this._vacSource(i);if(!t)continue;const r=ql(this.hass?.states[t]?.attributes);if(!r)continue;const a=i.marker?.vacuum?.calibration?.[this._vacMapId(i,r)];if(!a||6!==a.length)continue;const l=this._vacRt.get(i.id),c=l?.moving??!1,h=Zl(i.marker?.vacuum),d="always"===h||"cleaning"===h&&c,p=this._vacSrvTrails[i.id],u=this._vacMapId(i,r),_=p?.current?.map_id===u&&Array.isArray(p.current.points)?p.current:null,m=p?.previous?.map_id===u&&Array.isArray(p.previous.points)?p.previous:null;if("always"===h&&m&&m.points.length>1){const t=m.points.map(([t,e])=>{const[i,s]=Al(a,t,e);return i.toFixed(1)+","+s.toFixed(1)}).join(" ");o.push(W``)}if(d){const t=Ll(r,_,l?.trail||[]),e=!c||"integration"!==t.source&&"server"!==t.source?t.path:Hl(t.path);if(e.length){const t=e.map(t=>t.map(([t,e],i)=>{const[s,n]=Al(a,t,e);return`${i?"L":"M"} ${s.toFixed(1)} ${n.toFixed(1)}`}).join(" ")).join(" ");o.push(W``);const s=e[e.length-1];if(c&&s?.length>=2){const t=s[s.length-1],[e,n]=Al(a,t[0],t[1]),r=e.toFixed(1),l=n.toFixed(1);o.push(W``)}}}if(!c||!r.pos)continue;const[g,f]=Al(a,r.pos.x,r.pos.y),v=(g-e.x)/e.w*100,y=(f-e.y)/e.h*100,b=l&&l.lastTs>0&&Date.now()-l.lastTs>6e4,w=i.marker?.icon||i.icon||"mdi:robot-vacuum";n.push(B`
{t.stopPropagation();const e=this._vacEntity(i);e&&this._openMoreInfo(e)}}> -
`)}return this._vacLastView=e,o.length&&!this._vacRaf&&this._vacRafLoop(),o.length||n.length?B` - ${n.length?j`${n}`:W} - ${o}`:W}_renderDevice(t,e,i=!0){const s=this._pos(t),o=(s.x-e.x)/e.w*100,n=(s.y-e.y)/e.h*100,r=this._devicePresentation(t,i),a=[`left:${o}%`,`top:${n}%`,...zt(r)],l=r.disabledReason,c=r.haDisabled?this._t(`marker.ha_disabled_${l}`):t.userHidden?this._t("marker.hidden_ghost"):t.name,h=[t.model,null!=r.tempText?r.tempText+"°":"",null!=r.humText?r.humText+"%":"",null!=r.lqiText?"LQI "+r.lqiText:""].filter(Boolean).join(" · ");return B`
`)}return this._vacLastView=e,n.length&&!this._vacRaf&&this._vacRafLoop(),n.length||o.length?B` + ${o.length?W`${o}`:G} + ${n}`:G}_renderDevice(t,e,i=!0){const s=this._pos(t),n=(s.x-e.x)/e.w*100,o=(s.y-e.y)/e.h*100,r=this._devicePresentation(t,i),a=[`left:${n}%`,`top:${o}%`,...zt(r)],l=r.disabledReason,c=r.haDisabled?this._t(`marker.ha_disabled_${l}`):t.userHidden?this._t("marker.hidden_ghost"):t.name,h=[t.model,null!=r.tempText?r.tempText+"°":"",null!=r.humText?r.humText+"%":"",null!=r.lqiText?"LQI "+r.lqiText:""].filter(Boolean).join(" · ");return B`
`}_roomArea(t){const e=Je(t);if(!e)return null;const i=this._spaceWalls,s=i.length&&t.id&&jr(this._spaceModel().rooms,t.id,i,this._openPairs().flatMap(t=>t.segs),this._wallKeyPitch,this._cellCm,this._gridPitch,eh)||e,o=this._cleanFloor(t,s),n=this._cellCm/this._gridPitch;return In(o.area*n*n/1e4,"mi"===this.hass?.config?.unit_system?.length)}_roomTemp(t){const e=t.settings?.temp_source;return e?Oo(this._planHass,e,"temp",this._markers):t.area?this._climate().get(t.area)?.temp??null:null}_roomHum(t){const e=t.settings?.hum_source;return e?Oo(this._planHass,e,"hum",this._markers):t.area?this._climate().get(t.area)?.hum??null:null}_climate(){const t=this._serverCfg?.markers,e=this._planHass,i=this._climateCache;if(i&&i.h===e&&i.r===this._iconRules&&i.mk===t)return i.m;const s=function(t,e,i){const s=new Map;if(!t?.entities)return s;const o=Io(i),n=new Set;for(const t of i||[]){if(t?.removed||!0!==t?.use_climate_temp)continue;const e=(t.binding||"").indexOf(":");e>0&&n.add(t.binding.slice(e+1))}const r=new Map;for(const[e,i]of Object.entries(t.entities)){if(i.device_id&&o.devices.has(i.device_id)||!i.device_id&&o.entities.has(e))continue;const s=i.device_id?t.devices?.[i.device_id]:null,a=i.area_id||s?.area_id||null;if(!a)continue;if(i.entity_category)continue;if(!(n.size>0&&e.startsWith("climate.")&&(n.has(e)||i.device_id&&n.has(i.device_id)))){if(Et.has(i.platform))continue;if(Lo.test(e))continue}let l=r.get(a);l||(l=new Map,r.set(a,l));const c=i.device_id||e;let h=l.get(c);if(!h){const o=t.states?.[e];h={name:(s?s.name_by_user||s.name:i.name||o?.attributes?.friendly_name||e)||e,model:s?.model,ents:[]},l.set(c,h)}h.ents.push(e)}for(const[i,o]of r){const r=[],a=[];for(const[i,s]of o){const o=zo(t,s.name,s.model,s.ents,e),l="mdi:thermometer"===o||"mdi:air-filter"===o;if(l){const e=Do(t,s.ents);null!=e&&r.push(e)}if(n.size>0&&(n.has(i)||s.ents.some(t=>n.has(t)))){const e=To(t,s.ents);null!=e&&r.push(e)}if(l||"mdi:water-percent"===o){const e=Ro(t,s.ents);null!=e&&a.push(e)}}(r.length||a.length)&&s.set(i,{temp:r.length?Math.round(r.reduce((t,e)=>t+e,0)/r.length*10)/10:null,hum:a.length?Math.round(a.reduce((t,e)=>t+e,0)/a.length):null})}return s}(e,this._iconRules,t);return this._climateCache={h:e,r:this._iconRules,mk:t,m:s},s}_resetRoomDialogFields(){this._roomEditId=null,this._roomFill="",this._roomCustomFill=null,this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1}_openRoomEdit(t){if(!t.id)return;this._roomEditId=t.id,this._nameSel=t.name||"",this._areaSel=t.area||"",this._roomFill="glow"===t.settings?.fill_mode?"":t.settings?.fill_mode||"";const e=t.settings?.custom_fill;this._roomCustomFill=e&&"object"==typeof e?Vi(e,Yi(this._curSpaceCfg).customFill):null,this._roomTempSrc=t.settings?.temp_source||"",this._roomHumSrc=t.settings?.hum_source||"",this._roomNameScale=ks(t.settings?.name_scale),this._roomLabelScale=ks(t.settings?.label_scale),this._roomSrcOpen=null,this._roomSrcFilter="",this._roomDialog=!0}_roomSettingsFromDialog(){const t={};return this._roomFill&&(t.fill_mode=this._roomFill),this._roomCustomFill&&(t.custom_fill=this._roomCustomFill),this._roomTempSrc&&(t.temp_source=this._roomTempSrc),this._roomHumSrc&&(t.hum_source=this._roomHumSrc),1!==this._roomNameScale&&(t.name_scale=this._roomNameScale),1!==this._roomLabelScale&&(t.label_scale=this._roomLabelScale),Object.keys(t).length?t:null}_saveRoomEdit(){const t=this._curSpaceCfg,e=t?.rooms.find(t=>t.id===this._roomEditId);if(!e)return this._roomDialog=!1,void(this._roomEditId=null);e.name=this._nameSel.trim()||e.name,e.area=this._areaSel||null;const i=e.settings||{},s={...i};this._roomFill?s.fill_mode=this._roomFill:delete s.fill_mode,this._roomCustomFill?s.custom_fill=this._roomCustomFill:delete s.custom_fill,"glow"===i.fill_mode&&"boolean"!=typeof i.glow&&(s.glow=!0),this._roomTempSrc?s.temp_source=this._roomTempSrc:delete s.temp_source,this._roomHumSrc?s.hum_source=this._roomHumSrc:delete s.hum_source,1!==this._roomNameScale?s.name_scale=this._roomNameScale:delete s.name_scale,1!==this._roomLabelScale?s.label_scale=this._roomLabelScale:delete s.label_scale,Object.keys(s).length?e.settings=s:delete e.settings,this._saveConfig(),this._roomDialog=!1,this._roomEditId=null,this._nameSel="",this._areaSel="",this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate(),this._showToast(this._t("toast.room_updated"))}_roomSrcCandidates(){const t=this._planHass,e=Io(this._markers),i=this._roomSrcFilter.trim().toLowerCase(),s=[];for(const o of Object.values(t.devices)){if("service"===o.entry_type)continue;if(e.devices.has(o.id))continue;const t=(o.name_by_user||o.name||o.id).trim();i&&!t.toLowerCase().includes(i)||s.push({value:"device:"+o.id,label:t,sub:o.model||this._t("marker.sub_device")})}for(const[o,n]of Object.entries(t.entities)){if(!o.startsWith("sensor.")||n.hidden)continue;if(Eo(t,o,e))continue;const r=n.name||t.states[o]?.attributes?.friendly_name||o;i&&!(r+" "+o).toLowerCase().includes(i)||s.push({value:"entity:"+o,label:r,sub:o})}return s.sort((t,e)=>t.label.localeCompare(e.label)),s.slice(0,200)}_roomSrcLabel(t){const e=t.indexOf(":"),i=t.slice(0,e),s=t.slice(e+1);return"device"===i?this._fullRegistryHass.devices[s]?.name_by_user||this._fullRegistryHass.devices[s]?.name||s:this._fullRegistryHass.entities[s]?.name||this.hass.states[s]?.attributes?.friendly_name||s}_labelPos(t,e){const i=this._layout["rl_"+(t.id||"")];if(i&&i.s===e)return{x:i.x*eh,y:i.y*eh};const s=this._snap(this._roomCenter(t));return{x:s[0],y:s[1]}}_labelDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=this._labelPos(e,i);this._drag={id:"rl_"+(e.id||""),sx:t.clientX,sy:t.clientY,ox:s.x,oy:s.y,moved:!1},lh(t),this._tip=null}_labelMove(t,e,i){const s="rl_"+(e.id||"");if(!this._drag||this._drag.id!==s)return;const o=this._stageEl;if(!o)return;const n=this._spaceModel(i).vb,r=o.getBoundingClientRect(),a=this._viewOr(n),l=(t.clientX-this._drag.sx)/r.width*a.w,c=(t.clientY-this._drag.sy)/r.height*a.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0);const h=Ea(this._drag.ox+l),d=Ea(this._drag.oy+c);this._savePos({id:s,space:i},h,d)}_labelUp(t){const e="rl_"+(t.id||"");if(!this._drag||this._drag.id!==e)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&window.setTimeout(()=>this._drag=null,0)}_labelScale(t){const e=this._layout["rl_"+(t.id||"")]?.k;return"number"==typeof e&&Number.isFinite(e)?Math.min(3,Math.max(.5,e)):1}_rlResizeDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=t.target.closest(".roomlabel");if(!s)return;const o=s.getBoundingClientRect(),n=o.left+o.width/2,r=o.top+o.height/2,a=Math.max(8,Math.hypot(t.clientX-n,t.clientY-r));this._rlResize={id:"rl_"+(e.id||""),space:i,k0:this._labelScale(e),cx:n,cy:r,d0:a},lh(t)}_rlResizeMove(t){const e=this._rlResize;if(!e)return;t.stopPropagation();const i=Math.max(8,Math.hypot(t.clientX-e.cx,t.clientY-e.cy)),s=Math.min(3,Math.max(.5,e.k0*(i/e.d0))),o=this._layout[e.id];if(o)this._layout={...this._layout,[e.id]:{...o,k:s}};else{const t=e.id.slice(3),i=this._spaceModel(e.space).rooms.find(e=>e.id===t);if(!i)return;const o=this._labelPos(i,e.space);this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/eh,y:o.y/eh,k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomGear(t,e,i){if(!t.id)return W;let s=null;if(t.poly?(s=this._gearPtCache.get(t.poly)||null,s||(s=ci(t.poly),this._gearPtCache.set(t.poly,s))):null!=t.x&&null!=t.y&&(s=[t.x+(t.w||0)/2,t.y+(t.h||0)/2]),!s)return W;const o=(s[0]-i.x)/i.w*100,n=(s[1]-i.y)/i.h*100;return B``}_renderRoomLabel(t,e,i,s){if(!t.name&&!this._markup)return W;const o=this._labelPos(t,e.id),n=(o.x-i.x)/i.w*100,r=(o.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if(t.area||t.settings?.temp_source||t.settings?.hum_source||s.labelLight){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight){const e=(h=xo(this._planHass,this._devices,t)).length?{on:h.filter(t=>t.on).length,total:h.length}:null;if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}var h;return B`
`}_renderRoomLabel(t,e,i,s){if(!t.name&&!this._markup)return G;const n=this._labelPos(t,e.id),o=(n.x-i.x)/i.w*100,r=(n.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if(t.area||t.settings?.temp_source||t.settings?.hum_source||s.labelLight){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight){const e=(h=xn(this._planHass,this._devices,t)).length?{on:h.filter(t=>t.on).length,total:h.length}:null;if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}var h;return B`
this._labelDown(i,t,e.id)} @pointermove=${i=>this._labelMove(i,t,e.id)} @pointerup=${()=>this._labelUp(t)} @@ -4802,38 +4872,38 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 >${t.name||(this._markup?this._t("room.unnamed"):"")}${!this._markup&&t.area?B`{e.stopPropagation(),this._clickRoom(t)}} - @pointerdown=${t=>t.stopPropagation()}>`:W} - ${c.length?B`${c}`:W} + @pointerdown=${t=>t.stopPropagation()}>`:G} + ${c.length?B`${c}`:G} ${"plan"===this._mode?["tl","tr","bl","br"].map(i=>B`this._rlResizeDown(i,t,e.id)} @pointermove=${t=>this._rlResizeMove(t)} @pointerup=${()=>this._rlResizeUp()} - @pointercancel=${()=>this._rlResizeUp()}>`):W} -
`}get _measureAnchor(){return this._markup&&this._cursorPt?"draw"!==this._tool&&"partition"!==this._tool||!this._path.length||this._contourClosed?"split"===this._tool&&this._splitSel?.pts?.length?this._splitSel.pts[this._splitSel.pts.length-1]:null:this._path[this._path.length-1]:null}_renderMeasureLabel(t){const e=this._measureAnchor,i=this._cursorPt,s=(i[0]-t.x)/t.w*100,o=(i[1]-t.y)/t.h*100,n=$s(e,i),r=Math.round(10*n)/10,a=xs(n);return B`
- ${this._fmtLen(e,i)} · ${r}°
`}get _decorMeasure(){const t=this._decorDraft;if(!t||"decor"!==this._mode)return null;const[e,i]=t.a,[s,o]=t.b;if(Math.abs(e-s)<1e-6&&Math.abs(i-o)<1e-6)return null;const n=(e+s)/2,r=(i+o)/2;if("line"===t.kind){const e=$s(t.a,t.b);return{x:n,y:r,on45:xs(e),text:`${this._fmtLen(t.a,t.b)} · ${Math.round(10*e)/10}°`}}const a=this._fmtLen([e,i],[s,i]),l=this._fmtLen([s,i],[s,o]);if("ellipse"===t.kind){const t=this._fmtLen([0,0],[Math.abs(s-e)/2,0]),a=this._fmtLen([0,0],[0,Math.abs(o-i)/2]);return{x:n,y:r,on45:!1,text:Math.abs(Math.abs(s-e)-Math.abs(o-i))<1e-6?`R ${t}`:`Rx ${t} × Ry ${a}`}}return{x:n,y:r,on45:!1,text:`${a} × ${l} · ${In(tr(Math.abs(s-e),this._cellCm,this._gridPitch)*tr(Math.abs(o-i),this._cellCm,this._gridPitch)/1e4,this._imperial)}`}}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=eh,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=Je(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id||"ha_disabled"===e.bindingStatus?.kind)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const e=this._decorMove?.id;return t.push(...this._decorSnapGeometry(e).points),this._decorDraft&&t.push(this._decorDraft.a),t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return j``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,o=null;for(const n of e)if(!(Math.abs(n[0]-t[0])<1e-6&&Math.abs(n[1]-t[1])<1e-6)){if(Math.abs(n[0]-t[0])<=i){const e=Math.abs(n[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!o||e - ${i.map(e=>{const[i,n,r,a]="x"===e.axis?[e.at,e.from[1],e.at,t[1]+Math.sign(t[1]-e.from[1])*o]:[e.from[0],e.at,t[0]+Math.sign(t[0]-e.from[0])*o,e.at];return j` + @pointercancel=${()=>this._rlResizeUp()}>`):G} +
`}get _measureAnchor(){return this._markup&&this._cursorPt?"draw"!==this._tool&&"partition"!==this._tool||!this._path.length||this._contourClosed?"split"===this._tool&&this._splitSel?.pts?.length?this._splitSel.pts[this._splitSel.pts.length-1]:null:this._path[this._path.length-1]:null}_renderMeasureLabel(t){const e=this._measureAnchor,i=this._cursorPt,s=(i[0]-t.x)/t.w*100,n=(i[1]-t.y)/t.h*100,o=$s(e,i),r=Math.round(10*o)/10,a=xs(o);return B`
+ ${this._fmtLen(e,i)} · ${r}°
`}get _decorMeasure(){const t=this._decorDraft;if(!t||"decor"!==this._mode)return null;const[e,i]=t.a,[s,n]=t.b;if(Math.abs(e-s)<1e-6&&Math.abs(i-n)<1e-6)return null;const o=(e+s)/2,r=(i+n)/2;if("line"===t.kind){const e=$s(t.a,t.b);return{x:o,y:r,on45:xs(e),text:`${this._fmtLen(t.a,t.b)} · ${Math.round(10*e)/10}°`}}const a=this._fmtLen([e,i],[s,i]),l=this._fmtLen([s,i],[s,n]);if("ellipse"===t.kind){const t=this._fmtLen([0,0],[Math.abs(s-e)/2,0]),a=this._fmtLen([0,0],[0,Math.abs(n-i)/2]);return{x:o,y:r,on45:!1,text:Math.abs(Math.abs(s-e)-Math.abs(n-i))<1e-6?`R ${t}`:`Rx ${t} × Ry ${a}`}}return{x:o,y:r,on45:!1,text:`${a} × ${l} · ${Io(tr(Math.abs(s-e),this._cellCm,this._gridPitch)*tr(Math.abs(n-i),this._cellCm,this._gridPitch)/1e4,this._imperial)}`}}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=oh,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=Je(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id||"ha_disabled"===e.bindingStatus?.kind)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const e=this._decorMove?.id;return t.push(...this._decorSnapGeometry(e).points),this._decorDraft&&t.push(this._decorDraft.a),t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return W``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,n=null;for(const o of e)if(!(Math.abs(o[0]-t[0])<1e-6&&Math.abs(o[1]-t[1])<1e-6)){if(Math.abs(o[0]-t[0])<=i){const e=Math.abs(o[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!n||e + ${i.map(e=>{const[i,o,r,a]="x"===e.axis?[e.at,e.from[1],e.at,t[1]+Math.sign(t[1]-e.from[1])*n]:[e.from[0],e.at,t[0]+Math.sign(t[0]-e.from[0])*n,e.at];return W` `})} - `}_renderOpeningCenterTick(t){const e=(t.angle+90)*Math.PI/180;return j``}_renderOpeningCenterTick(t){const e=(t.angle+90)*Math.PI/180;return W``}_roomCenter(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}_openingAmt(t){const e=t.contact&&this._planEntityAvailable(t.contact)?this.hass.states[t.contact]?.state:null;return ei(t.type,e,!!t.invert)}_planEntityAvailable(t){return!!t&&(!Eo(this._fullRegistryHass,t,Io(this._markers))&&"active"===this._bindingStatus("entity:"+t).kind)}_renderOpenings(t){const e=this._openingsR;if(!e.length)return j``;const i=t.color,s=this._spaceWalls,o=this._openPairs().flatMap(t=>t.segs),n=this._openingWallIndexFor(this._spaceModel(),o).value;return j`${e.map(t=>{const e=t.rlen/2,o=this._openingAmt(t),r=o>0&&!!t.contact&&this._planEntityAvailable(t.contact)?"var(--hp-open)":i,a="gate"===t.type?!t.flip_v:t.flip_v,l=s.length||"gate"===t.type?ta(n,{x:t.rx,y:t.ry,angle:t.angle,length:t.rlen,flip_v:a}):{ox:0,oy:0,cm:0,side:-1},c=l.cm>0?l.cm/this._cellCm*this._gridPitch/2:4,h=t.flip_h?-1:1,d=t.flip_v?-1:1;let p,u=0,_=0;if(l.cm>0&&(l.ox||l.oy)){const e=-t.angle*Math.PI/180,i=Math.cos(e),s=Math.sin(e);u=l.ox*i-l.oy*s,_=l.ox*s+l.oy*i,_*=d,u*=h}if("window"===t.type){const t=Math.PI/2*e,i=l.cm>0?j``:W;p=j` + x2="${t.x+15*Math.cos(e)}" y2="${t.y+15*Math.sin(e)}">`}_roomCenter(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}_openingAmt(t){const e=t.contact&&this._planEntityAvailable(t.contact)?this.hass.states[t.contact]?.state:null;return ei(t.type,e,!!t.invert)}_planEntityAvailable(t){return!!t&&(!En(this._fullRegistryHass,t,In(this._markers))&&"active"===this._bindingStatus("entity:"+t).kind)}_renderOpenings(t){const e=this._openingsR;if(!e.length)return W``;const i=t.color,s=this._spaceWalls,n=this._openPairs().flatMap(t=>t.segs),o=this._openingWallIndexFor(this._spaceModel(),n).value;return W`${e.map(t=>{const e=t.rlen/2,n=this._openingAmt(t),r=n>0&&!!t.contact&&this._planEntityAvailable(t.contact)?"var(--hp-open)":i,a="gate"===t.type?!t.flip_v:t.flip_v,l=s.length||"gate"===t.type?ta(o,{x:t.rx,y:t.ry,angle:t.angle,length:t.rlen,flip_v:a}):{ox:0,oy:0,cm:0,side:-1},c=l.cm>0?l.cm/this._cellCm*this._gridPitch/2:4,h=t.flip_h?-1:1,d=t.flip_v?-1:1;let p,u=0,_=0;if(l.cm>0&&(l.ox||l.oy)){const e=-t.angle*Math.PI/180,i=Math.cos(e),s=Math.sin(e);u=l.ox*i-l.oy*s,_=l.ox*s+l.oy*i,_*=d,u*=h}if("window"===t.type){const t=Math.PI/2*e,i=l.cm>0?W``:G;p=W` + stroke="${r}" stroke-dasharray="${t}" stroke-dashoffset="${t*(1-n)}"> + stroke="${r}" stroke-dasharray="${t}" stroke-dashoffset="${t*(1-n)}"> - + - + ${i} - `}else if("gate"===t.type){const t=l.side*d*10*o;p=j` + `}else if("gate"===t.type){const t=l.side*d*10*n;p=W` @@ -4845,16 +4915,16 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 - `}else{const i=t.rlen,s=Math.PI/2*i;p=j` + `}else{const i=t.rlen,s=Math.PI/2*i;p=W` + stroke="${r}" stroke-dasharray="${s}" stroke-dashoffset="${s*(1-n)}"> - + - `}const m="gate"===t.type?Math.sin(10*Math.PI/180)*e:0,g=Math.max(16,c+8,m+8),f=Math.max(20,c+10,m+12);return j``}const m="gate"===t.type?Math.sin(10*Math.PI/180)*e:0,g=Math.max(16,c+8,m+8),f=Math.max(20,c+10,m+12);return W` @@ -4868,25 +4938,25 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 @pointermove=${e=>this._opPointerMove(e,t)} @pointerup=${e=>this._opPointerUp(e,t)} @pointercancel=${e=>this._opPointerUp(e,t)}> - `})}`}_renderOpeningLocks(t){const e=this._openingsR.filter(t=>"window"!==t.type&&t.lock&&this._planEntityAvailable(t.lock));if(!e.length)return B``;const i=this._openPairs().flatMap(t=>t.segs),s=this._openingWallIndexFor(this._spaceModel(),i).value;return B`${e.map(e=>{const i=this.hass.states[e.lock]?.state,o="locked"===i,n=o||["unlocked","open","opening","unlocking","locking"].includes(String(i)),r=(e.angle+90)*Math.PI/180,a="gate"===e.type?ta(s,{x:e.rx,y:e.ry,angle:e.angle,length:e.rlen,flip_v:!e.flip_v}):null,l=a?-16*a.side:16*(e.flip_v?-1:1),c=e.rx+Math.cos(r)*l,h=e.ry+Math.sin(r)*l,d=(c-t.x)/t.w*100,p=(h-t.y)/t.h*100;return B`
`})}`}_renderOpeningLocks(t){const e=this._openingsR.filter(t=>"window"!==t.type&&t.lock&&this._planEntityAvailable(t.lock));if(!e.length)return B``;const i=this._openPairs().flatMap(t=>t.segs),s=this._openingWallIndexFor(this._spaceModel(),i).value;return B`${e.map(e=>{const i=this.hass.states[e.lock]?.state,n="locked"===i,o=n||["unlocked","open","opening","unlocking","locking"].includes(String(i)),r=(e.angle+90)*Math.PI/180,a="gate"===e.type?ta(s,{x:e.rx,y:e.ry,angle:e.angle,length:e.rlen,flip_v:!e.flip_v}):null,l=a?-16*a.side:16*(e.flip_v?-1:1),c=e.rx+Math.cos(r)*l,h=e.ry+Math.sin(r)*l,d=(c-t.x)/t.w*100,p=(h-t.y)/t.h*100;return B`
{t.stopPropagation(),"view"===this._mode&&(this._openingInfo=e)}}> - -
`})}`}_lockAction(t,e){if(this._planEntityAvailable(t)){if("unlock"===e){const e=this.hass?.states?.[t]?.attributes?.friendly_name||t;if(!confirm(this._t("confirm.unlock",{name:e})))return}this.hass?.callService?.("lock",e,{entity_id:t})}}_renderOpeningInfoCard(){const t=this._openingInfo,e=t.contact&&this._planEntityAvailable(t.contact)?t.contact:null,i=t.lock&&this._planEntityAvailable(t.lock)?t.lock:null,s=e?this.hass.states[e]?.state:null,o=this._openingAmt(t),n=i?this.hass.states[i]?.state:null,r="door"===t.type?"opening.door":"gate"===t.type?"opening.gate":"opening.window",a="door"===t.type?"mdi:door":"gate"===t.type?"mdi:gate":"mdi:window-closed-variant",l="gate"===t.type?o>0?"mdi:gate-open":"mdi:gate":o>0?"mdi:door-open":"mdi:door-closed",c=(t,e,i,s="")=>B`
${e}${i}
`;return B` +
`})}`}_lockAction(t,e){if(this._planEntityAvailable(t)){if("unlock"===e){const e=this.hass?.states?.[t]?.attributes?.friendly_name||t;if(!confirm(this._t("confirm.unlock",{name:e})))return}this.hass?.callService?.("lock",e,{entity_id:t})}}_renderOpeningInfoCard(){const t=this._openingInfo,e=t.contact&&this._planEntityAvailable(t.contact)?t.contact:null,i=t.lock&&this._planEntityAvailable(t.lock)?t.lock:null,s=e?this.hass.states[e]?.state:null,n=this._openingAmt(t),o=i?this.hass.states[i]?.state:null,r="door"===t.type?"opening.door":"gate"===t.type?"opening.gate":"opening.window",a="door"===t.type?"mdi:door":"gate"===t.type?"mdi:gate":"mdi:window-closed-variant",l="gate"===t.type?n>0?"mdi:gate-open":"mdi:gate":n>0?"mdi:door-open":"mdi:door-closed",c=(t,e,i,s="")=>B`
${e}${i}
`;return B`this._openingInfo=null}>
- ${e?c(l,this._t("opening.contact_label"),"unavailable"===s||null==s?this._t("opening.state_unknown"):this._t(o>0?"opening.open":"opening.closed"),o>0?"warn":"ok"):W} - ${i?c("locked"===n?"mdi:lock":"mdi:lock-open-variant",this._t("opening.lock_label"),"locked"===n?this._t("opening.locked"):["unlocked","open"].includes(String(n))?this._t("opening.unlocked"):this._t("opening.state_unknown"),"locked"===n?"ok":"warn"):W} - ${i&&("locked"===n||["unlocked","open"].includes(String(n)))?B``:i&&["locking","unlocking"].includes(String(n))?B``:i&&["locking","unlocking"].includes(String(o))?B``:W} - ${e||i?W:B`

${this._t("opening.no_entities")}

`} + `:G} + ${e||i?G:B`

${this._t("opening.no_entities")}

`}
@@ -4912,98 +4982,101 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 {const i=Hc(e.target.value);null!=i&&(this._openingDialog={...t,lengthCm:i})}} /> + @input=${e=>{const i=Vc(e.target.value);null!=i&&(this._openingDialog={...t,lengthCm:i})}} /> ${i(this._contactCandidates(),t.contact,e=>this._openingDialog={...t,contact:e})} ${t.contact?B``:W} + ${this._t("opening.invert")}`:G} ${"window"!==t.type?B` - ${i(this._lockCandidates(),t.lock,e=>this._openingDialog={...t,lock:e})}`:W} + ${i(this._lockCandidates(),t.lock,e=>this._openingDialog={...t,lock:e})}`:G} ${"gate"!==t.type?B``:W} + ${this._t("opening.flip_h")}`:G}
-
- ${t.id?B``:W} - - - + - `}_gridLevels(){const t=this._stageEl,e=this._viewOr(this._baseVb()),i=t&&t.clientWidth&&e.w?t.clientWidth/e.w:1;return function(t,e,i=7){if(!(t>0&&e>0&&Number.isFinite(e)))return null;const s=Ja.find(s=>t*s*e>=i);if(void 0===s)return null;const o=Ja.find(t=>t>=5*s)??5*s;return{fine:s,coarse:o}}(this._gridPitch,i)}_renderMarkupDefs(t){const e=this._gridLevels();if(!e)return j``;const i=this._gridPitch*e.fine,s=this._gridPitch*e.coarse,o=this._gridPitch*e.fine*.14;return j` + `}_gridLevels(){const t=this._stageEl,e=this._viewOr(this._baseVb()),i=t&&t.clientWidth&&e.w?t.clientWidth/e.w:1;return function(t,e,i=7){if(!(t>0&&e>0&&Number.isFinite(e)))return null;const s=Ya.find(s=>t*s*e>=i);if(void 0===s)return null;const n=Ya.find(t=>t>=5*s)??5*s;return{fine:s,coarse:n}}(this._gridPitch,i)}_renderMarkupDefs(t){const e=this._gridLevels();if(!e)return W``;const i=this._gridPitch*e.fine,s=this._gridPitch*e.coarse,n=this._gridPitch*e.fine*.14;return W` - - - - + + + + - - - - + + + + - `}_renderPhysicalEditorLayer(){const t=this._spaceModel(),e=this._gridPitch,i=this._viewOr(this._baseVb()),s=this._stageEl,o=s?.clientWidth?i.w/s.clientWidth:e/8,n=Math.max(.22*e,8*o),r=Math.max(n,12*o),a=t=>`M ${t.map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`,l=(t,e)=>this._physicalSel?.kind===t&&this._physicalSel.id===e||"column"===t&&this._duplicateColumnId===e,c=(t.room_drafts||[]).flatMap(t=>t.points.slice(0,-1).map((e,i)=>{const s=t.points[i+1],n=dr(t.segments[i]?.cm||15,this._cellCm,this._gridPitch);return j``}_renderPhysicalEditorLayer(){const t=this._spaceModel(),e=this._gridPitch,i=this._viewOr(this._baseVb()),s=this._stageEl,n=s?.clientWidth?i.w/s.clientWidth:e/8,o=Math.max(.22*e,8*n),r=Math.max(o,12*n),a=t=>`M ${t.map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`,l=(t,e)=>this._physicalSel?.kind===t&&this._physicalSel.id===e||"column"===t&&this._duplicateColumnId===e,c=(t.room_drafts||[]).flatMap(t=>t.points.slice(0,-1).map((e,i)=>{const s=t.points[i+1],o=dr(t.segments[i]?.cm||15,this._cellCm,this._gridPitch);return W`{this._physicalSel={kind:"draft",id:t.id,segment:i},this._registerPhysicalTap("draft",t.id,i)}}>`})),h=(t.partitions||[]).map(t=>{const e=ca(t.a,t.b,t.cm,this._cellCm,this._gridPitch);return e?j`{this._physicalSel={kind:"draft",id:t.id,segment:i},this._registerPhysicalTap("draft",t.id,i)}}>`})),h=(t.partitions||[]).map(t=>{const e=ca(t.a,t.b,t.cm,this._cellCm,this._gridPitch);return e?W`this._physicalDown(e,"partition",t.id)} @pointermove=${t=>this._physicalMove(t)} - @pointerup=${t=>this._physicalUp(t)}>`:W}),d=(t.wall_columns||[]).map(t=>{const e=ha(this._physicalRotate?.id===t.id?{...t,angle:this._physicalRotate.angle}:t,this._cellCm,this._gridPitch);return j`this._physicalUp(t)}>`:G}),d=(t.wall_columns||[]).map(t=>{const e=ha(this._physicalRotate?.id===t.id?{...t,angle:this._physicalRotate.angle}:t,this._cellCm,this._gridPitch);return W`this._physicalDown(e,"column",t.id)} @pointermove=${t=>this._physicalMove(t)} - @pointerup=${t=>this._physicalUp(t)}>`}),p=this._physicalDrag,u=(()=>{if(!p?.moved)return W;const t="partition"===p.kind?ca(p.base.a,p.base.b,p.base.cm,this._cellCm,this._gridPitch):ha(p.base,this._cellCm,this._gridPitch);return t?j``:W})(),_=(()=>{const i=this._physicalSel;if(!i)return W;if("draft"===i.kind){const e=t.room_drafts.find(t=>t.id===i.id);return e?j` + @pointerup=${t=>this._physicalUp(t)}>`}),p=this._physicalDrag,u=(()=>{if(!p?.moved)return G;const t="partition"===p.kind?ca(p.base.a,p.base.b,p.base.cm,this._cellCm,this._gridPitch):ha(p.base,this._cellCm,this._gridPitch);return t?W``:G})(),_=(()=>{const i=this._physicalSel;if(!i)return G;if("draft"===i.kind){const e=t.room_drafts.find(t=>t.id===i.id);return e?W` t.join(",")).join(" ")}> - ${e.points.map(t=>j``)} - `:W}if("partition"===i.kind){const e=t.partitions.find(t=>t.id===i.id);if(!e)return W;const s=ca(e.a,e.b,e.cm,this._cellCm,this._gridPitch);if(!s)return W;const o=(e.a[0]+e.b[0])/2,r=(e.a[1]+e.b[1])/2;return j` + ${e.points.map(t=>W``)} + `:G}if("partition"===i.kind){const e=t.partitions.find(t=>t.id===i.id);if(!e)return G;const s=ca(e.a,e.b,e.cm,this._cellCm,this._gridPitch);if(!s)return G;const n=(e.a[0]+e.b[0])/2,r=(e.a[1]+e.b[1])/2;return W` - - - - `}const s=t.wall_columns.find(t=>t.id===i.id);if(!s)return W;const l=this._physicalRotate?.id===s.id?{...s,angle:this._physicalRotate.angle}:s,c=ha(l,this._cellCm,this._gridPitch);if("square"!==l.shape)return j` + + + + `}const s=t.wall_columns.find(t=>t.id===i.id);if(!s)return G;const l=this._physicalRotate?.id===s.id?{...s,angle:this._physicalRotate.angle}:s,c=ha(l,this._cellCm,this._gridPitch);if("square"!==l.shape)return W` - - `;const h=dr(l.cm,this._cellCm,this._gridPitch),d=ra(l.angle)*Math.PI/180,p=Math.sin(d),u=-Math.cos(d),_=[l.center[0]+p*h/2,l.center[1]+u*h/2],m=[_[0]+p*Math.max(e,24*o),_[1]+u*Math.max(e,24*o)];return j` + + `;const h=dr(l.cm,this._cellCm,this._gridPitch),d=ra(l.angle)*Math.PI/180,p=Math.sin(d),u=-Math.cos(d),_=[l.center[0]+p*h/2,l.center[1]+u*h/2],m=[_[0]+p*Math.max(e,24*n),_[1]+u*Math.max(e,24*n)];return W` - + this._physicalRotateDown(t,s)} @pointermove=${t=>this._physicalRotateMove(t)} @pointerup=${t=>this._physicalRotateUp(t)}> - `})();return j`${c}${h}${d}${u}${_}`}_renderMarkupLayer(t){const e=this._openPairs().flatMap(t=>t.segs),i=this._thickWallCuts(),s=e.concat(i),o=s.length?_s(this._segments,s,.02*this._gridPitch):this._segments,n=this._path,r=this._gridPitch,a=this._viewOr(this._baseVb()),l="draw"===this._tool||"partition"===this._tool?this._drawWallCm:null,c=(()=>"draw"!==this._tool&&"partition"!==this._tool||!n.length||!(null!=l&&l>0)?null:this._contourClosed?n:this._cursorPt?[...n,this._cursorPt]:n.length>=2?n:null)(),h=c?Dr(c,dr(l,this._cellCm,this._gridPitch)/2,this._contourClosed):"";return j` - ${this._gridLevels()?j``:W} - ${o.map(t=>j``)} + `})();return W`${c}${h}${d}${u}${_}`}_renderMarkupLayer(t){const e=this._openPairs().flatMap(t=>t.segs),i=this._thickWallCuts(),s=e.concat(i),n=s.length?_s(this._segments,s,.02*this._gridPitch):this._segments,o=this._path,r=this._gridPitch,a=this._viewOr(this._baseVb()),l="draw"===this._tool||"partition"===this._tool?this._drawWallCm:null,c=(()=>"draw"!==this._tool&&"partition"!==this._tool||!o.length||!(null!=l&&l>0)?null:this._contourClosed?o:this._cursorPt?[...o,this._cursorPt]:o.length>=2?o:null)(),h=c?Dr(c,dr(l,this._cellCm,this._gridPitch)/2,this._contourClosed):"";return W` + ${this._gridLevels()?W``:G} + ${n.map(t=>W``)} ${this._renderPhysicalEditorLayer()} - ${"column"===this._tool&&this._cursorPt&&this._drawWallCm?j``M ${ha({shape:"square",center:this._cursorPt,cm:this._drawWallCm},this._cellCm,this._gridPitch).map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`)()}>`:W} - ${h?j` - `:W} - ${n.length>1?j``:W} - ${!n.length||!this._cursorPt||"draw"!==this._tool&&"partition"!==this._tool||this._contourClosed?W:j``M ${ha({shape:"square",center:this._cursorPt,cm:this._drawWallCm},this._cellCm,this._gridPitch).map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`)()}>`:G} + ${h?W` + `:G} + ${o.length>1?W``:G} + ${!o.length||!this._cursorPt||"draw"!==this._tool&&"partition"!==this._tool||this._contourClosed?G:W``} - ${n.map((t,e)=>j``)} - ${(()=>{const t=this._openingPreview;if(!t)return W;const e=t.angle*Math.PI/180,i=Math.cos(e)*t.rlen/2,s=Math.sin(e)*t.rlen/2;return j`W``)} + ${(()=>{const t=this._openingPreview;if(!t)return G;const e=t.angle*Math.PI/180,i=Math.cos(e)*t.rlen/2,s=Math.sin(e)*t.rlen/2;return W` `})()} - ${"split"===this._tool&&this._splitSel?.pts?.length?j`${this._splitSel.pts.length>1?j``:W} - ${this._splitSel.pts.map((t,e)=>j``)} - ${this._cursorPt?j``:W}`:W} + ${"split"===this._tool&&this._splitSel?.pts?.length?W`${this._splitSel.pts.length>1?W``:G} + ${this._splitSel.pts.map((t,e)=>W``)} + ${this._cursorPt?W``:G}`:G} `}_renderPhysicalDialog(){const t=this._physicalDialog,e="column"===t.kind;return B`{const i=e.target.value;this._physicalDialog={...t,shape:i}}}> - `:W} + `:G}
this._physicalDialog={...t,angle:e.target.value}} />`:W} - ${t.length?B`
${this._t("physical.length")}: ${t.length}
`:W} + @input=${e=>this._physicalDialog={...t,angle:e.target.value}} />`:G} + ${t.length?B`
${this._t("physical.length")}: ${t.length}
`:G}
-
`}_renderMarkupBar(){const t=this._geometryHistory.undoName,e=this._geometryHistory.redoName,i="boundary"===this._tool&&!!this._openWallAnchor,s=i?this._t("markup.boundary_cancel"):t?this._t("history.undo_named",{name:t}):this._t("history.undo_empty"),o=i?this._t("markup.boundary_cancel"):e?this._t("history.redo_named",{name:e}):this._t("history.redo_empty");return B`
+ `}_renderMarkupBar(){const t=this._geometryHistory.undoName,e=this._geometryHistory.redoName,i="boundary"===this._tool&&!!this._openWallAnchor,s=i?this._t("markup.boundary_cancel"):t?this._t("history.undo_named",{name:t}):this._t("history.undo_empty"),n=i?this._t("markup.boundary_cancel"):e?this._t("history.redo_named",{name:e}):this._t("history.redo_empty");return B`
@@ -5116,7 +5189,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
@@ -5147,33 +5220,33 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
-
`}_cardEntities(t){const e=this._planHass,i=[],s=new Set,o=t=>{if(!t||s.has(t)||!e.states[t])return;const o=e.entities[t];if("config"===o?.entity_category||"diagnostic"===o?.entity_category)return;s.add(t);const n=t.split(".")[0];["light","switch","fan","humidifier","siren","input_boolean"].includes(n)?i.push({eid:t,kind:"toggle"}):["cover","valve","lock","climate","media_player","vacuum","water_heater"].includes(n)?i.push({eid:t,kind:"open"}):["sensor","binary_sensor","number","select"].includes(n)&&i.push({eid:t,kind:"value"})};for(const i of xo(e,[t]))o(i.eid);t.primary&&o(t.primary);for(const e of t.entities)o(e);return i.slice(0,12)}_cardToggle(t){const e=t.split(".")[0];"lock"!==e&&"alarm_control_panel"!==e&&this._planEntityAvailable(t)&&this.hass.callService("homeassistant","toggle",{entity_id:t}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}_renderInfoCard(){const t=this._infoCard,e=t.primary?this.hass.states[t.primary]:void 0,i=e?Oi(this.hass,t.primary)?.text??e.state:null,s=(t.controls??t.marker?.controls??[]).filter(ps).filter(t=>this._planEntityAvailable(t));return B``}_cardEntities(t){const e=this._planHass,i=[],s=new Set,n=t=>{if(!t||s.has(t)||!e.states[t])return;const n=e.entities[t];if("config"===n?.entity_category||"diagnostic"===n?.entity_category)return;s.add(t);const o=t.split(".")[0];["light","switch","fan","humidifier","siren","input_boolean"].includes(o)?i.push({eid:t,kind:"toggle"}):["cover","valve","lock","climate","media_player","vacuum","water_heater"].includes(o)?i.push({eid:t,kind:"open"}):["sensor","binary_sensor","number","select"].includes(o)&&i.push({eid:t,kind:"value"})};for(const i of xn(e,[t]))n(i.eid);t.primary&&n(t.primary);for(const e of t.entities)n(e);return i.slice(0,12)}_cardToggle(t){const e=t.split(".")[0];"lock"!==e&&"alarm_control_panel"!==e&&this._planEntityAvailable(t)&&this.hass.callService("homeassistant","toggle",{entity_id:t}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}_renderInfoCard(){const t=this._infoCard,e=t.primary?this.hass.states[t.primary]:void 0,i=e?Oi(this.hass,t.primary)?.text??e.state:null,s=(t.controls??t.marker?.controls??[]).filter(ps).filter(t=>this._planEntityAvailable(t));return B`
${(()=>{const e=this._cardEntities(t);return e.length?B`
- ${e.map(({eid:t,kind:e})=>{const i=this.hass.states[t],s=this.hass.entities[t]?.name||i?.attributes?.friendly_name||t,o=i?Oi(this.hass,t)?.text??i.state:"",n="on"===i?.state||["open","unlocked","playing","cleaning"].includes(i?.state);return B`
- + ${e.map(({eid:t,kind:e})=>{const i=this.hass.states[t],s=this.hass.entities[t]?.name||i?.attributes?.friendly_name||t,n=i?Oi(this.hass,t)?.text??i.state:"",o="on"===i?.state||["open","unlocked","playing","cleaning"].includes(i?.state);return B`
+ ${s} - ${"toggle"===e?B``:"open"===e?B``:B`${o}`} + ${"toggle"===e?B``:"open"===e?B``:B`${n}`}
`})} -
`:W})()} - ${t.model?B`
${this._t("info.model")}${t.model}
`:W} - ${i&&!this._cardEntities(t).length?B`
${this._t("info.state")}${i}
`:W} +
`:G})()} + ${t.model?B`
${this._t("info.model")}${t.model}
`:G} + ${i&&!this._cardEntities(t).length?B`
${this._t("info.state")}${i}
`:G} ${$i(t.link)?B`
${this._t("info.link")} - ${t.link}
`:W} - ${t.description?B`
${t.description}
`:W} + ${t.link}
`:G} + ${t.description?B`
${t.description}
`:G} ${t.pdfs&&t.pdfs.length?B`
${this._t("info.manuals")} ${t.pdfs.map(t=>B` - ${t.name}`)}
`:W} + ${t.name}`)}
`:G} ${s.length?B`
${this._t("info.controls")} ${s.map(t=>{const e=this.hass.states[t],i="on"===e?.state;return B` ${e?.attributes?.friendly_name||t}`})} -
`:W} - ${t.model||i||t.link||t.description||t.pdfs&&t.pdfs.length||s.length?W:B`
${this._t("info.none")}
`} +
`:G} + ${t.model||i||t.link||t.description||t.pdfs&&t.pdfs.length||s.length?G:B`
${this._t("info.none")}
`}
${t.primary?B``:W} + `:G}
- `}_markerDraft(t){if("ha"===t.bindingMode&&(!t.binding||"virtual"===t.binding))return null;const e=Ds(t.room),i=yi(t.binding,t.devId,()=>"__hp_device_preview__"),s=this._markers.find(e=>e.id===i||e.id===t.devId),o=yo(t.binding,t.controls,this._bindingEntities(t.binding)),n={id:i,binding:t.binding,name:t.name.trim()||null,icon:t.icon||null,display:"badge"!==t.display?t.display:null,ripple_color:"icon_ripple"===t.display&&t.rippleColor?t.rippleColor:null,ripple_size:"icon_ripple"===t.display&&3!==t.rippleSize?t.rippleSize:null,size:1!==t.size?t.size:null,angle:t.angle||null,tap_action:t.tapAction||null,tap_target:"run"===t.tapAction&&t.tapTarget||null,tap_confirm:!!t.tapConfirm||null,controls:o.length?o:null,...this._markerLightFields(t),use_climate_temp:!!t.useClimateTemp||null,glow_radius_cm:(()=>{const e=Hc(t.glowRadius);return null==e||e<=0?null:Math.round(this._imperial?30.48*e:100*e)})(),model:t.model.trim()||null,link:t.link.trim()||null,description:t.description.trim()||null,pdfs:t.pdfs,hidden:t.hideFromPlan,vacuum:s?.vacuum||null};return("virtual"===t.binding||t.room)&&(n.space=e?.space||("virtual"===t.binding?this._space:null),n.area=e?.area||null,n.room_id=e?.roomId||null),n}_markerPreviewDevice(t){const e=this._markerDraft(t);if(!e)return null;const i=`${this._haRegistry.revision}\n${JSON.stringify(e)}`;if(this._markerPreviewMemo?.key===i)return this._markerPreviewMemo.device;const s=function(t){const{marker:e,...i}=t;return Fo({...i,markers:[e]}).find(t=>t.id===e.id)||null}({hass:this.hass,registry:this._haRegistry,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),marker:e,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||this._space,loc:t=>this._t(t),iconRules:this._iconRules});return this._markerPreviewMemo={key:i,device:s},s}_markerSpatialSource(t){const e=this._markerPreviewDevice(t);return e?So(xo(this._planHass,[{...e,hidden:!1}])):null}_markerAutoHasSpatialSource(t){const e={...t,lightRole:"auto",lightRoleTouched:!0},i=this._markerPreviewDevice(e);return!!i&&ko(this._planHass,{...i})}_setMarkerLightRole(t){const e=this._markerDialog;e&&(this._markerDialog={...e,lightRole:t,lightRoleTouched:!0})}_setMarkerGlowMode(t){const e=this._markerDialog;if(!e)return;if("auto"===t)return void(this._markerDialog={...e,glowMode:t,glowTouched:!0});const i=this._markerSpatialSource(e),s=cs(i?this._planHass.states[i.eid]:void 0,null,this._fillColors.glow_light.c),o=!e.glowColorDrafted,n="fixed"===t&&!e.glowBrightnessDrafted;this._markerDialog={...e,glowMode:t,glowColor:o?s.c:e.glowColor,glowBrightness:n?Math.max(1,Math.round(100*s.bri)):e.glowBrightness,glowColorDrafted:!0,glowBrightnessDrafted:e.glowBrightnessDrafted||"fixed"===t,glowTouched:!0}}_renderMarkerDialog(){const t=this._markerDialog,e="virtual"===t.bindingMode,i=this._bindingCandidates(),s=this._bindingEntities(t.binding),o=e?null:this._bindingStatus(t.binding),n=!e&&this._bindingHasHaPage(t.binding),r=this._markerPreviewDevice(t),a=r?Yi(this._serverCfg?.spaces.find(t=>t.id===r.space)):null,l=r?Zo(this._planHass,r,{liveStates:!1!==this._config?.live_states,showTemperature:!1!==this._config?.show_temperature,showSignal:a?.showLqi??!1!==this._config?.show_signal,designPreview:!0,activityRuntime:this._activityRt.get(r.id)}):null,c=this._markerAutoHasSpatialSource(t),h=this._markerSpatialSource(t),d="never"===t.lightRole||!h,p="never"===t.lightRole?this._t("marker.glow_disabled_never"):"auto"!==t.lightRole||c?this._t("marker.glow_disabled_no_entity"):this._t("marker.glow_disabled_auto"),u=(()=>{if(e)return null;const s=i.find(e=>e.value===t.binding);if(s)return s.label;const[o,n]=t.binding.split(":");return"device"===o?this._fullRegistryHass.devices[n]?.name_by_user||this._fullRegistryHass.devices[n]?.name||n:this._fullRegistryHass.entities[n]?.name||this.hass.states[n]?.attributes?.friendly_name||n})();return B``}_markerDraft(t){if("ha"===t.bindingMode&&(!t.binding||"virtual"===t.binding))return null;const e=Ds(t.room),i=yi(t.binding,t.devId,()=>"__hp_device_preview__"),s=this._markers.find(e=>e.id===i||e.id===t.devId),n=yn(t.binding,t.controls,this._bindingEntities(t.binding)),o={id:i,binding:t.binding,name:t.name.trim()||null,icon:t.icon||null,display:"badge"!==t.display?t.display:null,ripple_color:"icon_ripple"===t.display&&t.rippleColor?t.rippleColor:null,ripple_size:"icon_ripple"===t.display&&3!==t.rippleSize?t.rippleSize:null,size:1!==t.size?t.size:null,angle:t.angle||null,tap_action:t.tapAction||null,tap_target:"run"===t.tapAction&&t.tapTarget||null,tap_confirm:!!t.tapConfirm||null,controls:n.length?n:null,...this._markerLightFields(t),use_climate_temp:!!t.useClimateTemp||null,glow_radius_cm:(()=>{const e=Vc(t.glowRadius);return null==e||e<=0?null:Math.round(this._imperial?30.48*e:100*e)})(),model:t.model.trim()||null,link:t.link.trim()||null,description:t.description.trim()||null,pdfs:t.pdfs,hidden:t.hideFromPlan,vacuum:s?.vacuum||null};return("virtual"===t.binding||t.room)&&(o.space=e?.space||("virtual"===t.binding?this._space:null),o.area=e?.area||null,o.room_id=e?.roomId||null),o}_markerPreviewDevice(t){const e=this._markerDraft(t);if(!e)return null;const i=`${this._haRegistry.revision}\n${JSON.stringify(e)}`;if(this._markerPreviewMemo?.key===i)return this._markerPreviewMemo.device;const s=function(t){const{marker:e,...i}=t;return Nn({...i,markers:[e]}).find(t=>t.id===e.id)||null}({hass:this.hass,registry:this._haRegistry,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),marker:e,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||this._space,loc:t=>this._t(t),iconRules:this._iconRules});return this._markerPreviewMemo={key:i,device:s},s}_markerSpatialSource(t){const e=this._markerPreviewDevice(t);return e?Sn(xn(this._planHass,[{...e,hidden:!1}])):null}_markerAutoHasSpatialSource(t){const e={...t,lightRole:"auto",lightRoleTouched:!0},i=this._markerPreviewDevice(e);return!!i&&kn(this._planHass,{...i})}_setMarkerLightRole(t){const e=this._markerDialog;e&&(this._markerDialog={...e,lightRole:t,lightRoleTouched:!0})}_setMarkerGlowMode(t){const e=this._markerDialog;if(!e)return;if("auto"===t)return void(this._markerDialog={...e,glowMode:t,glowTouched:!0});const i=this._markerSpatialSource(e),s=cs(i?this._planHass.states[i.eid]:void 0,null,this._fillColors.glow_light.c),n=!e.glowColorDrafted,o="fixed"===t&&!e.glowBrightnessDrafted;this._markerDialog={...e,glowMode:t,glowColor:n?s.c:e.glowColor,glowBrightness:o?Math.max(1,Math.round(100*s.bri)):e.glowBrightness,glowColorDrafted:!0,glowBrightnessDrafted:e.glowBrightnessDrafted||"fixed"===t,glowTouched:!0}}_renderMarkerDialog(){const t=this._markerDialog,e="virtual"===t.bindingMode,i=this._bindingCandidates(),s=this._bindingEntities(t.binding),n=e?null:this._bindingStatus(t.binding),o=!e&&this._bindingHasHaPage(t.binding),r=this._markerPreviewDevice(t),a=r?Yi(this._serverCfg?.spaces.find(t=>t.id===r.space)):null,l=r?Zn(this._planHass,r,{liveStates:!1!==this._config?.live_states,showTemperature:!1!==this._config?.show_temperature,showSignal:a?.showLqi??!1!==this._config?.show_signal,designPreview:!0,activityRuntime:this._activityRt.get(r.id)}):null,c=this._markerAutoHasSpatialSource(t),h=this._markerSpatialSource(t),d="never"===t.lightRole||!h,p="never"===t.lightRole?this._t("marker.glow_disabled_never"):"auto"!==t.lightRole||c?this._t("marker.glow_disabled_no_entity"):this._t("marker.glow_disabled_auto"),u=(()=>{if(e)return null;const s=i.find(e=>e.value===t.binding);if(s)return s.label;const[n,o]=t.binding.split(":");return"device"===n?this._fullRegistryHass.devices[o]?.name_by_user||this._fullRegistryHass.devices[o]?.name||o:this._fullRegistryHass.entities[o]?.name||this.hass.states[o]?.attributes?.friendly_name||o})();return B`this._markerDialog=null}>
- ${"ha_disabled"===o?.kind?B`
+ ${"ha_disabled"===n?.kind?B`
- ${this._t(`marker.ha_disabled_${o.reason}`)} - ${n?B``:W} -
`:"unverified"===o?.kind&&t.binding?B`
+ `:G} +
`:"unverified"===n?.kind&&t.binding?B`
${this._t("marker.ha_registry_limited")} -
`:W} +
`:G}
@@ -5223,7 +5296,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
${"ha"===t.bindingMode?B` ${t.bindingOpen?B`
@@ -5232,12 +5305,12 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 @input=${e=>this._markerDialog={...t,bindingFilter:e.target.value}} />
${i.map(e=>B`
this._markerDialog={...t,binding:e.value,bindingOpen:!1,controls:vo(e.value,t.controls,this._bindingEntities(e.value)),autoIcon:this._autoIconForBinding(e.value)}}> + @click=${()=>this._markerDialog={...t,binding:e.value,bindingOpen:!1,controls:vn(e.value,t.controls,this._bindingEntities(e.value)),autoIcon:this._autoIconForBinding(e.value)}}> ${e.label}${e.sub}
`)} - ${i.length?W:B`
${this._t("marker.nothing_found")}
`} + ${i.length?G:B`
${this._t("marker.nothing_found")}
`}
-
`:W}`:W} +
`:G}`:G}
@@ -5256,22 +5329,22 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 ${"run"===t.tapAction?(()=>{const e=t.runFilter.trim().toLowerCase(),i=this._runCandidates().filter(t=>!e||t.label.toLowerCase().includes(e)||t.value.includes(e)),s=t.tapTarget?this._runCandidates().find(e=>e.value===t.tapTarget):null;return B` - ${t.tapTarget&&!s?B`
${this._t("marker.run_target_gone",{id:t.tapTarget})}
`:W} + ${t.tapTarget&&!s?B`
${this._t("marker.run_target_gone",{id:t.tapTarget})}
`:G} {t.target.select()}} @input=${e=>this._markerDialog={...t,runFilter:e.target.value,tapTarget:""}} /> - ${s?W:B`
+ ${s?G:B`
${i.slice(0,40).map(e=>B`
this._markerDialog={...t,tapTarget:e.value,runFilter:""}}> ${e.label}${e.sub}
`)} - ${i.length?W:B`
${this._t("marker.nothing_found")}
`} -
`}`})():W} + ${i.length?G:B`
${this._t("marker.nothing_found")}
`} +
`}`})():G} ${"run"===t.tapAction||"toggle"===t.tapAction||"cover"===t.tapAction||!t.tapAction&&"toggle"===t.defaultTap?B``:W} + `:G}
${this._t("marker.controls_hint")}
@@ -5280,23 +5353,23 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 ${this.hass.states[e]?.attributes?.friendly_name||e} this._markerDialog={...t,controls:t.controls.filter(t=>t!==e)}}> `)} - `:W} + `:G} this._markerDialog={...t,controlsFilter:e.target.value}} /> ${t.controlsFilter.trim()?B`
- ${Object.keys(this.hass.states).filter(e=>vo(t.binding,[e],s).length>0&&!t.controls.includes(e)&&this._planEntityAvailable(e)).filter(e=>{const i=t.controlsFilter.trim().toLowerCase(),s=String(this.hass.states[e]?.attributes?.friendly_name||"");return e.toLowerCase().includes(i)||s.toLowerCase().includes(i)}).slice(0,8).map(e=>B``)} -
`:W} + `:G} ${this._bindingHasClimate(t.binding)?B``:W} + `:G}
${this._t("marker.light_role_label")}

${this._t("marker.light_role_tip")}

@@ -5314,7 +5387,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
+ aria-describedby=${d?"marker-glow-disabled-hint":G}> ${this._t("marker.glow_color_label")}

${this._t("marker.glow_color_tip")}

@@ -5339,9 +5412,9 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 ${"fixed"===t.glowMode?B` ${this._t("marker.glow_brightness")} ${this._rangeInput(1,100,1,t.glowBrightness,e=>{this._markerDialog={...t,glowBrightness:e,glowBrightnessDrafted:!0,glowTouched:!0}},d,this._t("marker.glow_brightness"))} - ${Math.round(t.glowBrightness)}%`:W} -
`:W} - ${"fixed"===t.glowMode?B`

${this._t("marker.glow_brightness_hint")}

`:W} + ${Math.round(t.glowBrightness)}%`:G} + `:G} + ${"fixed"===t.glowMode?B`

${this._t("marker.glow_brightness_hint")}

`:G}
${p} -

`:W} +

`:G} ${customElements.get("ha-icon-picker")?B`this._markerDialog={...t,icon:t.autoIcon}}> ${this._t("marker.icon_pin_auto")} -

`:W} +

`:G} this._spaceDialog={...t,source:"draw"}} /> @@ -5496,9 +5569,9 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
- {const i=Hc(e.target.value);this._spaceDialog={...t,cellCm:null!=i&&i>0?Math.max(.1,Math.min(Fc,i)):t.cellCm}}} /> + @input=${e=>{const i=Vc(e.target.value);this._spaceDialog={...t,cellCm:null!=i&&i>0?Math.max(.1,Math.min(jc,i)):t.cellCm}}} /> ${this._t("space.scale_unit")}
@@ -5552,20 +5625,20 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 - ${"static"===(t.bgMode??Wn(this._settings,{}))?B` + ${"static"===(t.bgMode??Go(this._settings,{}))?B`
this._spaceDialog={...t,bgColor:e.target.value}} /> ${t.bgColor?B``:B`${this._t("space.bg_inherited")}`} -
`:W} +
`:G}
{const i=e.target.value.trim(),s=""===i?null:Math.round(Number(i));this._spaceDialog={...t,northDeg:null!==s&&Number.isFinite(s)?Math.min(359,Math.max(0,s)):null}}} /> - ${null===t.northDeg?this._t("space.north_inherited",{v:null===Gn(this._settings,{})?"—":String(Gn(this._settings,{}))+"°"}):"°"} + ${null===t.northDeg?this._t("space.north_inherited",{v:null===jo(this._settings,{})?"—":String(jo(this._settings,{}))+"°"}):"°"}
{const i=Hc(e.target.value);null!=i&&(this._spaceDialog={...t,tempMin:i})}} /> + @input=${e=>{const i=Vc(e.target.value);null!=i&&(this._spaceDialog={...t,tempMin:i})}} /> – {const i=Hc(e.target.value);null!=i&&(this._spaceDialog={...t,tempMax:i})}} /> + @input=${e=>{const i=Vc(e.target.value);null!=i&&(this._spaceDialog={...t,tempMax:i})}} /> °C - `:W} + `:G} ${"custom"===e&&"custom"===t.fillMode?B`
${this._t("space.custom_fill")} {this._spaceDialog={...t,customFill:{c:e.detail.color,a:e.detail.opacity}}}}> ${t.customFill?B``:W} -
`:W}`)} + ${this._t("btn.reset")}`:G} + `:G}`)} -
- ${"edit"===t.mode?B``:W} - - ${this._importTotal>0&&"create"===t.mode?B``:W} - - + - `}_renderMergeDialog(){const t=this._mergeDialog,e=this._spaceModel().rooms,i=(i,s)=>{const o=e.find(t=>t.id===i),n=o?.area?this.hass.areas[o.area]?.name:null;return B`
- `:W}`:W}`}_renderRoomDialog(){const t=!!this._roomEditId,e=!!this._areaSel||!!this._nameSel.trim(),i=Yi(this._curSpaceCfg),s=this._roomFill||i.fill,o=this._roomCustomFill||i.customFill,n=[...this._freeAreas];if(t&&this._areaSel&&!n.some(t=>t.area_id===this._areaSel)){const t=this.hass.areas[this._areaSel];t&&n.unshift(t)}return B``:G}`:G}`}_renderRoomDialog(){const t=!!this._roomEditId,e=!!this._areaSel||!!this._nameSel.trim(),i=Yi(this._curSpaceCfg),s=this._roomFill||i.fill,n=this._roomCustomFill||i.customFill,o=[...this._freeAreas];if(t&&this._areaSel&&!o.some(t=>t.area_id===this._areaSel)){const t=this.hass.areas[this._areaSel];t&&o.unshift(t)}return B`
@@ -5684,7 +5760,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 @@ -5699,11 +5775,11 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 {this._roomCustomFill={c:t.detail.color,a:t.detail.opacity}}}> - ${this._roomCustomFill?B``:W} -
`:W} + ${this._roomCustomFill?B``:G} + `:G} ${this._renderRoomSource("temp")} ${this._renderRoomSource("hum")} @@ -5725,11 +5801,11 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 ${t?B``:B`${this._pendingSplit?W:B``:B`${this._pendingSplit?G:B``} `} -
`}}ch.properties={_hdrH:{state:!0},_booting:{state:!0},_bootFading:{state:!0},_bootSoft:{state:!0},_tapConfirm:{state:!0},hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_hoverRoom:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_wallDialog:{state:!0},_drawWallField:{state:!0},_activeDraftId:{state:!0},_physicalSel:{state:!0},_physicalDialog:{state:!0},_physicalDrag:{state:!0},_physicalRotate:{state:!0},_duplicateColumnId:{state:!0},_rszSel:{state:!0},_rszLive:{state:!0},_opMeasure:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_openWallAnchor:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorEraseConfirm:{state:!0},_decorTextDialog:{state:!0},_decorShapeDialog:{state:!0},_backdropDialog:{state:!0},_furnPalette:{state:!0},_bdDrag:{state:!0},_dtBox:{state:!0},_dtDrag:{state:!0},_kioskDialog:{state:!0},_vacFit:{state:!0},_vacAllCamerasFor:{state:!0},_vacCalConfirm:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomCustomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_alignDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},ch.ZOOM_MAX=8,ch.ZOOM_MIN=1/3,ch._touchSeen=!1,ch._noHoverMq="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,ch.styles=[At,mc],customElements.get("houseplan-card")||customElements.define("houseplan-card",ch),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info(`%c HOUSEPLAN-CARD %c v${Ec} `,"background:#3ea6ff;color:#04121f;font-weight:700",""); +
`}}uh.properties={_hdrH:{state:!0},_booting:{state:!0},_bootFading:{state:!0},_bootSoft:{state:!0},_continuityEpoch:{state:!0},_tapConfirm:{state:!0},hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_hoverRoom:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_wallDialog:{state:!0},_drawWallField:{state:!0},_activeDraftId:{state:!0},_physicalSel:{state:!0},_physicalDialog:{state:!0},_physicalDrag:{state:!0},_physicalRotate:{state:!0},_duplicateColumnId:{state:!0},_rszSel:{state:!0},_rszLive:{state:!0},_opMeasure:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_openWallAnchor:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorEraseConfirm:{state:!0},_decorTextDialog:{state:!0},_decorShapeDialog:{state:!0},_backdropDialog:{state:!0},_furnPalette:{state:!0},_bdDrag:{state:!0},_dtBox:{state:!0},_dtDrag:{state:!0},_kioskDialog:{state:!0},_vacFit:{state:!0},_vacAllCamerasFor:{state:!0},_vacCalConfirm:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomCustomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_alignDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},uh.ZOOM_MAX=8,uh.ZOOM_MIN=1/3,uh._touchSeen=!1,uh._noHoverMq="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,uh.styles=[At,$c],customElements.get("houseplan-card")||customElements.define("houseplan-card",uh),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info(`%c HOUSEPLAN-CARD %c v${Bc} `,"background:#3ea6ff;color:#04121f;font-weight:700",""); diff --git a/custom_components/houseplan/manifest.json b/custom_components/houseplan/manifest.json index ee9879bf..5b47b1ec 100755 --- a/custom_components/houseplan/manifest.json +++ b/custom_components/houseplan/manifest.json @@ -16,5 +16,5 @@ "issue_tracker": "https://github.com/Matysh/houseplan-card/issues", "requirements": [], "single_config_entry": true, - "version": "1.61.0-beta.6" + "version": "1.61.0-beta.7" } diff --git a/demo/golden/README.md b/demo/golden/README.md index 5928e0c6..b7d9aeea 100644 --- a/demo/golden/README.md +++ b/demo/golden/README.md @@ -54,3 +54,8 @@ Future updates must still use the `golden-images` artifact produced by the Linux CI job as the review set: desktop font rasterisation can differ from the CI environment even with the same pinned Chromium. Pass its unpacked root via `--from=...` when accepting it locally. + +Scenarios may also declare a semantic pixel region (for example, a receiving +room that must contain warm light). `golden:capture` and `golden:verify` reject +the capture before baseline comparison when that visual precondition is empty; +a reviewed but meaningless PNG therefore cannot become the contract. diff --git a/demo/golden/harness.mjs b/demo/golden/harness.mjs index 39a534aa..927347a5 100644 --- a/demo/golden/harness.mjs +++ b/demo/golden/harness.mjs @@ -51,11 +51,19 @@ async function stableEnvironment(page, scenario) { /** Apply every data-only scenario override before the fixture crosses into the browser. */ export function prepareGoldenFixture(scenario) { const fixture = fixtureFor(scenario.fixture); - if (scenario.deviceName && fixture.devices?.[scenario.deviceId]) + const requireSpace = () => { + const space = fixture.config.spaces.find((item) => item.id === scenario.space); + if (!space) throw new Error(`golden override references missing space: ${scenario.space}`); + return space; + }; + if (scenario.deviceName) { + if (!scenario.deviceId || !fixture.devices?.[scenario.deviceId]) + throw new Error(`golden deviceName references missing device: ${scenario.deviceId || ''}`); fixture.devices[scenario.deviceId].name = scenario.deviceName; + } if (scenario.fillMode || typeof scenario.glowEnabled === 'boolean' || typeof scenario.sunRays === 'boolean') { - const space = fixture.config.spaces.find((item) => item.id === scenario.space); + const space = requireSpace(); space.settings = { ...(space.settings || {}), ...(scenario.fillMode ? { fill_mode: scenario.fillMode } : {}), @@ -65,26 +73,41 @@ export function prepareGoldenFixture(scenario) { }; } if (scenario.extraOpenings?.length) { - const space = fixture.config.spaces.find((item) => item.id === scenario.space); + const space = requireSpace(); + const known = new Set((space.openings || []).map((opening) => opening.id)); + for (const opening of scenario.extraOpenings) { + if (!opening?.id || known.has(opening.id)) + throw new Error(`golden extraOpening has missing/duplicate id: ${opening?.id || ''}`); + if (!['door', 'window', 'gate'].includes(opening.type)) + throw new Error(`golden extraOpening has unknown type: ${opening.type}`); + known.add(opening.id); + } space.openings = [...(space.openings || []), ...structuredClone(scenario.extraOpenings)]; } if (scenario.hideOpenings) { - const space = fixture.config.spaces.find((item) => item.id === scenario.space); + const space = requireSpace(); space.settings = { ...(space.settings || {}), hide_openings: true }; } if (scenario.roomGlow) { - const space = fixture.config.spaces.find((item) => item.id === scenario.space); + const space = requireSpace(); + const unknown = new Set(Object.keys(scenario.roomGlow)); for (const room of space.rooms) { if (!(room.id in scenario.roomGlow)) continue; + unknown.delete(room.id); room.settings = { ...(room.settings || {}), glow: scenario.roomGlow[room.id] }; } + if (unknown.size) throw new Error(`golden roomGlow references missing room(s): ${[...unknown].join(', ')}`); } if (scenario.roomCustomFill) { - const space = fixture.config.spaces.find((item) => item.id === scenario.space); + const space = requireSpace(); + const unknown = new Set(Object.keys(scenario.roomCustomFill)); for (const room of space.rooms) { if (!(room.id in scenario.roomCustomFill)) continue; + unknown.delete(room.id); room.settings = { ...(room.settings || {}), custom_fill: scenario.roomCustomFill[room.id] }; } + if (unknown.size) + throw new Error(`golden roomCustomFill references missing room(s): ${[...unknown].join(', ')}`); } if (scenario.allLightsOff) { for (const [entityId, state] of Object.entries(fixture.states || {})) { @@ -94,7 +117,8 @@ export function prepareGoldenFixture(scenario) { } if (scenario.stateOverrides) { for (const [entityId, override] of Object.entries(scenario.stateOverrides)) { - const current = fixture.states?.[entityId] || { entity_id: entityId, attributes: {} }; + const current = fixture.states?.[entityId]; + if (!current) throw new Error(`golden stateOverride references missing entity: ${entityId}`); fixture.states[entityId] = { ...current, ...structuredClone(override), @@ -104,12 +128,17 @@ export function prepareGoldenFixture(scenario) { } if (scenario.markerOverrides) { const ids = new Set(scenario.markerOverrides.map((marker) => marker.id)); + const known = new Set((fixture.config.markers || []).map((marker) => marker.id)); + const missing = [...ids].filter((id) => !known.has(id)); + if (missing.length) throw new Error(`golden markerOverrides reference missing marker(s): ${missing.join(', ')}`); fixture.config.markers = [ ...(fixture.config.markers || []).filter((marker) => !ids.has(marker.id)), ...structuredClone(scenario.markerOverrides), ]; } if (scenario.layoutOverrides) { + const missing = Object.keys(scenario.layoutOverrides).filter((id) => !(id in (fixture.layout || {}))); + if (missing.length) throw new Error(`golden layoutOverrides reference missing item(s): ${missing.join(', ')}`); fixture.layout = { ...(fixture.layout || {}), ...structuredClone(scenario.layoutOverrides) }; } diff --git a/demo/golden/matrix.mjs b/demo/golden/matrix.mjs index 9534ef2d..3de173d5 100644 --- a/demo/golden/matrix.mjs +++ b/demo/golden/matrix.mjs @@ -1,5 +1,5 @@ /** Data-only HP-QA-01 capture matrix. Bump when framing or scenarios change. */ -export const GOLDEN_MATRIX_VERSION = 7; +export const GOLDEN_MATRIX_VERSION = 8; const stage = { capture: 'stage', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0005 } }; const page = { capture: 'page', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0008 } }; @@ -66,7 +66,10 @@ export const GOLDEN_SCENARIOS = Object.freeze([ extraOpenings: [ { id: 'light-door-second', type: 'door', x: 0.50, y: 0.32, angle: 90, length: 0.13 }, ], - layoutOverrides: { 'golden-light-one': { s: 'golden-lighting', x: 0.22, y: 0.48 } }, + layoutOverrides: { 'golden-light-one': { s: 'golden-lighting', x: 0.40, y: 0.48 } }, + // The golden is protection only if the receiving half actually contains + // rendered light. A data-only check once let a visually empty baseline pass. + warmPixelRegion: { x: 0.5, y: 0, w: 0.5, h: 1, minPixels: 2500, minRedBlueDelta: 25 }, theme: 'dark', viewport: { width: 1000, height: 900 }, ...stage }, { id: 'lighting-custom-glow-light', fixture: 'visual', space: 'golden-lighting', mode: 'view', fillMode: 'custom', customFill: { c: '#486a8f', a: 0.42 }, glowEnabled: true, diff --git a/demo/golden/run.mjs b/demo/golden/run.mjs index ff36ce1c..8eeb8635 100644 --- a/demo/golden/run.mjs +++ b/demo/golden/run.mjs @@ -105,6 +105,30 @@ async function comparePng(page, actual, baseline, threshold) { }); } +/** Assert scenario semantics against the actual capture, not only its data. + * This protects a reviewed-but-empty baseline from becoming the reference. */ +async function countWarmPixels(page, png, region) { + return page.evaluate(async ({ png64, region }) => { + const bytes = Uint8Array.from(atob(png64), (char) => char.charCodeAt(0)); + const image = await createImageBitmap(new Blob([bytes], { type: 'image/png' })); + const canvas = document.createElement('canvas'); + canvas.width = image.width; + canvas.height = image.height; + const context = canvas.getContext('2d', { willReadFrequently: true }); + context.drawImage(image, 0, 0); + const left = Math.max(0, Math.min(image.width, Math.floor(region.x * image.width))); + const top = Math.max(0, Math.min(image.height, Math.floor(region.y * image.height))); + const right = Math.max(left, Math.min(image.width, Math.ceil((region.x + region.w) * image.width))); + const bottom = Math.max(top, Math.min(image.height, Math.ceil((region.y + region.h) * image.height))); + const pixels = context.getImageData(left, top, right - left, bottom - top).data; + let warm = 0; + for (let offset = 0; offset < pixels.length; offset += 4) { + if (pixels[offset] - pixels[offset + 2] > region.minRedBlueDelta) warm++; + } + return { warm, bounds: [left, top, right, bottom] }; + }, { png64: png.toString('base64'), region }); +} + let baselineManifest = null; const baselineManifestPath = resolve(baselineRoot, GOLDEN_BASELINE_MANIFEST); if (existsSync(baselineManifestPath)) { @@ -147,6 +171,17 @@ try { writeFileSync(actualPath, actual); result.actualSha256 = sha256(actual); result.actual = actualPath; + if (scenario.warmPixelRegion) { + const sample = await countWarmPixels(page, actual, scenario.warmPixelRegion); + result.warmPixels = sample.warm; + result.warmPixelBounds = sample.bounds; + if (sample.warm < scenario.warmPixelRegion.minPixels) { + throw new Error( + `semantic golden assertion failed: ${sample.warm} warm pixels, expected at least ` + + `${scenario.warmPixelRegion.minPixels}`, + ); + } + } const baselinePath = resolve(baselineRoot, `${scenario.id}.png`); result.baseline = baselinePath; if (!existsSync(baselinePath)) { diff --git a/demo/screencast_visual_continuity.mjs b/demo/screencast_visual_continuity.mjs new file mode 100644 index 00000000..b35af902 --- /dev/null +++ b/demo/screencast_visual_continuity.mjs @@ -0,0 +1,115 @@ +// #73 stable-release pixel gate. Unlike a loop of Playwright screenshots, +// CDP screencast observes compositor-presented frames and can therefore catch +// the single black/empty frame that motivated the visual-continuity contract. +import { mkdirSync, writeFileSync } from 'node:fs'; +import { launch, check, finish } from './serve.mjs'; + +const { page, browser } = await launch({ width: 820, height: 760 }); +const session = await page.context().newCDPSession(page); +const frames = []; +let stopped = false; + +session.on('Page.screencastFrame', (event) => { + if (frames.length < 120) frames.push(event.data); + void session.send('Page.screencastFrameAck', { sessionId: event.sessionId }); +}); + +const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); +try { + const stage = await page.locator('houseplan-card').evaluate((card) => { + const node = (card.shadowRoot || card.renderRoot).querySelector('.stage'); + const rect = node.getBoundingClientRect(); + return { x: rect.x, y: rect.y, width: rect.width, height: rect.height }; + }); + + await session.send('Page.startScreencast', { + format: 'png', everyNthFrame: 1, maxWidth: 820, maxHeight: 760, + }); + await wait(120); + await page.evaluate(() => window.__card._pageVisibility({ + kind: 'visible', token: 73, at: Date.now(), hiddenFor: 20_000, long: true, + })); + // A harmless real paint gives the compositor enough damage to emit the + // candidate sequence even when the fresh server response equals the stale + // frame pixel-for-pixel. + await page.mouse.move(stage.x + stage.width * 0.42, stage.y + stage.height * 0.52); + await wait(850); + await page.mouse.move(2, 2); + await wait(180); + await session.send('Page.stopScreencast'); + stopped = true; + + const metrics = await page.evaluate(async ({ encoded, crop }) => { + const decode = (data) => new Promise((resolve, reject) => { + const image = new Image(); + image.onload = () => resolve(image); + image.onerror = reject; + image.src = `data:image/png;base64,${data}`; + }); + const out = []; + for (const data of encoded) { + const image = await decode(data); + const scaleX = image.width / innerWidth; + const scaleY = image.height / innerHeight; + const x = Math.max(0, Math.floor(crop.x * scaleX)); + const y = Math.max(0, Math.floor(crop.y * scaleY)); + const width = Math.max(1, Math.min(image.width - x, Math.floor(crop.width * scaleX))); + const height = Math.max(1, Math.min(image.height - y, Math.floor(crop.height * scaleY))); + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const context = canvas.getContext('2d', { willReadFrequently: true }); + context.drawImage(image, x, y, width, height, 0, 0, width, height); + const pixels = context.getImageData(0, 0, width, height).data; + let count = 0; + let sum = 0; + let sum2 = 0; + let dark = 0; + for (let row = 0; row < height; row += 4) { + for (let column = 0; column < width; column += 4) { + const offset = (row * width + column) * 4; + const luma = pixels[offset] * 0.2126 + + pixels[offset + 1] * 0.7152 + pixels[offset + 2] * 0.0722; + count++; + sum += luma; + sum2 += luma * luma; + if (luma < 18) dark++; + } + } + const mean = sum / Math.max(1, count); + out.push({ + mean, + variance: sum2 / Math.max(1, count) - mean * mean, + darkRatio: dark / Math.max(1, count), + }); + } + return out; + }, { encoded: frames, crop: stage }); + + const baseline = metrics[0] || { mean: 0, variance: 0, darkRatio: 1 }; + const forbidden = metrics.filter((frame) => ( + frame.variance < Math.max(12, baseline.variance * 0.08) + || frame.mean < Math.max(4, baseline.mean * 0.35) + || (baseline.darkRatio < 0.85 && frame.darkRatio > 0.96) + )); + + mkdirSync('artifacts/continuity-screencast', { recursive: true }); + frames.forEach((data, index) => writeFileSync( + `artifacts/continuity-screencast/frame-${String(index).padStart(3, '0')}.png`, + Buffer.from(data, 'base64'), + )); + writeFileSync('artifacts/continuity-screencast/metrics.json', JSON.stringify({ + stage, frames: metrics, forbidden: forbidden.length, + }, null, 2)); + + const result = { + capturedPresentedFrames: frames.length >= 2, + baselineContainsPlanDetail: baseline.variance >= 12 && baseline.mean >= 4, + noEmptyOrBlackPresentedFrame: forbidden.length === 0, + }; + for (const [name, value] of Object.entries(result)) check(name, value); + await finish(browser, result); +} finally { + if (!stopped) await session.send('Page.stopScreencast').catch(() => undefined); + await browser.close().catch(() => undefined); +} diff --git a/demo/smoke_free_walls.mjs b/demo/smoke_free_walls.mjs index 5cf41f02..13a68ae4 100644 --- a/demo/smoke_free_walls.mjs +++ b/demo/smoke_free_walls.mjs @@ -141,8 +141,6 @@ const out = await page.evaluate(async () => { const mockTitleRect = mockTitle?.getBoundingClientRect(); const dangerRect = partitionLayout.danger?.getBoundingClientRect(); const commitRect = partitionLayout.commit?.getBoundingClientRect(); - o.physicalTitleUsesAutoHeight = !!haDialog && getComputedStyle(haDialog) - .getPropertyValue('--ha-dialog-header-title-height').trim() === 'auto'; o.physicalTitleWrapsAndIsContained = !!titleRect && !!mockTitleRect && titleRect.height > 35 && titleRect.top >= mockTitleRect.top - 1 diff --git a/demo/smoke_glow.mjs b/demo/smoke_glow.mjs index 94382816..0b1719bc 100644 --- a/demo/smoke_glow.mjs +++ b/demo/smoke_glow.mjs @@ -109,9 +109,12 @@ const res = await page.evaluate(async () => { // region: a hair on screen, never a soft sector pasted over the plan. const glowFilters = [...sr().querySelectorAll('defs filter[id^="hp-glow"]')]; const pools = sr().querySelector('.glow-pools'); + const frameFilter = sr().querySelector('.glow-pools-frame')?.getAttribute('filter'); + const featherTemporarilySuspended = frameFilter === null + && Date.now() < Number(c._glowFeatherSuspendUntil || 0); out.edgeFeatherIsAHair = glowFilters.length === 1 && glowFilters[0].querySelectorAll('feGaussianBlur').length === 1 - && sr().querySelector('.glow-pools-frame')?.getAttribute('filter') === 'url(#hp-glowfeather)' + && (frameFilter === 'url(#hp-glowfeather)' || featherTemporarilySuspended) && Number(pools?.dataset.featherPx) > 0 && Number(pools?.dataset.featherPx) <= 3 && [...sr().querySelectorAll('.glowlayer [mask], .glow-pools [filter]')].length === 0; // Дверь наружу: свет через неё не выходит — есть проём, но за ним нет пола. @@ -239,14 +242,27 @@ const res = await page.evaluate(async () => { roomEl?.dispatchEvent(new MouseEvent('mouseenter')); await c.updateComplete; const hoverFill = sr().querySelector('.room-hover-fill'); + const hoverFillLayer = sr().querySelector('.room-hover-fill-layer'); const hoverHalo = sr().querySelector('.room-hover-halo'); const hoverOutline = sr().querySelector('.room-hover-outline'); + const hoverOutlineLayer = sr().querySelector('.room-hover-outline-layer'); + const glowLayer = sr().querySelector('.glow-pools-frame'); + const wallLayer = sr().querySelector('.wallbodies'); out.hoverKeepsGlowDom = poolBeforeHover === sr().querySelector('.glow-pool') && gradientBeforeHover === sr().querySelector('radialGradient[id^="hp-glow-"]'); out.hoverUsesPlainSvg = !!roomEl && !!hoverFill && !!hoverHalo && !!hoverOutline && getComputedStyle(roomEl).filter === 'none' && getComputedStyle(hoverHalo).filter === 'none' && getComputedStyle(hoverOutline).filter === 'none'; + const fillStyle = hoverFill ? getComputedStyle(hoverFill) : null; + out.hoverUsesNeutralDarkening = fillStyle?.fill === 'rgb(0, 0, 0)' + && Math.abs(Number(fillStyle.fillOpacity) - 0.22) < 0.001; + out.hoverLayerOrder = !!hoverFillLayer && !!glowLayer && !!hoverOutlineLayer + && !!(hoverFillLayer.compareDocumentPosition(glowLayer) & Node.DOCUMENT_POSITION_FOLLOWING) + // The default smoke fixture has no thick wall body. When one exists, the + // late outline must follow it; absence is not a layer-order failure. + && (!wallLayer + || !!(wallLayer.compareDocumentPosition(hoverOutlineLayer) & Node.DOCUMENT_POSITION_FOLLOWING)); roomEl?.dispatchEvent(new MouseEvent('mouseleave')); await c.updateComplete; return out; diff --git a/demo/smoke_visual_continuity.mjs b/demo/smoke_visual_continuity.mjs new file mode 100644 index 00000000..e0d190ee --- /dev/null +++ b/demo/smoke_visual_continuity.mjs @@ -0,0 +1,77 @@ +// #73: lifecycle frame-sequence contract. The last complete plan must remain +// visible across quick/long returns; recovery tokens may advance, pixels may +// not disappear or regress to an empty/default frame. +import { launch, check, finish } from './serve.mjs'; + +const { page, browser } = await launch({ width: 820, height: 760 }); + +const result = await page.evaluate(async () => { + const out = {}; + const card = window.__card; + const root = () => card.shadowRoot || card.renderRoot; + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + const waitUntil = async (predicate, timeout = 3000) => { + const start = performance.now(); + while (!predicate() && performance.now() - start < timeout) await sleep(20); + return predicate(); + }; + await waitUntil(() => !card._booting + && root().querySelector('.stage') + && root().querySelector('ha-card')?.dataset.frameFingerprint); + + const stage = root().querySelector('.stage'); + const before = { + rooms: stage.querySelectorAll('.room').length, + viewBox: stage.querySelector('.zoomwrap > svg')?.getAttribute('viewBox'), + fingerprint: root().querySelector('ha-card')?.dataset.frameFingerprint || '', + token: Number(root().querySelector('ha-card')?.dataset.continuityToken || 0), + }; + out.hasCompleteBaseline = before.rooms > 0 && !!before.viewBox && !!before.fingerprint; + + // A short return is deliberately a direct no-op signal: no token/update. + card._pageVisibility({ + kind: 'visible', token: 1, at: Date.now(), hiddenFor: 1000, long: false, + }); + await new Promise(requestAnimationFrame); + const afterQuick = root().querySelector('ha-card'); + out.quickReturnSteady = afterQuick?.dataset.continuityState === 'steady'; + out.quickReturnKeepsToken = Number(afterQuick?.dataset.continuityToken || 0) === before.token; + + // Long return may revalidate asynchronously. Sample every presented frame. + card._pageVisibility({ + kind: 'visible', token: 2, at: Date.now(), hiddenFor: 20_000, long: true, + }); + const samples = []; + await new Promise((done) => { + let left = 36; + const sample = () => { + const liveStage = root().querySelector('.stage'); + const zoom = liveStage?.querySelector('.zoomwrap'); + const computed = zoom ? getComputedStyle(zoom) : null; + samples.push({ + rooms: liveStage?.querySelectorAll('.room').length || 0, + viewBox: liveStage?.querySelector('.zoomwrap > svg')?.getAttribute('viewBox') || '', + visible: !!computed && computed.display !== 'none' && computed.visibility !== 'hidden', + overlay: !!root().querySelector('.recoveryoverlay'), + state: root().querySelector('ha-card')?.dataset.continuityState || '', + }); + if (--left > 0) requestAnimationFrame(sample); + else done(); + }; + requestAnimationFrame(sample); + }); + out.longReturnNeverHidesPlan = samples.every((sample) => sample.visible); + out.longReturnNeverEmptiesPlan = samples.every((sample) => sample.rooms > 0); + out.longReturnKeepsViewport = samples.every((sample) => sample.viewBox === before.viewBox); + out.staleFrameNeedsNoOverlay = samples.every((sample) => !sample.overlay); + out.noLegacyResumeClass = !root().querySelector('.stage.hpresume'); + out.productionAttributesPresent = samples.every((sample) => !!sample.state); + + const trace = card.houseplanContinuityTrace?.() || []; + out.traceIsBoundedAndRedacted = trace.length > 0 && trace.length <= 80 + && !JSON.stringify(trace).includes('/api/houseplan/content/'); + return out; +}); + +for (const [name, value] of Object.entries(result)) check(name, value); +await finish(browser, result); diff --git a/demo/smoke_yellow_principle.mjs b/demo/smoke_yellow_principle.mjs index 7eb41e10..c15866a3 100644 --- a/demo/smoke_yellow_principle.mjs +++ b/demo/smoke_yellow_principle.mjs @@ -68,4 +68,41 @@ const out = await page.evaluate(() => { }) === 'on'; return o; }); +const working = page.locator('.dev.on').first(); +const neutral = page.locator('.dev:not(.on):not(.open):not(.alarm):not(.unavail)').first(); +out.hasWorkingAndNeutralMarkers = await working.count() > 0 && await neutral.count() > 0; +if (out.hasWorkingAndNeutralMarkers) { + const visual = (locator) => locator.evaluate((el) => { + const style = getComputedStyle(el); + return { + background: style.backgroundColor, + color: style.color, + shadow: style.boxShadow, + z: style.zIndex, + }; + }); + const restingWorking = await visual(working); + const restingNeutral = await visual(neutral); + out.workingUsesNeutralShadow = restingWorking.shadow === restingNeutral.shadow + && !/255,\s*212,\s*92/.test(restingWorking.shadow); + await working.hover(); + await page.waitForTimeout(180); + const hoverWorking = await visual(working); + await neutral.hover(); + await page.waitForTimeout(180); + const hoverNeutral = await visual(neutral); + out.workingHoverMatchesNeutral = hoverWorking.background === hoverNeutral.background + && hoverWorking.color === hoverNeutral.color + && hoverWorking.shadow === hoverNeutral.shadow + && hoverWorking.z === '5'; + await page.mouse.move(0, 0); + await page.waitForTimeout(180); + const restoredWorking = await visual(working); + out.workingReturnsToYellow = restoredWorking.background === restingWorking.background + && restoredWorking.color === restingWorking.color; +} else { + out.workingUsesNeutralShadow = false; + out.workingHoverMatchesNeutral = false; + out.workingReturnsToYellow = false; +} await finish(browser, checkAll(out)); diff --git a/demo/srv/assets/houseplan-card.js b/demo/srv/assets/houseplan-card.js index 6655bb0d..569edbba 100755 --- a/demo/srv/assets/houseplan-card.js +++ b/demo/srv/assets/houseplan-card.js @@ -1,5 +1,5 @@ -globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96fbc26ab50eb69b49466b3c7bab33";const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),s=new WeakMap;let o=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const i=this.t;if(e&&void 0===t){const e=void 0!==i&&1===i.length;e&&(t=s.get(i)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&s.set(i,t))}return t}toString(){return this.cssText}};const n=(t,...e)=>{const s=1===t.length?t[0]:e.reduce((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1],t[0]);return new o(s,t,i)},r=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new o("string"==typeof t?t:t+"",void 0,i))(e)})(t):t,{is:a,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:d,getPrototypeOf:p}=Object,u=globalThis,_=u.trustedTypes,m=_?_.emptyScript:"",g=u.reactiveElementPolyfillSupport,f=(t,e)=>t,v={toAttribute(t,e){switch(e){case Boolean:t=t?m:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},y=(t,e)=>!a(t,e),b={attribute:!0,type:String,converter:v,reflect:!1,useDefault:!1,hasChanged:y};Symbol.metadata??=Symbol("metadata"),u.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=b){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(t,i,e);void 0!==s&&l(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){const{get:s,set:o}=c(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:s,set(e){const n=s?.call(this);o?.call(this,e),this.requestUpdate(t,n,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??b}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const t=p(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const t=this.properties,e=[...h(t),...d(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(r(t))}else void 0!==t&&e.push(r(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.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(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(e)i.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of s){const s=document.createElement("style"),o=t.litNonce;void 0!==o&&s.setAttribute("nonce",o),s.textContent=e.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$ET(t,e){const i=this.constructor.elementProperties.get(t),s=this.constructor._$Eu(t,i);if(void 0!==s&&!0===i.reflect){const o=(void 0!==i.converter?.toAttribute?i.converter:v).toAttribute(e,i.type);this._$Em=t,null==o?this.removeAttribute(s):this.setAttribute(s,o),this._$Em=null}}_$AK(t,e){const i=this.constructor,s=i._$Eh.get(t);if(void 0!==s&&this._$Em!==s){const t=i.getPropertyOptions(s),o="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:v;this._$Em=s;const n=o.fromAttribute(e,t.type);this[s]=n??this._$Ej?.get(s)??n,this._$Em=null}}requestUpdate(t,e,i,s=!1,o){if(void 0!==t){const n=this.constructor;if(!1===s&&(o=this[t]),i??=n.getPropertyOptions(t),!((i.hasChanged??y)(o,e)||i.useDefault&&i.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,i))))return;this.C(t,e,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:i,reflect:s,wrapped:o},n){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),!0!==o||void 0!==n)||(this._$AL.has(t)||(this.hasUpdated||i||(e=void 0),this._$AL.set(t,e)),!0===s&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,i]of t){const{wrapped:t}=i,s=this[e];!0!==t||this._$AL.has(e)||void 0===s||this.C(e,void 0,i,s)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};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,$=t=>t,x=k.trustedTypes,S=x?x.createPolicy("lit-html",{createHTML:t=>t}):void 0,M="$lit$",C=`lit$${Math.random().toFixed(9).slice(2)}$`,D="?"+C,T=`<${D}>`,P=document,R=()=>P.createComment(""),A=t=>null===t||"object"!=typeof t&&"function"!=typeof t,z=Array.isArray,I="[ \t\n\f\r]",E=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,N=/-->/g,F=/>/g,O=RegExp(`>|${I}(?:([^\\s"'>=/]+)(${I}*=${I}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),L=/'/g,H=/"/g,q=/^(?:script|style|textarea|title)$/i,U=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),B=U(1),j=U(2),G=Symbol.for("lit-noChange"),W=Symbol.for("lit-nothing"),V=new WeakMap,K=P.createTreeWalker(P,129);function Y(t,e){if(!z(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const J=(t,e)=>{const i=t.length-1,s=[];let o,n=2===e?"":3===e?"":"",r=E;for(let e=0;e"===l[0]?(r=o??E,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?O:'"'===l[3]?H:L):r===H||r===L?r=O:r===N||r===F?r=E:(r=O,o=void 0);const d=r===O&&t[e+1].startsWith("/>")?" ":"";n+=r===E?i+T:c>=0?(s.push(a),i.slice(0,c)+M+i.slice(c)+C+d):i+C+(-2===c?e:d)}return[Y(t,n+(t[i]||"")+(2===e?"":3===e?"":"")),s]};class Z{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let o=0,n=0;const r=t.length-1,a=this.parts,[l,c]=J(t,e);if(this.el=Z.createElement(l,i),K.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(s=K.nextNode())&&a.length0){s.textContent=x?x.emptyScript:"";for(let i=0;iz(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==W&&A(this._$AH)?this._$AA.nextSibling.data=t:this.T(P.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:i}=t,s="number"==typeof i?this._$AC(t):(void 0===i.el&&(i.el=Z.createElement(Y(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(e);else{const t=new Q(s,this),i=t.u(this.options);t.p(e),this.T(i),this._$AH=t}}_$AC(t){let e=V.get(t.strings);return void 0===e&&V.set(t.strings,e=new Z(t)),e}k(t){z(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const o of t)s===e.length?e.push(i=new tt(this.O(R()),this.O(R()),this,this.options)):i=e[s],i._$AI(o),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=W}_$AI(t,e=this,i,s){const o=this.strings;let n=!1;if(void 0===o)t=X(this,t,e,0),n=!A(t)||t!==this._$AH&&t!==G,n&&(this._$AH=t);else{const s=t;let r,a;for(t=o[0],r=0;r{const s=i?.renderBefore??e;let o=s._$litPart$;if(void 0===o){const t=i?.renderBefore??null;s._$litPart$=o=new tt(e.insertBefore(R(),t),t,void 0,i??{})}return o._$AI(t),o})(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return G}};ct._$litElement$=!0,ct.finalized=!0,lt.litElementHydrateSupport?.({LitElement:ct});const ht=lt.litElementPolyfillSupport;ht?.({LitElement:ct}),(lt.litElementVersions??=[]).push("4.2.2");const dt=2;let pt=class{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}};const{I:ut}=rt,_t=t=>t,mt=()=>document.createComment(""),gt=(t,e,i)=>{const s=t._$AA.parentNode,o=void 0===e?t._$AB:e._$AA;if(void 0===i){const e=s.insertBefore(mt(),o),n=s.insertBefore(mt(),o);i=new ut(e,n,t,t.options)}else{const e=i._$AB.nextSibling,n=i._$AM,r=n!==t;if(r){let e;i._$AQ?.(t),i._$AM=t,void 0!==i._$AP&&(e=t._$AU)!==n._$AU&&i._$AP(e)}if(e!==o||r){let t=i._$AA;for(;t!==e;){const e=_t(t).nextSibling;_t(s).insertBefore(t,o),t=e}}}return i},ft=(t,e,i=t)=>(t._$AI(e,i),t),vt={},yt=(t,e=vt)=>t._$AH=e,bt=t=>{t._$AR(),t._$AA.remove()},wt=(t,e,i)=>{const s=new Map;for(let o=e;o<=i;o++)s.set(t[o],o);return s},kt=(t=>(...e)=>({_$litDirective$:t,values:e}))(class extends pt{constructor(t){if(super(t),t.type!==dt)throw Error("repeat() can only be used in text expressions")}dt(t,e,i){let s;void 0===i?i=e:void 0!==e&&(s=e);const o=[],n=[];let r=0;for(const e of t)o[r]=s?s(e,r):r,n[r]=i(e,r),r++;return{values:n,keys:o}}render(t,e,i){return this.dt(t,e,i).values}update(t,[e,i,s]){const o=(t=>t._$AH)(t),{values:n,keys:r}=this.dt(e,i,s);if(!Array.isArray(o))return this.ut=r,n;const a=this.ut??=[],l=[];let c,h,d=0,p=o.length-1,u=0,_=n.length-1;for(;d<=p&&u<=_;)if(null===o[d])d++;else if(null===o[p])p--;else if(a[d]===r[u])l[u]=ft(o[d],n[u]),d++,u++;else if(a[p]===r[_])l[_]=ft(o[p],n[_]),p--,_--;else if(a[d]===r[_])l[_]=ft(o[d],n[_]),gt(t,l[_+1],o[d]),d++,_--;else if(a[p]===r[u])l[u]=ft(o[p],n[u]),gt(t,o[d],o[p]),p--,u++;else if(void 0===c&&(c=wt(r,u,_),h=wt(a,d,p)),c.has(a[d]))if(c.has(a[p])){const e=h.get(r[u]),i=void 0!==e?o[e]:null;if(null===i){const e=gt(t,o[d]);ft(e,n[u]),l[u]=e}else l[u]=ft(i,n[u]),gt(t,o[d],i),o[e]=null;u++}else bt(o[p]),p--;else bt(o[d]),d++;for(;u<=_;){const e=gt(t,l[_+1]);ft(e,n[u]),l[u++]=e}for(;d<=p;){const t=o[d++];null!==t&&bt(t)}return this.ut=r,yt(t,l),G}}),$t=new WeakMap;let xt=0;class St extends ct{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._titleId="hp-dialog-title-"+ ++xt,this._focusInitial=()=>{const t=this.querySelector("[autofocus]")||this._focusableElements()[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=t=>{if("Escape"===t.key)return t.preventDefault(),t.stopImmediatePropagation(),void this._requestClose();if("Tab"!==t.key||this._useHaDialog)return;const e=this.renderRoot.querySelector(".close"),i=e?[e,...this._focusableElements()]:this._focusableElements();if(!i.length)return t.preventDefault(),void this.renderRoot.querySelector(".surface")?.focus({preventScroll:!0});const s=this._deepActiveElement(),o=i[0],n=i[i.length-1];!t.shiftKey||s!==o&&i.includes(s)?t.shiftKey||s!==n||(t.preventDefault(),o.focus()):(t.preventDefault(),n.focus())},this._onFallbackCancel=t=>{t.preventDefault(),this._requestClose()},this._onFallbackClick=t=>{this.dismissOnScrim&&t.target===t.currentTarget&&this._requestClose()}}connectedCallback(){super.connectedCallback(),this._opener=this._deepActiveElement();const t=this.getRootNode();this._focusRoot=t;const e=$t.get(t)||{dialogs:new Set,opener:this._opener};e.dialogs.add(this),$t.set(t,e),this._useHaDialog=!!customElements.get("ha-dialog"),this.addEventListener("keydown",this._onKeyDown,!0)}disconnectedCallback(){this.removeEventListener("keydown",this._onKeyDown,!0);const t=this._focusRoot,e=this._opener;this._opener=null,this._focusRoot=null;const i=t?$t.get(t):void 0;i?.dialogs.delete(this),super.disconnectedCallback(),t&&i&&requestAnimationFrame(()=>{const i=$t.get(t);if(!i)return;if(!i.dialogs.size)return i.opener?.isConnected&&i.opener.focus({preventScroll:!0}),void $t.delete(t);const s=e?.closest("hp-dialog");e?.isConnected&&s&&i.dialogs.has(s)&&e.focus({preventScroll:!0})})}firstUpdated(t){if(super.firstUpdated(t),!this._useHaDialog){const t=this.renderRoot.querySelector("dialog");t&&!t.open&&t.showModal()}queueMicrotask(()=>this._focusInitial())}_deepActiveElement(){let t=document.activeElement;for(;t?.shadowRoot?.activeElement;)t=t.shadowRoot.activeElement;return t&&t!==document.body?t:null}_focusableElements(){const t=["[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(",");return Array.from(this.querySelectorAll(t)).filter(t=>{const e=getComputedStyle(t);return!t.hidden&&"none"!==e.display&&"hidden"!==e.visibility})}render(){const t=B` - ${this.icon?B``:W} +globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="0f963f24ce2ba631daf1931ac9ffd44c7326b1bfee5fca1dac9b3a1678c892f7";const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),s=new WeakMap;let n=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const i=this.t;if(e&&void 0===t){const e=void 0!==i&&1===i.length;e&&(t=s.get(i)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&s.set(i,t))}return t}toString(){return this.cssText}};const o=(t,...e)=>{const s=1===t.length?t[0]:e.reduce((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1],t[0]);return new n(s,t,i)},r=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new n("string"==typeof t?t:t+"",void 0,i))(e)})(t):t,{is:a,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:d,getPrototypeOf:p}=Object,u=globalThis,_=u.trustedTypes,m=_?_.emptyScript:"",g=u.reactiveElementPolyfillSupport,f=(t,e)=>t,v={toAttribute(t,e){switch(e){case Boolean:t=t?m:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},y=(t,e)=>!a(t,e),b={attribute:!0,type:String,converter:v,reflect:!1,useDefault:!1,hasChanged:y};Symbol.metadata??=Symbol("metadata"),u.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=b){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(t,i,e);void 0!==s&&l(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){const{get:s,set:n}=c(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:s,set(e){const o=s?.call(this);n?.call(this,e),this.requestUpdate(t,o,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??b}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const t=p(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const t=this.properties,e=[...h(t),...d(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(r(t))}else void 0!==t&&e.push(r(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.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(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(e)i.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of s){const s=document.createElement("style"),n=t.litNonce;void 0!==n&&s.setAttribute("nonce",n),s.textContent=e.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$ET(t,e){const i=this.constructor.elementProperties.get(t),s=this.constructor._$Eu(t,i);if(void 0!==s&&!0===i.reflect){const n=(void 0!==i.converter?.toAttribute?i.converter:v).toAttribute(e,i.type);this._$Em=t,null==n?this.removeAttribute(s):this.setAttribute(s,n),this._$Em=null}}_$AK(t,e){const i=this.constructor,s=i._$Eh.get(t);if(void 0!==s&&this._$Em!==s){const t=i.getPropertyOptions(s),n="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:v;this._$Em=s;const o=n.fromAttribute(e,t.type);this[s]=o??this._$Ej?.get(s)??o,this._$Em=null}}requestUpdate(t,e,i,s=!1,n){if(void 0!==t){const o=this.constructor;if(!1===s&&(n=this[t]),i??=o.getPropertyOptions(t),!((i.hasChanged??y)(n,e)||i.useDefault&&i.reflect&&n===this._$Ej?.get(t)&&!this.hasAttribute(o._$Eu(t,i))))return;this.C(t,e,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:i,reflect:s,wrapped:n},o){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,o??e??this[t]),!0!==n||void 0!==o)||(this._$AL.has(t)||(this.hasUpdated||i||(e=void 0),this._$AL.set(t,e)),!0===s&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,i]of t){const{wrapped:t}=i,s=this[e];!0!==t||this._$AL.has(e)||void 0===s||this.C(e,void 0,i,s)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};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,$=t=>t,x=k.trustedTypes,S=x?x.createPolicy("lit-html",{createHTML:t=>t}):void 0,M="$lit$",C=`lit$${Math.random().toFixed(9).slice(2)}$`,D="?"+C,T=`<${D}>`,R=document,P=()=>R.createComment(""),A=t=>null===t||"object"!=typeof t&&"function"!=typeof t,z=Array.isArray,I="[ \t\n\f\r]",E=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,F=/-->/g,N=/>/g,O=RegExp(`>|${I}(?:([^\\s"'>=/]+)(${I}*=${I}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),L=/'/g,H=/"/g,q=/^(?:script|style|textarea|title)$/i,U=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),B=U(1),W=U(2),j=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),V=new WeakMap,K=R.createTreeWalker(R,129);function Y(t,e){if(!z(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const J=(t,e)=>{const i=t.length-1,s=[];let n,o=2===e?"":3===e?"":"",r=E;for(let e=0;e"===l[0]?(r=n??E,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?O:'"'===l[3]?H:L):r===H||r===L?r=O:r===F||r===N?r=E:(r=O,n=void 0);const d=r===O&&t[e+1].startsWith("/>")?" ":"";o+=r===E?i+T:c>=0?(s.push(a),i.slice(0,c)+M+i.slice(c)+C+d):i+C+(-2===c?e:d)}return[Y(t,o+(t[i]||"")+(2===e?"":3===e?"":"")),s]};class Z{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let n=0,o=0;const r=t.length-1,a=this.parts,[l,c]=J(t,e);if(this.el=Z.createElement(l,i),K.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(s=K.nextNode())&&a.length0){s.textContent=x?x.emptyScript:"";for(let i=0;iz(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==G&&A(this._$AH)?this._$AA.nextSibling.data=t:this.T(R.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:i}=t,s="number"==typeof i?this._$AC(t):(void 0===i.el&&(i.el=Z.createElement(Y(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(e);else{const t=new Q(s,this),i=t.u(this.options);t.p(e),this.T(i),this._$AH=t}}_$AC(t){let e=V.get(t.strings);return void 0===e&&V.set(t.strings,e=new Z(t)),e}k(t){z(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const n of t)s===e.length?e.push(i=new tt(this.O(P()),this.O(P()),this,this.options)):i=e[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(t,e=this,i,s){const n=this.strings;let o=!1;if(void 0===n)t=X(this,t,e,0),o=!A(t)||t!==this._$AH&&t!==j,o&&(this._$AH=t);else{const s=t;let r,a;for(t=n[0],r=0;r{const s=i?.renderBefore??e;let n=s._$litPart$;if(void 0===n){const t=i?.renderBefore??null;s._$litPart$=n=new tt(e.insertBefore(P(),t),t,void 0,i??{})}return n._$AI(t),n})(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return j}};ct._$litElement$=!0,ct.finalized=!0,lt.litElementHydrateSupport?.({LitElement:ct});const ht=lt.litElementPolyfillSupport;ht?.({LitElement:ct}),(lt.litElementVersions??=[]).push("4.2.2");const dt=2;let pt=class{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}};const{I:ut}=rt,_t=t=>t,mt=()=>document.createComment(""),gt=(t,e,i)=>{const s=t._$AA.parentNode,n=void 0===e?t._$AB:e._$AA;if(void 0===i){const e=s.insertBefore(mt(),n),o=s.insertBefore(mt(),n);i=new ut(e,o,t,t.options)}else{const e=i._$AB.nextSibling,o=i._$AM,r=o!==t;if(r){let e;i._$AQ?.(t),i._$AM=t,void 0!==i._$AP&&(e=t._$AU)!==o._$AU&&i._$AP(e)}if(e!==n||r){let t=i._$AA;for(;t!==e;){const e=_t(t).nextSibling;_t(s).insertBefore(t,n),t=e}}}return i},ft=(t,e,i=t)=>(t._$AI(e,i),t),vt={},yt=(t,e=vt)=>t._$AH=e,bt=t=>{t._$AR(),t._$AA.remove()},wt=(t,e,i)=>{const s=new Map;for(let n=e;n<=i;n++)s.set(t[n],n);return s},kt=(t=>(...e)=>({_$litDirective$:t,values:e}))(class extends pt{constructor(t){if(super(t),t.type!==dt)throw Error("repeat() can only be used in text expressions")}dt(t,e,i){let s;void 0===i?i=e:void 0!==e&&(s=e);const n=[],o=[];let r=0;for(const e of t)n[r]=s?s(e,r):r,o[r]=i(e,r),r++;return{values:o,keys:n}}render(t,e,i){return this.dt(t,e,i).values}update(t,[e,i,s]){const n=(t=>t._$AH)(t),{values:o,keys:r}=this.dt(e,i,s);if(!Array.isArray(n))return this.ut=r,o;const a=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(a[d]===r[u])l[u]=ft(n[d],o[u]),d++,u++;else if(a[p]===r[_])l[_]=ft(n[p],o[_]),p--,_--;else if(a[d]===r[_])l[_]=ft(n[d],o[_]),gt(t,l[_+1],n[d]),d++,_--;else if(a[p]===r[u])l[u]=ft(n[p],o[u]),gt(t,n[d],n[p]),p--,u++;else if(void 0===c&&(c=wt(r,u,_),h=wt(a,d,p)),c.has(a[d]))if(c.has(a[p])){const e=h.get(r[u]),i=void 0!==e?n[e]:null;if(null===i){const e=gt(t,n[d]);ft(e,o[u]),l[u]=e}else l[u]=ft(i,o[u]),gt(t,n[d],i),n[e]=null;u++}else bt(n[p]),p--;else bt(n[d]),d++;for(;u<=_;){const e=gt(t,l[_+1]);ft(e,o[u]),l[u++]=e}for(;d<=p;){const t=n[d++];null!==t&&bt(t)}return this.ut=r,yt(t,l),j}}),$t=new WeakMap;let xt=0;class St extends ct{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._titleId="hp-dialog-title-"+ ++xt,this._focusInitial=()=>{const t=this.querySelector("[autofocus]")||this._focusableElements()[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=t=>{if("Escape"===t.key)return t.preventDefault(),t.stopImmediatePropagation(),void this._requestClose();if("Tab"!==t.key||this._useHaDialog)return;const e=this.renderRoot.querySelector(".close"),i=e?[e,...this._focusableElements()]:this._focusableElements();if(!i.length)return t.preventDefault(),void this.renderRoot.querySelector(".surface")?.focus({preventScroll:!0});const s=this._deepActiveElement(),n=i[0],o=i[i.length-1];!t.shiftKey||s!==n&&i.includes(s)?t.shiftKey||s!==o||(t.preventDefault(),n.focus()):(t.preventDefault(),o.focus())},this._onFallbackCancel=t=>{t.preventDefault(),this._requestClose()},this._onFallbackClick=t=>{this.dismissOnScrim&&t.target===t.currentTarget&&this._requestClose()}}connectedCallback(){super.connectedCallback(),this._opener=this._deepActiveElement();const t=this.getRootNode();this._focusRoot=t;const e=$t.get(t)||{dialogs:new Set,opener:this._opener};e.dialogs.add(this),$t.set(t,e),this._useHaDialog=!!customElements.get("ha-dialog"),this.addEventListener("keydown",this._onKeyDown,!0)}disconnectedCallback(){this.removeEventListener("keydown",this._onKeyDown,!0);const t=this._focusRoot,e=this._opener;this._opener=null,this._focusRoot=null;const i=t?$t.get(t):void 0;i?.dialogs.delete(this),super.disconnectedCallback(),t&&i&&requestAnimationFrame(()=>{const i=$t.get(t);if(!i)return;if(!i.dialogs.size)return i.opener?.isConnected&&i.opener.focus({preventScroll:!0}),void $t.delete(t);const s=e?.closest("hp-dialog");e?.isConnected&&s&&i.dialogs.has(s)&&e.focus({preventScroll:!0})})}firstUpdated(t){if(super.firstUpdated(t),!this._useHaDialog){const t=this.renderRoot.querySelector("dialog");t&&!t.open&&t.showModal()}queueMicrotask(()=>this._focusInitial())}_deepActiveElement(){let t=document.activeElement;for(;t?.shadowRoot?.activeElement;)t=t.shadowRoot.activeElement;return t&&t!==document.body?t:null}_focusableElements(){const t=["[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(",");return Array.from(this.querySelectorAll(t)).filter(t=>{const e=getComputedStyle(t);return!t.hidden&&"none"!==e.display&&"hidden"!==e.visibility})}render(){const t=B` + ${this.icon?B``:G} ${this.title} `;return this._useHaDialog?B` - `}}St.properties={title:{type:String},icon:{type:String},wide:{type:Boolean,reflect:!0},dismissOnScrim:{type:Boolean,attribute:"dismiss-on-scrim"},hass:{attribute:!1}},St.styles=n` + `}}St.properties={title:{type:String},icon:{type:String},wide:{type:Boolean,reflect:!0},dismissOnScrim:{type:Boolean,attribute:"dismiss-on-scrim"},hass:{attribute:!1}},St.styles=o` :host { display: contents; color: var(--primary-text-color, #e6e7eb); @@ -191,8 +191,8 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 display: flex; flex-direction: column; } - `,customElements.get("hp-dialog")||customElements.define("hp-dialog",St);const Mt=/^#[0-9a-fA-F]{6}$/;function Ct(t,e){return"string"==typeof t&&Mt.test(t)?t:e}const Dt="(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])",Tt=new RegExp(`^rgb\\(${Dt}, ${Dt}, ${Dt}\\)$`);function Pt(t){if(!Array.isArray(t)||t.length<3)return null;const e=t.slice(0,3);if(!e.every(t=>"number"==typeof t&&Number.isFinite(t)))return null;const[i,s,o]=e.map(t=>Math.round(Math.min(255,Math.max(0,t))));return`rgb(${i}, ${s}, ${o})`}class Rt extends ct{constructor(){super(...arguments),this.label="",this.opacityLabel="Opacity",this.color="#607d8b",this.opacity=1,this.disabled=!1,this.showOpacity=!0,this._open=!1,this._pickerRaf=0,this._outsidePointerDown=t=>{this._open&&!t.composedPath().includes(this)&&this._closePicker()},this._keyDown=t=>{this._open&&"Escape"===t.key&&(t.preventDefault(),t.stopImmediatePropagation(),this._closePicker(!0))},this._queuePickerPosition=()=>{this._open&&(this._pickerRaf&&cancelAnimationFrame(this._pickerRaf),this._pickerRaf=requestAnimationFrame(()=>{this._pickerRaf=0,this._positionPicker()}))}}connectedCallback(){super.connectedCallback(),document.addEventListener("pointerdown",this._outsidePointerDown,!0),document.addEventListener("scroll",this._queuePickerPosition,!0),window.addEventListener("resize",this._queuePickerPosition),this.addEventListener("keydown",this._keyDown,!0)}disconnectedCallback(){document.removeEventListener("pointerdown",this._outsidePointerDown,!0),document.removeEventListener("scroll",this._queuePickerPosition,!0),window.removeEventListener("resize",this._queuePickerPosition),this.removeEventListener("keydown",this._keyDown,!0),this._pickerRaf&&cancelAnimationFrame(this._pickerRaf),this._pickerRaf=0,super.disconnectedCallback()}_toggle(){this.disabled||(this._open?this._closePicker():(this._open=!0,this.updateComplete.then(()=>this._positionPicker())))}_closePicker(t=!1){const e=this.renderRoot.querySelector(".picker");if(e?.hidePopover)try{e.matches(":popover-open")&&e.hidePopover()}catch{}this._open=!1,t&&this.updateComplete.then(()=>this.renderRoot.querySelector(".trigger")?.focus())}_positionPicker(){if(!this._open)return;const t=this.renderRoot.querySelector(".trigger"),e=this.renderRoot.querySelector(".picker");if(!t||!e)return;if(e.style.visibility="hidden",e.showPopover)try{e.matches(":popover-open")||e.showPopover()}catch{}const i=t.getBoundingClientRect(),s=e.getBoundingClientRect(),o=Math.max(8,window.innerWidth-s.width-8);let n=i.left;n+s.width>window.innerWidth-8&&(n=i.right-s.width),n=Math.min(o,Math.max(8,n));const r=i.bottom+7,a=i.top-7-s.height;let l=r;l=r+s.height>window.innerHeight-8&&a>=8?a:Math.min(Math.max(8,l),Math.max(8,window.innerHeight-s.height-8)),e.style.left=`${Math.round(n)}px`,e.style.top=`${Math.round(l)}px`,e.style.visibility=""}_emit(t,e){const i=Number.isFinite(e)?e:Number(this.opacity)||0,s=Math.min(1,Math.max(0,i));this.color=t,this.opacity=s,this.dispatchEvent(new CustomEvent("hp-color-opacity-change",{detail:{color:t,opacity:s},bubbles:!0,composed:!0}))}render(){const t=Math.round(100*Math.min(1,Math.max(0,Number(this.opacity)||0))),e=Ct(this.color,"#607d8b"),i=this.showOpacity?`${this.label||"Color"}: ${e}, ${t}%`:`${this.label||"Color"}: ${e}`;return B` - ${this.label?B`${this.label}`:W} + `,customElements.get("hp-dialog")||customElements.define("hp-dialog",St);const Mt=/^#[0-9a-fA-F]{6}$/;function Ct(t,e){return"string"==typeof t&&Mt.test(t)?t:e}const Dt="(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])",Tt=new RegExp(`^rgb\\(${Dt}, ${Dt}, ${Dt}\\)$`);function Rt(t){if(!Array.isArray(t)||t.length<3)return null;const e=t.slice(0,3);if(!e.every(t=>"number"==typeof t&&Number.isFinite(t)))return null;const[i,s,n]=e.map(t=>Math.round(Math.min(255,Math.max(0,t))));return`rgb(${i}, ${s}, ${n})`}class Pt extends ct{constructor(){super(...arguments),this.label="",this.opacityLabel="Opacity",this.color="#607d8b",this.opacity=1,this.disabled=!1,this.showOpacity=!0,this._open=!1,this._pickerRaf=0,this._outsidePointerDown=t=>{this._open&&!t.composedPath().includes(this)&&this._closePicker()},this._keyDown=t=>{this._open&&"Escape"===t.key&&(t.preventDefault(),t.stopImmediatePropagation(),this._closePicker(!0))},this._queuePickerPosition=()=>{this._open&&(this._pickerRaf&&cancelAnimationFrame(this._pickerRaf),this._pickerRaf=requestAnimationFrame(()=>{this._pickerRaf=0,this._positionPicker()}))}}connectedCallback(){super.connectedCallback(),document.addEventListener("pointerdown",this._outsidePointerDown,!0),document.addEventListener("scroll",this._queuePickerPosition,!0),window.addEventListener("resize",this._queuePickerPosition),this.addEventListener("keydown",this._keyDown,!0)}disconnectedCallback(){document.removeEventListener("pointerdown",this._outsidePointerDown,!0),document.removeEventListener("scroll",this._queuePickerPosition,!0),window.removeEventListener("resize",this._queuePickerPosition),this.removeEventListener("keydown",this._keyDown,!0),this._pickerRaf&&cancelAnimationFrame(this._pickerRaf),this._pickerRaf=0,super.disconnectedCallback()}_toggle(){this.disabled||(this._open?this._closePicker():(this._open=!0,this.updateComplete.then(()=>this._positionPicker())))}_closePicker(t=!1){const e=this.renderRoot.querySelector(".picker");if(e?.hidePopover)try{e.matches(":popover-open")&&e.hidePopover()}catch{}this._open=!1,t&&this.updateComplete.then(()=>this.renderRoot.querySelector(".trigger")?.focus())}_positionPicker(){if(!this._open)return;const t=this.renderRoot.querySelector(".trigger"),e=this.renderRoot.querySelector(".picker");if(!t||!e)return;if(e.style.visibility="hidden",e.showPopover)try{e.matches(":popover-open")||e.showPopover()}catch{}const i=t.getBoundingClientRect(),s=e.getBoundingClientRect(),n=Math.max(8,window.innerWidth-s.width-8);let o=i.left;o+s.width>window.innerWidth-8&&(o=i.right-s.width),o=Math.min(n,Math.max(8,o));const r=i.bottom+7,a=i.top-7-s.height;let l=r;l=r+s.height>window.innerHeight-8&&a>=8?a:Math.min(Math.max(8,l),Math.max(8,window.innerHeight-s.height-8)),e.style.left=`${Math.round(o)}px`,e.style.top=`${Math.round(l)}px`,e.style.visibility=""}_emit(t,e){const i=Number.isFinite(e)?e:Number(this.opacity)||0,s=Math.min(1,Math.max(0,i));this.color=t,this.opacity=s,this.dispatchEvent(new CustomEvent("hp-color-opacity-change",{detail:{color:t,opacity:s},bubbles:!0,composed:!0}))}render(){const t=Math.round(100*Math.min(1,Math.max(0,Number(this.opacity)||0))),e=Ct(this.color,"#607d8b"),i=this.showOpacity?`${this.label||"Color"}: ${e}, ${t}%`:`${this.label||"Color"}: ${e}`;return B` + ${this.label?B`${this.label}`:G} `:W} - `}}pn.properties={hass:{attribute:!1},presentation:{attribute:!1},registry:{attribute:!1},deviceName:{attribute:!1}},pn.styles=[At,n` + `:G} + `}}po.properties={hass:{attribute:!1},presentation:{attribute:!1},registry:{attribute:!1},deviceName:{attribute:!1}},po.styles=[At,o` :host { display: block; min-width: 0; } .devicepreview { margin: 4px 0 14px; @@ -3389,80 +3416,102 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 .previewstage { min-height: 142px; } .previewfacts > div { grid-template-columns: minmax(0, 1fr); gap: 2px; } } - `],customElements.get("hp-device-preview")||customElements.define("hp-device-preview",pn);const un=(t,e)=>[t[0]-e[0],t[1]-e[1]],_n=(t,e)=>[t[0]+e[0],t[1]+e[1]],mn=(t,e)=>t[0]*e[0]+t[1]*e[1],gn=t=>Math.hypot(t[0],t[1]);function fn(t){let e=0;for(let i=0;it[1]!=l>t[1]&&t[0]<(a-n)*(t[1]-r)/(l-r)+n&&(i=!i)}return i}([a[0]+r[0]*l,a[1]+r[1]*l],t)&&(r=[-r[0],-r[1]]),r}function bn(t,e,i,s){const o=s||yn(t,e),n=(e+1)%t.length;return t.map((t,s)=>s===e||s===n?[t[0]+o[0]*i,t[1]+o[1]*i]:[...t])}function wn(t,e,i,s){const o=[],n=un(i,e),r=gn(n);if(rs||h>s)continue;const d=mn(un(n,e),a),p=mn(un(l,e),a),u=Math.max(0,Math.min(d,p)),_=Math.min(r,Math.max(d,p));_-u>s&&o.push([[e[0]+a[0]*u,e[1]+a[1]*u],[e[0]+a[0]*_,e[1]+a[1]*_]])}return o}function kn(t,e,i,s,o){const n=wn(t,e,i,o);if(!n.length)return null;const r=t=>n.some(([e,i])=>vn(t,e,i)<=o),a=t.length,l=[];for(let n=0;no||f>o)continue;const v=mn(un(e,c),u),y=mn(un(i,c),u),b=Math.max(0,Math.min(v,y)),w=Math.min(p,Math.max(v,y));if(!(w-b<=o)){if(b>o&&bo&&wgn(un(i,t[(s+1)%t.length]))>e);for(let t=0;t<2;t++)i=i.filter((t,s)=>{const o=i[(s-1+i.length)%i.length],n=i[(s+1)%i.length],r=(t[0]-o[0])*(n[1]-o[1])-(t[1]-o[1])*(n[0]-o[0]),a=gn(un(n,o))||1;return Math.abs(r)/a>e});return i.length>=3?i:t}function xn(t){const e=t.length;if(e<3)return!1;for(let i=0;i(t[0]-o[0])*a[1]-(t[1]-o[1])*a[0],c=t=>(t[0]-o[0])*a[0]+(t[1]-o[1])*a[1],h=i,d=r-i;for(const e of t){const t=Math.abs(l(e));if(t<=i)continue;const o=c(e);o<=h||o>=d||tMath.abs(r+(t-p)/g*(a-r));s=Math.min(s,f(_),f(m))}}return s}function Mn(t,e,i){if(ui(t,e,i))return!0;if(pi(t,e,i)||pi(e,t,i))return!1;let s=0;try{const i=je([[...t.map(t=>[t[0],t[1]]),[t[0][0],t[0][1]]]],[[...e.map(t=>[t[0],t[1]]),[e[0][0],e[0][1]]]]);for(const t of i)t?.[0]&&(s+=mi(t[0]))}catch{return!1}return s>Math.max(1e-7,i*i)}function Cn(t,e,i,s,o){const n=[i.n[0]*s,i.n[1]*s],r={polys:{},openings:{},movedSpans:{}};if(Math.abs(s)<1e-9)return r;for(const e of t){if(e.id===i.roomId){r.polys[e.id]=bn(e.poly,i.edge,s,i.n),r.movedSpans[e.id]=[[_n(i.a,n),_n(i.b,n)]];continue}const t=wn(e.poly,i.a,i.b,o);if(!t.length)continue;const a=kn(e.poly,i.a,i.b,n,o);a&&(r.polys[e.id]=a,r.movedSpans[e.id]=t.map(([t,e])=>[_n(t,n),_n(e,n)]))}for(const t of e)vn([t.x,t.y],i.a,i.b)<=o&&(r.openings[t.id]=[t.x+n[0],t.y+n[1]]);return r}function Dn(t,e,i){for(const s of e)for(let e=0;ei)continue;const c=(t.x-o[0])*l[0]+(t.y-o[1])*l[1];if(c-t.length/2>=-i&&c+t.length/2<=a+i)return!0}return!1}function Tn(t,e,i){return t.filter(t=>e.some(e=>{for(let s=0;sa.polys[t.id]||t.poly;for(const e of l){const s=t.find(t=>t.id===e),o=a.polys[e];if(!xn(o))return!1;const l=fn(s.poly),h=fn(o);if(Math.abs(h)t.find(t=>t.id===e).poly),d=t.map(c);for(const t of Tn(e,h,2*r)){const e=a.openings[t.id];if(!Dn(e?{...t,x:e[0],y:e[1]}:t,d,2*r))return!1}return!0}function Rn(t,e,i,s,o,n){const r=t=>[s[0]+(t[0]-s[0])*o,s[1]+(t[1]-s[1])*o],a={poly:t.poly.map(r),openings:{}};for(const s of e){let e=!1;for(let i=0;i{for(let e=0;et.id===i);if(!l)return!1;const c=t.filter(t=>t.id!==i).map(t=>t.poly),h=Rn(l,e,c,s,o,2*a),d=h.poly,p=function(t){const e=function(t){const e=[...t].sort((t,e)=>t[0]-e[0]||t[1]-e[1]);if(e.length<3)return e;const i=(t,e,i)=>(e[0]-t[0])*(i[1]-t[1])-(e[1]-t[1])*(i[0]-t[0]),s=[];for(const t of e){for(;s.length>=2&&i(s[s.length-2],s[s.length-1],t)<=0;)s.pop();s.push(t)}const o=[];for(let t=e.length-1;t>=0;t--){const s=e[t];for(;o.length>=2&&i(o[o.length-2],o[o.length-1],s)<=0;)o.pop();o.push(s)}return s.pop(),o.pop(),s.concat(o)}(t);if(e.length<3)return 0;let i=1/0;for(let t=0;tt.id===i?d:t.poly);for(const t of Tn(e,[l.poly],2*a)){const e=h.openings[t.id];if(!Dn(e?{...t,x:e[0],y:e[1]}:t,u,2*a))return!1}return!0}function zn(t,e,i){const s=i/e;return mi(t)*s*s/1e4}function In(t,e){return e?`${Math.round(10.7639*t)} ft²`:`${(Math.round(10*t)/10).toFixed(1)} m²`}const En=[[-90,"#070c14"],[-12,"#070c14"],[-4,"#131a28"],[0,"#4a3527"],[10,"#e8ddcf"],[30,"#ffffff"],[90,"#ffffff"]],Nn=t=>Math.min(1,Math.max(0,t));function Fn(t){const e=Math.min(90,Math.max(-90,Number(t)||0));let i=En[En.length-1][1];for(let t=1;t{const n=[t.x+o[0]*i*s,t.y+o[1]*i*s];return e.find(t=>t.poly.length>=3&&si(n,t.poly))||null},r=n(1),a=n(-1);return r&&a?null:r||a?r?{normal:[-o[0],-o[1]],roomId:r.id}:{normal:o,roomId:a.id}:null}function Ln(t,e,i){return i>0&&t[0]*e[0]+t[1]*e[1]>.05}function Hn(t,e,i,s){return[[t[0],t[1]],[e[0],e[1]],[e[0]+i[0]*s,e[1]+i[1]*s],[t[0]+i[0]*s,t[1]+i[1]*s]]}function qn(t,e){try{const i=je([[...t.map(t=>[t[0],t[1]]),[t[0][0],t[0][1]]]],[[...e.map(t=>[t[0],t[1]]),[e[0][0],e[0][1]]]]),s=[];for(const t of i){const e=t?.[0];!Array.isArray(e)||e.length<4||s.push(e.slice(0,e.length-1).map(t=>[t[0],t[1]]))}return s}catch{return[]}}function Un(t,e,i,s,o,n,r){if(!(s>0))return[];const a=function(t,e){const i=function(t,e){return function(t){const e=t%360;return e<0?e+360:e}(t-e)}(t,e)*Math.PI/180;return[Math.sin(i),-Math.cos(i)]}(i,o),l=[-a[0],-a[1]],c=function(t){const e=Math.min(90,Math.max(0,t));return.7*(.8+1.7*Math.pow(1-e/90,1.6))}(s),h=[];for(const i of e){if(!(i.length>0))continue;const e=On(i,t);if(!e||!Ln(e.normal,a,s))continue;const o=t.find(t=>t.id===e.roomId);if(!o)continue;const d=n&&n[e.roomId]||o.poly,p=i.angle*Math.PI/180,u=i.length/2,_=[-e.normal[0],-e.normal[1]],m=Math.max(0,r?.[i.id]||0),g=i.x+_[0]*m/2,f=i.y+_[1]*m/2,v=Math.cos(p)*u,y=Math.sin(p)*u,b=[g-v,f-y],w=[g+v,f+y],k=c*i.length,$=qn(Hn(b,w,l,k),d);if(!$.length)continue;const x=l[0]*_[0]+l[1]*_[1];h.push({openingId:i.id,roomId:e.roomId,polys:$,a:b,b:w,dir:l,len:k,normal:_,depth:k*x})}return h}function Bn(t){return Math.round(10*(Number(t)||0))/10}const jn=t=>"number"==typeof t&&Number.isInteger(t)&&t>=0&&t<=359?t:null;function Gn(t,e){const i=jn(e?.north_deg);return null!==i?i:jn(t?.north_deg)}function Wn(t,e){const i=t=>"static"===t||"daynight"===t?t:null;return i(e?.bg_mode)??i(t?.bg_mode)??"static"}function Vn(t,e){const i=e?.sun_rays;return"boolean"==typeof i?i:!0===t?.sun_rays}function Kn(t){const e=t?.states?.["sun.sun"]?.attributes,i=Number(e?.azimuth),s=Number(e?.elevation);return Number.isFinite(i)&&Number.isFinite(s)?{azimuth:i,elevation:s}:null}const Yn={color:"#607d8b",opacity:1,widthCm:3.6,fill:!1,fillColor:"#607d8b",fillOpacity:.25},Jn=(t,e=1)=>{const i=Number(t);return Number.isFinite(i)?Math.min(1,Math.max(0,i)):e},Zn=t=>{let e=Number(t);return Number.isFinite(e)?(e=(e%360+360)%360,e>180?e-360:e):0};function Xn(t,e,i){return Zn(e)?[t.x,t.y]:i([t.x,t.y])}const Qn=(t,e,i)=>{const s=Number(t),o=Number.isFinite(e)&&e>0?e:5;return Number.isFinite(s)&&s>0?s/o*i:0},tr=(t,e,i)=>{const s=Number(t),o=Number.isFinite(e)&&e>0?e:5;return Number.isFinite(s)&&i>0?s/i*o:0},er=(t,e,i,s=Yn.widthCm)=>{const o=Number(t?.width_cm);if(Number.isFinite(o)&&o>0)return o;const n=Number(t?.width);return Number.isFinite(n)&&n>0?tr(n,e,i):s};function ir(t,e){return{color:t.color,opacity:Jn(t.opacity),width_cm:Math.max(.1,Math.min(100,Number(t.widthCm)||.1)),...e?{fill:t.fill,fill_color:t.fillColor,fill_opacity:Jn(t.fillOpacity,.25)}:{}}}function sr(t){const e=t.x+t.w/2,i=t.y+t.h/2,s=Zn(t.angle)*Math.PI/180,o=Math.cos(s),n=Math.sin(s),r=(t,s)=>{const r=t-e,a=s-i;return[e+r*o-a*n,i+r*n+a*o]};return[r(t.x,t.y),r(t.x+t.w,t.y),r(t.x+t.w,t.y+t.h),r(t.x,t.y+t.h)]}function or(t){const e=sr(t),i=(t,e)=>[(t[0]+e[0])/2,(t[1]+e[1])/2],s=[(e[0][0]+e[2][0])/2,(e[0][1]+e[2][1])/2];return{points:[...e,i(e[0],e[1]),i(e[1],e[2]),i(e[2],e[3]),i(e[3],e[0]),s],segments:e.map((t,i)=>({a:t,b:e[(i+1)%4]}))}}function nr(t,e,i,s,o,n,r,a){const l=Zn(t.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l),d=-Math.sin(l),p=Math.cos(l),u=t.x+t.w/2,_=t.y+t.h/2,m=e>0?-t.w/2:t.w/2,g=i>0?-t.h/2:t.h/2,f=u+m*c+g*d,v=_+m*h+g*p,y=s-f,b=o-v;let w=(y*c+b*h)*(e>0?1:-1),k=(y*d+b*p)*(i>0?1:-1),$=null;if(n){const e=w/Math.max(t.w,a),i=k/Math.max(t.h,a);$=Math.max(a/Math.max(t.w,a),a/Math.max(t.h,a),e,i),w=t.w*$,k=t.h*$}if(r>0)if(n){const e=Math.max(1,Math.round(t.w/r)),i=Math.max(1,Math.round(t.h/r));if(Math.abs(t.w-e*r)<1e-6&&Math.abs(t.h-i*r)<1e-6){const s=(t,e)=>{let i=Math.abs(t),s=Math.abs(e);for(;s;)[i,s]=[s,i%s];return Math.max(1,i)},o=1/s(e,i),n=Math.max(a/t.w,a/t.h),r=Math.max(n,Math.round(($??1)/o)*o);w=t.w*r,k=t.h*r}else{t.w>=t.h?(w=Math.round(w/r)*r,k=w*(t.h/Math.max(t.w,a))):(k=Math.round(k/r)*r,w=k*(t.w/Math.max(t.h,a)))}}else w=Math.round(w/r)*r,k=Math.round(k/r)*r;w=Math.max(a,w),k=Math.max(a,k);return{x:f+(e>0?w/2:-w/2)*c+(i>0?k/2:-k/2)*d-w/2,y:v+(e>0?w/2:-w/2)*h+(i>0?k/2:-k/2)*p-k/2,w:w,h:k,angle:Zn(t.angle)||void 0}}const rr=(t,e,i)=>{const s=i[0]-e[0],o=i[1]-e[1],n=s*s+o*o;if(n<1e-12)return[...e];const r=Math.max(0,Math.min(1,((t[0]-e[0])*s+(t[1]-e[1])*o)/n));return[e[0]+s*r,e[1]+o*r]};const ar=15;function lr(t,e){return Number.isFinite(t)&&t>0&&Number.isFinite(e)&&e>0&&t*e<3}function cr(t){return Number.isFinite(t)?Math.max(1,Math.min(100,t)):1}function hr(t,e){return!Number.isFinite(t)||t<=0?"":String(e?Math.round(t/2.54*100)/100:Math.round(100*t)/100)}function dr(t,e,i){const s=Number(e)>0?Number(e):5;return cr(t)/s*i}function pr(t,e){return e>0&&Number.isFinite(t)?Math.round(t/e)*e:t}function ur(t,e){let i=e[0]-t[0],s=e[1]-t[1];const o=Math.hypot(i,s);return o<1e-12?[1,0]:(i/=o,s/=o,(i<-1e-12||Math.abs(i)<=1e-12&&s<0)&&(i=-i,s=-s),[i,s])}function _r(t,e,i){const s=pr((t[0]+e[0])/2,i),o=pr((t[1]+e[1])/2,i),[n,r]=ur(t,e);let a=Math.atan2(r,n);a<0&&(a+=Math.PI);const l=Math.round(1800*a)/1800,c=i>0&&i<.01?6:i<1?4:2;return`${s.toFixed(c)},${o.toFixed(c)}@${l.toFixed(4)}`}function mr(t,e,i,s){return 1===s?_r(t,e,i):_r([t[0]/s,t[1]/s],[e[0]/s,e[1]/s],i)}function gr(t,e){if(!Array.isArray(t.a)||!Array.isArray(t.b)||t.a.length<2||t.b.length<2)return null;const i=[Number(t.a[0]),Number(t.a[1]),Number(t.b[0]),Number(t.b[1])];if(!i.every(Number.isFinite))return null;const s=e>0?e:1;return[[i[0]*s,i[1]*s],[i[2]*s,i[3]*s]]}function fr(t,e,i,s,o){const n=o>0?o:1;return{key:mr(t,e,s,n),cm:cr(i),a:[t[0]/n,t[1]/n],b:[e[0]/n,e[1]/n]}}function vr(t,e){const i=e>0?e:1,s=[];for(const e of t){const t=e.key.lastIndexOf("@");if(t<0)continue;const[o,n]=e.key.slice(0,t).split(",").map(Number),r=Number(e.key.slice(t+1));[o,n,r].every(Number.isFinite)&&s.push({w:e,x:o*i,y:n*i,ang:r})}return s}function yr(t,e){const[i,s]=ur(t,e);let o=Math.atan2(s,i);return o<0&&(o+=Math.PI),o}function br(t,e){let i=Math.abs(t-e);return i>Math.PI/2&&(i=Math.PI-i),i<.02}function wr(t,e,i,s,o=1){if(!t?.length)return null;const n=mr(e,i,s,o),r=t.find(t=>t.key===n);if(r)return r;const a=o>0?o:1,l=(e[0]+i[0])/2,c=(e[1]+i[1])/2,h=yr(e,i),d=Math.max(.5*s,1e-9)*a;for(const e of vr(t,a))if(br(e.ang,h)&&Math.hypot(e.x-l,e.y-c)<=d)return e.w;return null}function kr(t,e,i,s,o=1){const n=wr(t,e,i,s,o);return n&&n.cm>0?cr(n.cm):0}function $r(t,e,i,s=1,o=[]){if(!t?.length)return[];const n=new Set,r=Xe(e);for(const t of r)n.add(mr([t[0],t[1]],[t[2],t[3]],i,s));const a=e||[],l=Math.max(i*s*.02,1e-9);for(let t=0;t(n.has(t.key)||(t=>{const e=gr(t,s);if(!e)return!1;const[i,n]=e,a=n[0]-i[0],c=n[1]-i[1],h=Math.hypot(a,c);if(h<=l)return!1;if(!r.some(t=>{const e=[t[0],t[1]],s=[t[2],t[3]];return br(yr(i,n),yr(e,s))&&Br(i[0],i[1],e[0],e[1],s[0],s[1])<=l&&Br(n[0],n[1],e[0],e[1],s[0],s[1])<=l}))return!1;const d=(o||[]).some(t=>{const e=[t[0],t[1]],s=[t[2],t[3]];if(!br(yr(i,n),yr(e,s)))return!1;const o=t=>Math.abs((t[0]-i[0])*c-(t[1]-i[1])*a)/h;if(o(e)>l||o(s)>l)return!1;const r=h*h,d=((e[0]-i[0])*a+(e[1]-i[1])*c)/r,p=((s[0]-i[0])*a+(s[1]-i[1])*c)/r;return Math.min(1,Math.max(d,p))-Math.max(0,Math.min(d,p))>l/h});return!d})(t))&&t.cm>=1&&t.cm<=100)}function xr(t,e,i,s=8){const[o,n]=ur(t,e);let r=Math.atan2(n,o);r<0&&(r+=Math.PI);let a=i*Math.PI/180%Math.PI;a<0&&(a+=Math.PI);let l=Math.abs(r-a);return l>Math.PI/2&&(l=Math.PI-l),l<=s*Math.PI/180}function Sr(t,e,i,s,o=1){if(!t?.length)return[];if(e.length!==i.length)return t.slice();const n=new Map;for(let t=0;t0?o:1,a=Math.max(.5*s,1e-9)*r,l=new Set,c=[];for(const o of t){let t="",h=null;const d=gr(o,r);if(d)for(let o=0;oa||Br(d[1][0],d[1][1],n[0],n[1],l[0],l[1])>a)continue;const u=l[0]-n[0],_=l[1]-n[1],m=u*u+_*_;if(m<1e-18)continue;const g=t=>{const e=Math.max(0,Math.min(1,((t[0]-n[0])*u+(t[1]-n[1])*_)/m));return[c[0]+(p[0]-c[0])*e,c[1]+(p[1]-c[1])*e]};h=[g(d[0]),g(d[1])],t=mr(h[0],h[1],s,r);break}if(d||(t=n.get(o.key)||""),!t){const n=vr([o],r)[0];if(n)for(let o=0;o1.000001)continue;if(Br(n.x,n.y,l[0],l[1],c[0],c[1])>a)continue;const g=h[0]+(d[0]-h[0])*Math.max(0,Math.min(1,m)),f=h[1]+(d[1]-h[1])*Math.max(0,Math.min(1,m)),[v,y]=ur(h,d),b=Math.max(s*r,1e-6);t=mr([g-v*b,f-y*b],[g+v*b,f+y*b],s,r);break}}t||(t=o.key),l.has(t)||(l.add(t),c.push(h?fr(h[0],h[1],o.cm,s,r):{...o,key:t,cm:cr(o.cm)}))}return c}function Mr(t,e,i,s,o,n=1){const r=mr(e,i,o,n),a=(t||[]).filter(t=>t.key!==r);return null==s||s<1?a:[...a,fr(e,i,s,o,n)]}function Cr(t,e,i,s,o,n=[],r=1){let a=t?t.slice():[];for(const t of function(t,e,i,s,o=1,n=[]){const r=Fr(t,e,i,s,o,n);if(!r)return[];const a=[];for(let t=0;t0)||!t||t.length<2)return"";if(i&&t.length>=3){let i=t;const s=t[t.length-1];if(t.length>=4&&Math.hypot(t[0][0]-s[0],t[0][1]-s[1])<1e-9&&(i=t.slice(0,-1)),i.length>=3){const t=i.map(()=>e),s=Kr(i,t),o=Ir(i,t);if(s&&o)return`${Er(s)} ${Er(Nr(o))}`}}let s="";for(let i=0;i.05)&&Br(r,a,t[0],t[1],t[2],t[3])<=n)return!0}return!1}function Pr(t,e){return Math.max(t*(e>0?e:1)*.04,1e-9)}function Rr(t,e){const i=t[e],s=t[(e+1)%t.length],o=s[0]-i[0],n=s[1]-i[1],r=Math.hypot(o,n)||1;let a=-n/r,l=o/r;const c=[(i[0]+s[0])/2,(i[1]+s[1])/2];return function(t,e){let i=!1;for(let s=0,o=e.length-1;st[1]!=l>t[1]&&t[0]<(a-n)*(t[1]-r)/(l-r+0)+n&&(i=!i)}return i}([c[0]+.001*a,c[1]+.001*l],t)||(a=-a,l=-l),[a,l]}function Ar(t,e){const i=t[0]*e[1]-t[1]*e[0],s=t[0]*e[0]+t[1]*e[1];return Math.abs(i)<1e-9&&s>0}function zr(t,e,i,s){const o=e[0]*s[1]-e[1]*s[0];if(Math.abs(o)<1e-12)return null;const n=[i[0]-t[0],i[1]-t[1]],r=(n[0]*s[1]-n[1]*s[0])/o;return[t[0]+r*e[0],t[1]+r*e[1]]}function Ir(t,e){const i=t?.length||0;if(i<3||e.length!==i)return null;if(e.every(t=>!(t>0)))return t.map(t=>[t[0],t[1]]);const s=[];for(let o=0;o0||d>0)){s.push([t[o][0],t[o][1]]);continue}if(Ar(b,w)){const e=t[o],i=[e[0]+p*h,e[1]+u*h],n=[e[0]+_*d,e[1]+m*d];s.push(i),Math.hypot(n[0]-i[0],n[1]-i[1])>1e-9&&s.push(n);continue}const x=zr(k,b,$,w),S=Math.max(h,d,1e-9);if(x){if(Math.hypot(x[0]-t[o][0],x[1]-t[o][1])<=4*S){s.push(x);continue}}h>0&&s.push([t[o][0]+p*h,t[o][1]+u*h]),d>0&&s.push([t[o][0]+_*d,t[o][1]+m*d]),h>0||d>0||s.push([t[o][0],t[o][1]])}return s.length>=3?s:null}function Er(t,e=!0){if(!t.length)return"";let i=`M ${t[0][0]} ${t[0][1]}`;for(let e=1;et?.id===e),a=Je(r);if(!a||a.length<3)return null;const l=Pr(s,o),c=[];for(const i of t||[]){if(!i||i.id===e)continue;const t=Je(i);if(t)for(const e of us(a,t,l))c.push([e[0],e[1]],[e[2],e[3]])}for(const t of i||[])c.push([t[0],t[1]],[t[2],t[3]]);for(const t of n||[]){const e=gr(t,o);e&&c.push(e[0],e[1])}const h=[],d=[];for(let t=0;tl)continue;const r=((t[0]-e[0])*(i[0]-e[0])+(t[1]-e[1])*(i[1]-e[1]))/(s*s);r<=o||r>=1-o||(n.some(t=>Math.abs(t-r)*s<=2*l)||n.push(r))}n.sort((t,e)=>t-e);for(const s of n)h.push([e[0]+(i[0]-e[0])*s,e[1]+(i[1]-e[1])*s]),d.push(t)}return{poly:h,parent:d,orig:a}}function Or(t,e,i,s){const o=e.poly.length,n=new Array(o).fill(0);if(!t?.length)return n;const r=new Set,a=[];for(let l=0;l0?(n[l]=cr(c.cm),r.add(c.key)):a.push(l)}if(!a.length)return n;const l=s>0?s:1,c=Math.max(.5*i,1e-9)*l,h=vr(t,l).filter(t=>t.w.cm>0),d=new Map;for(const t of a){const i=e.parent[t],s=d.get(i);s?s.push(t):d.set(i,[t])}for(const[t,i]of d){const s=e.orig[t],o=e.orig[(t+1)%e.orig.length],a=yr(s,o),d=(s[0]+o[0])/2,p=(s[1]+o[1])/2;let u=null;const _=Math.hypot(o[0]-s[0],o[1]-s[1]);for(const t of h){if(r.has(t.w.key))continue;if(!br(t.ang,a))continue;const e=gr(t.w,l);let i=!1,n=0;if(e){if(!br(yr(e[0],e[1]),a))continue;if(Br(s[0],s[1],e[0][0],e[0][1],e[1][0],e[1][1])>c||Br(o[0],o[1],e[0][0],e[0][1],e[1][0],e[1][1])>c)continue;i=!0,n=Math.max(0,Math.hypot(e[1][0]-e[0][0],e[1][1]-e[0][1])-_)}else{if(Br(t.x,t.y,s[0],s[1],o[0],o[1])>c)continue;n=Math.hypot(t.x-d,t.y-p)}(!u||i&&!u.exact||i===u.exact&&nt?.id===e),o=Je(s);if(!o)return[];const n=[];for(const s of t||[]){if(!s||s.id===e)continue;const t=Je(s);if(t)for(const e of us(o,t,i))n.push(e)}return n}(t,e,Pr(o,a)),h=function(t,e,i,s,o){const n=Pr(s,o),r=[];for(let a=0;aBr(h,d,t[0],t[1],t[2],t[3])<=n);r.push(p?"shared":"outer")}return r}(l.poly,c,s,o,a),d=Or(i,l,o,a),p=d.map((t,e)=>h[e]&&t>0?dr(t,n,r)/2:0);return{...l,kinds:h,cms:d,offsets:p}}function Hr(t,e,i,s,o,n,r=1){const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,o,n,r);if(c)for(let t=0;t0&&!l.has(c.key)&&(l.add(c.key),a.push(c));const c=[];for(const a of t||[]){if(!a?.id)continue;const l=Lr(t,a.id,e,i,s,o,n,r);if(l)for(let t=0;t0)||null===l.kinds[i]){t++;continue}let n=t;for(;n+10&&c.push({a:[h[0],h[1]],b:[d[0],d[1]],key:mr(h,d,s,r),cm:o,len:p}),t=n+1}}}c.sort((t,e)=>e.len-t.len||t.key.localeCompare(e.key));const h=[],d=new Set,p=new Set,u=4*Pr(s,r);for(const t of c){const e=a.filter(e=>!p.has(e.key)&&e.cm===t.cm&&br(yr(e.a,e.b),yr(t.a,t.b))&&Br(e.a[0],e.a[1],t.a[0],t.a[1],t.b[0],t.b[1])<=u&&Br(e.b[0],e.b[1],t.a[0],t.a[1],t.b[0],t.b[1])<=u);if(e.length){for(const t of e)p.add(t.key);d.has(t.key)||(d.add(t.key),h.push(fr(t.a,t.b,t.cm,s,r)))}}for(const t of a)p.has(t.key)||d.has(t.key)||(d.add(t.key),h.push(fr(t.a,t.b,t.cm,s,r)));return h}function Ur(t,e,i,s,o,n,r,a=1){const l=Pr(o,a),c=(s[0]+s[2])/2,h=(s[1]+s[3])/2,d=yr([s[0],s[1]],[s[2],s[3]]);let p=null;for(const s of Hr(t,e,i,o,n,r,a)){if(!br(yr(s.a,s.b),d))continue;const t=Br(c,h,s.a[0],s.a[1],s.b[0],s.b[1]);t>4*l||(!p||tt?.id===e),c=Je(l);if(!c||c.length<3)return null;if(!i?.length)return c.map(t=>[t[0],t[1]]);const h=Lr(t,e,i,s,o,n,r,a);return h&&h.offsets.some(t=>t>0)&&Ir(h.poly,h.offsets)||c.map(t=>[t[0],t[1]])}function Gr(t){const e=t.map(t=>[t[0],t[1]]);return e.push([t[0][0],t[0][1]]),[e]}function Wr(t,e,i,s=[],o,n,r,a=1,l=[]){if(!e?.length&&!l.length)return null;const c=[];let h=0;for(const s of t||[]){if(!s?.id)continue;const l=Lr(t,s.id,e,i,o,n,r,a);if(!l||l.poly.length<3||!l.offsets.some(t=>t>0))continue;for(const t of l.offsets)t>0&&(h=Math.max(h,2*t));const d=Kr(l.poly,l.offsets),p=Ir(l.poly,l.offsets);d&&c.push({outset:d,inset:p})}for(const t of l){const e=t.map(t=>t[0]),i=t.map(t=>t[1]);if(e.length){const s=Math.min(Math.max(...e)-Math.min(...e),Math.max(...i)-Math.min(...i)),o=Math.min(...t.map((e,i)=>{const s=t[(i+1)%t.length];return Math.hypot(s[0]-e[0],s[1]-e[1])}));h=Math.max(h,t.length>16?s:o)}}const d=function(t,e,i,s,o,n,r){if(!e?.length||!i?.length)return[];const a=4*Pr(s,r),l=new Map;for(const a of Hr(t,e,i,s,o,n,r))!a.open&&a.half>0&&!l.has(a.key)&&l.set(a.key,a);const c=[...l.values()];if(c.length<2)return[];const h=[];for(const t of i)for(const e of[[t[0],t[1]],[t[2],t[3]]])h.some(t=>Math.hypot(t[0]-e[0],t[1]-e[1])<=a)||h.push(e);const d=[],p=(t,e)=>{let i=0,s=0;if(Math.hypot(t.a[0]-e[0],t.a[1]-e[1])<=a)i=t.b[0]-t.a[0],s=t.b[1]-t.a[1];else{if(!(Math.hypot(t.b[0]-e[0],t.b[1]-e[1])<=a))return null;i=t.a[0]-t.b[0],s=t.a[1]-t.b[1]}const o=Math.hypot(i,s);return o>a?[i/o,s/o]:null};for(const t of h){const e=c.map(e=>({iv:e,u:p(e,t)})).filter(t=>!!t.u);for(let i=0;i4*_||d.push(r>0?[t.slice(),h,u,p]:[t.slice(),p,u,h])}}return d}(t,e,i,o,n,r,a),p=s.length?Jr(t,e,i,o,n,r,a):null;try{const t=t=>{const e=Gr(t.outset);return t.inset?Ge(e,Gr(t.inset)):e};let e=c.length?t(c[0]):null;for(let i=1;i0))continue;const i=Qr(p,t,!0);if(!i.negative&&!i.positive)continue;const s=t.angle*Math.PI/180,n=Math.cos(s),r=Math.sin(s),l=-r,c=n,d=t.length/2,u=1.25*Math.max(h,o*a),_=[[t.x-n*d-l*u,t.y-r*d-c*u],[t.x+n*d-l*u,t.y+r*d-c*u],[t.x+n*d+l*u,t.y+r*d+c*u],[t.x-n*d+l*u,t.y-r*d+c*u]];e&&(e=Ge(e,Gr(_)))}for(const t of l)t.length<3||(e=e?Be(e,Gr(t)):[Gr(t)]);return e?{geom:e,depthUnits:h}:null}catch{return null}}function Vr(t,e,i,s=[],o,n,r,a=1,l=[]){if(!e?.length&&!l.length)return null;const c=Wr(t,e,i,s,o,n,r,a,l),h=c?function(t){if(!t)return"";let e="";for(const i of t)if(Array.isArray(i))for(const t of i){if(!Array.isArray(t)||t.length<4)continue;const i=t.slice(0,t.length-1);i.length<3||(e+=(e?" ":"")+Er(i.map(t=>[t[0],t[1]])))}return e}(c.geom):"";if(c&&h)return{d:h,depthUnits:c.depthUnits,fillRule:"evenodd"};const d=function(t,e,i,s,o,n,r=1){if(!e?.length)return[];const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,o,n,r);if(!c||c.poly.length<3||!c.offsets.some(t=>t>0))continue;const h=Kr(c.poly,c.offsets),d=Ir(c.poly,c.offsets);if(!h||!d)continue;const p=`${Er(h)} ${Er(Nr(d))}`;let u="",_="outer",m=0,g=0;for(let t=0;t0){u=mr(c.poly[t],c.poly[(t+1)%c.poly.length],s,r),_=c.kinds[t]||"outer",m=c.cms[t],g=dr(m,o,n);break}a.push({d:p,key:u,kind:_,cm:m,depthUnits:g})}return a}(t,e,i,o,n,r,a),p=l.map(t=>Er(t)).join(" ");if(!d.length&&!p)return null;let u=c?.depthUnits||0;for(const t of d)u=Math.max(u,t.depthUnits);return{d:[d.map(t=>t.d).join(" "),p].filter(Boolean).join(" "),depthUnits:u,fillRule:"nonzero"}}function Kr(t,e){const i=t?.length||0;if(i<3||e.length!==i)return null;if(e.every(t=>!(t>0)))return t.map(t=>[t[0],t[1]]);Nr(t),e.slice().reverse();const s=[];for(let o=0;o0||a>0)){s.push([t[o][0],t[o][1]]);continue}if(Ar(b,w)){const e=t[o],i=[e[0]-l*r,e[1]-c*r],n=[e[0]-h*a,e[1]-d*a];s.push(i),Math.hypot(n[0]-i[0],n[1]-i[1])>1e-9&&s.push(n);continue}const x=zr(k,b,$,w),S=Math.max(r,a,1e-9);if(x){if(Math.hypot(x[0]-t[o][0],x[1]-t[o][1])<=4*S){s.push(x);continue}}r>0&&s.push([t[o][0]-l*r,t[o][1]-c*r]),a>0&&s.push([t[o][0]-h*a,t[o][1]-d*a])}return s.length>=3?s:null}function Yr(t,e,i,s,o,n,r=1){const a=[];for(const l of t||[]){const c=Je(l);if(c&&c.length>=3){const h=Lr(t,l.id,e,i,s,o,n,r),d=h&&h.offsets.some(t=>t>0)?Kr(h.poly,h.offsets):null,p=d||c;a.push({poly:p.map(t=>t.join(",")).join(" ")})}else l&&null!=l.x&&null!=l.y&&null!=l.w&&null!=l.h&&a.push({rect:{x:l.x,y:l.y,w:l.w,h:l.h,rx:.03*Math.min(l.w,l.h)}})}return a}function Jr(t,e,i,s,o,n,r=1){const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,o,n,r);if(!c)continue;const h=Math.abs(mi(c.poly));for(let t=0;t[Math.max(e,t.x0),Math.min(i,t.x1)]).filter(t=>t[1]-t[0]>s).sort((t,e)=>t[0]-e[0]||t[1]-e[1]);if(!o.length)return{coverage:0,full:!1};let n=o[0][0],r=o[0][1],a=0,l=n<=e+s;for(let t=1;t=i-s,{coverage:a,full:l}}function Xr(t,e){return Number(e.full)-Number(t.full)||t.faceDistance-e.faceDistance||t.area-e.area||t.roomId.localeCompare(e.roomId)}function Qr(t,e,i=!1){const s=Number(e?.x),o=Number(e?.y),n=Number(e?.angle),r=Number(e?.length);if(!([s,o,n,r].every(Number.isFinite)&&r>0))return{negative:null,positive:null};const a=n*Math.PI/180,l=Math.cos(a),c=Math.sin(a),h=-c,d=l,p=r/2,u=Math.max(1e-9,t.adjacencyEps),_=new Map;let m=0;for(const e of t.edges){if(i&&!(e.half>0))continue;if(!xr(e.a,e.b,n))continue;const[t,r]=ur(e.a,e.b);if(Math.abs((s-e.a[0])*r-(o-e.a[1])*t)>u)continue;const a=(e.a[0]-s)*l+(e.a[1]-o)*c,g=(e.b[0]-s)*l+(e.b[1]-o)*c,f=Math.max(-p,Math.min(a,g)),v=Math.min(p,Math.max(a,g));if(v-f<=u)continue;const y=e.inward[0]*h+e.inward[1]*d>=0?1:-1,b=((e.a[0]+e.b[0])/2-s)*h+((e.a[1]+e.b[1])/2-o)*d,w=Math.abs(b+y*e.half),k=`${y}|${e.roomId}`,$={x0:f,x1:v,half:e.half,cm:e.cm,key:e.key,axis:[t,r]},x=_.get(k);x?(x.pieces.push($),x.faceDistance=Math.min(x.faceDistance,w)):_.set(k,{roomId:e.roomId,side:y,order:m++,pieces:[$],faceDistance:w,area:e.area,coverage:0,full:!1})}for(const t of _.values()){const e=Zr(t.pieces,-p,p,u);t.coverage=e.coverage,t.full=e.full}const g=t=>{const e=[..._.values()].filter(e=>e.side===t&&e.coverage>u);return e.sort(Xr),e[0]||null};return{negative:g(-1),positive:g(1)}}function ta(t,e){const i=Qr(t,e),s=[i.negative,i.positive].filter(t=>!!t).sort((t,e)=>t.order-e.order);if(!s.length)return{ox:0,oy:0,cm:0,side:-1};const o=s[0],n=e.flip_v?-o.side:o.side,r=(-1===n?i.negative:i.positive)||o,a=[...r.pieces].sort((t,e)=>(t.x0<=0&&t.x1>=0?0:Math.min(Math.abs(t.x0),Math.abs(t.x1)))-(e.x0<=0&&e.x1>=0?0:Math.min(Math.abs(e.x0),Math.abs(e.x1)))||e.x1-e.x0-(t.x1-t.x0)||t.key.localeCompare(e.key))[0];if(!(a.half>0&&a.cm>0))return{ox:0,oy:0,cm:0,side:n};const l=e.angle*Math.PI/180,c=-Math.sin(l),h=Math.cos(l);return{ox:c*n*a.half,oy:h*n*a.half,cm:a.cm,side:n}}function ea(t,e){const i=1e-9,s=[];for(const t of[...e].filter(t=>t.x1>t.x0&&t.half>0).sort((t,e)=>t.x0-e.x0||t.x1-e.x1||t.half-e.half)){const e=s[s.length-1];e&&t.x0<=e.x1+i&&Math.abs(t.half-e.half)<=i?e.x1=Math.max(e.x1,t.x1):s.push({...t})}const o=[];for(let e=0;e=o-l?i.push([s,o]):(t>s+l&&i.push([s,Math.min(o,t)]),et[0]-e[0]||t[1]-e[1]),b=[];for(const t of y){const e=b[b.length-1];e&&t[0]<=e[1]+l?e[1]=Math.max(e[1],t[1]):b.push([t[0],t[1]])}s.set(g,b)}return a}function sa(t,e){const i=new Map;return e.map(e=>function(t,e,i){const s=Qr(t,e,!0),o=s.negative,n=s.positive;if(!o&&!n)return null;let r;if(o&&n)r=[{candidate:o,side:-1},{candidate:n,side:1}];else{const t=o||n;r=[{candidate:t,side:-1},{candidate:t,side:1}]}const a=r.map(({candidate:t,side:s})=>({candidate:t,side:s,pieces:ia(e,s,t.pieces,i)})),l=a.map(({candidate:t,side:e,pieces:i})=>({side:e,roomId:t.roomId,d:ea(e,i)})),c=a.flatMap(({pieces:t})=>t);if(!c.length)return null;const h=Math.max(...c.map(t=>t.half));return{faces:l,minY:-h,maxY:h,wallKey:[...new Set(c.map(t=>t.key))].sort().join("|")}}(t,e,i))}const oa=150;function na(t){return Number.isFinite(t)?Math.max(1,Math.min(oa,t)):1}function ra(t){return((Number.isFinite(Number(t))?Number(t):0)%90+90)%90}const aa=t=>{const e=t.map(t=>[t[0],t[1]]);return!e.length||e[0][0]===e[e.length-1][0]&&e[0][1]===e[e.length-1][1]||e.push([...e[0]]),[e]};function la(t){const e=[];for(const i of t||[])for(const t of i||[]){const i=(t||[]).filter(t=>Array.isArray(t)&&t.length>=2);i.length<4||e.push(`M ${i.slice(0,-1).map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`)}return e.join(" ")}function ca(t,e,i,s,o){const n=e[0]-t[0],r=e[1]-t[1],a=Math.hypot(n,r);if(!(a>1e-9))return null;const l=dr(i,s,o)/2,c=-r/a*l,h=n/a*l;return[[t[0]+c,t[1]+h],[e[0]+c,e[1]+h],[e[0]-c,e[1]-h],[t[0]-c,t[1]-h]]}function ha(t,e,i){const s=Number(e)>0?Number(e):5,o=na(t.cm)/s*i,n=t.center[0],r=t.center[1];if("circle"===t.shape){const t=o/2;return Array.from({length:96},(e,i)=>{const s=i/96*Math.PI*2;return[n+Math.cos(s)*t,r+Math.sin(s)*t]})}const a=o/2,l=ra(t.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l);return[[-a,-a],[a,-a],[a,a],[-a,a]].map(([t,e])=>[n+t*c-e*h,r+t*h+e*c])}function da(t,e,i){const s=[];for(let o=0;o+1t.length>=3).map(t=>aa(t));return e.length?Be(e[0],...e.slice(1)):null}catch{return null}}const ua=t=>`M ${t.map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`;function _a(t){const e=[];for(const i of t||[]){const t=[];for(const e of i||[]){const i=(e||[]).filter(t=>Array.isArray(t)&&t.length>=2);i.length<4||t.push(ua(i.slice(0,-1)))}t.length&&e.push(t.join(" "))}return e}function ma(t,e){const i=pa(t.filter(t=>t.length>=3)),s=pa(e.filter(t=>t.length>=3));if(!i)return[];if(!s)return _a(i);try{return _a(je(i,s))}catch{return _a(i)}}function ga(t,e){if(!e.length)return[aa(t)];try{const i=pa(e);if(i)return Ge(aa(t),i)}catch{}let i=[aa(t)];for(const t of e)if(!(t.length<3))try{i=Ge(i,aa(t))}catch{}return i}function fa(t){let e=0;for(const i of t||[])if(i?.length){e+=mi(i[0]||[]);for(let t=1;t=4&&e.push(t.slice(0,-1).map(t=>[t[0],t[1]]))}return e}function ya(t,e){let i=!1;for(let s=0,o=e.length-1;st[1]!=l>t[1]&&t[0]<(a-n)*(t[1]-r)/(l-r||1e-12)+n&&(i=!i)}return i}function ba(t,e,i){if(Math.hypot(t.center[0]-e.center[0],t.center[1]-e.center[1])>i)return!1;if(Math.abs(na(t.cm)-na(e.cm))>1e-6)return!1;if(t.shape!==e.shape)return!0;if("circle"===t.shape||"circle"===e.shape)return!0;const s=Math.abs(ra(t.angle)-ra(e.angle));return Math.min(s,90-s)<=1e-6}const wa=1e3;function ka(t,e){const i=Number(t),s=Number.isFinite(i)&&i>0?i:1,o=s>=1?e:e*s,n=s>=1?e/s:e;return{x:(e-o)/2,y:(e-n)/2,w:o,h:n}}const $a=.01,xa=100;function Sa(t,e=1e3){const i=ka(t?.plan_aspect,e),s=Number(t?.plan_scale),o=Number.isFinite(s)&&s>0?Math.min(xa,Math.max($a,s)):1,n=Number(t?.plan_scale_x),r=Number(t?.plan_scale_y),a=Number.isFinite(n)&&n>0?Math.min(xa,Math.max($a,n)):o,l=Number.isFinite(r)&&r>0?Math.min(xa,Math.max($a,r)):o,c=Number(t?.plan_x),h=Number(t?.plan_y),d=Zn(t?.plan_angle);return{x:i.x+(Number.isFinite(c)?Na(c):0)*e,y:i.y+(Number.isFinite(h)?Na(h):0)*e,w:i.w*a,h:i.h*l,...d?{angle:d}:{}}}function Ma(t){if(null==t.x||null==t.y)return{x:t.x,y:t.y,w:t.w,h:t.h};const e=Number(t.w)||0,i=Number(t.h)||0;return{x:e<0?t.x+e:t.x,y:i<0?t.y+i:t.y,w:Math.abs(e),h:Math.abs(i)}}function Ca(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=wa,i=function(t){return Array.isArray(t)&&4===t.length&&t.every(t=>Number.isFinite(t))&&t[2]>1e-6&&t[3]>1e-6?t:[0,0,1,1]}(t.view_box);return{id:t.id,title:t.title,vb:[i[0]*wa,i[1]*e,i[2]*wa,i[3]*e],bg:t.plan_url?{href:ys(t.plan_url),...Sa(t,wa)}:null,rooms:(t.rooms||[]).map(t=>{const i={...t,...Ma(t)};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*wa:void 0,y:null!=i.y?i.y*e:void 0,w:null!=i.w?i.w*wa:void 0,h:null!=i.h?i.h*e:void 0,poly:i.poly?i.poly.map(t=>[t[0]*wa,t[1]*e]):void 0}}),room_drafts:(t.room_drafts||[]).map(t=>({id:t.id,points:(t.points||[]).map(t=>[t[0]*wa,t[1]*e]),segments:(t.segments||[]).map(t=>({cm:Number(t.cm)}))})),partitions:(t.partitions||[]).map(t=>({id:t.id,a:[t.a[0]*wa,t.a[1]*e],b:[t.b[0]*wa,t.b[1]*e],cm:Number(t.cm)})),wall_columns:(t.wall_columns||[]).map(t=>({id:t.id,shape:"circle"===t.shape?"circle":"square",center:[t.center[0]*wa,t.center[1]*e],cm:Number(t.cm),..."circle"===t.shape?{}:{angle:ra(t.angle)}}))}}):[]}const Da=5e3,Ta=Da*wa,Pa=240,Ra=wa/Pa,Aa=1/Pa;function za(t){if(!Number.isFinite(t))return t;const e=Math.round(t*Pa/wa)*wa/Pa;return Math.abs(e-t)<=1e-9*Ra?t:e}function Ia(t){return{x:za(t.x),y:za(t.y)}}function Ea(t){return Number.isFinite(t)?Math.min(Ta,Math.max(-Ta,t)):0}function Na(t){return Number.isFinite(t)?Math.min(Da,Math.max(-Da,t)):0}const Fa=1/3,Oa=200;function La(t){let e=1/0,i=1/0,s=-1/0,o=-1/0;for(const n of t){const t=Number(n[0]),r=Number(n[1]);Number.isFinite(t)&&Number.isFinite(r)&&(ts&&(s=t),r>o&&(o=r))}return e>s?null:{minX:e,minY:i,maxX:s,maxY:o}}function Ha(t){return t.poly&&t.poly.length?La(t.poly):null==t.x||null==t.y?null:La([[t.x,t.y],[t.x+(t.w||0),t.y+(t.h||0)]])}function qa(t,e){const i=[];for(const e of t.rooms||[]){const t=Ha(e);t&&i.push(t)}if(t.bg){const e=La(sr(t.bg));e&&i.push(e)}for(const t of e||[])if(Array.isArray(t)){const e=La([t]);e&&i.push(e)}else i.push(t);return i}const Ua=t=>{if(!t.length)return 0;const e=[...t].sort((t,e)=>t-e),i=e.length>>1;return e.length%2?e[i]:(e[i-1]+e[i])/2},Ba=t=>{let e=1/0,i=1/0,s=-1/0,o=-1/0;for(const n of t)n.minXs&&(s=n.maxX),n.maxY>o&&(o=n.maxY);return e>s||i>o?null:{x:e,y:i,w:s-e,h:o-i}};function ja(t,e){let{x:i,y:s,w:o,h:n}=t;o<30&&(i=i+o/2-100,o=Oa),n<30&&(s=s+n/2-100,n=Oa);const r=Math.max(o,n)*e;return{x:i-r,y:s-r,w:o+2*r,h:n+2*r}}function Ga(t,e={}){const i=e.pad??.05,s=e.k??10,o=e.minSpread??50,n=t.filter(t=>Number.isFinite(t.minX)&&Number.isFinite(t.minY)&&Number.isFinite(t.maxX)&&Number.isFinite(t.maxY)&&Math.abs(t.minX)<=Ta&&Math.abs(t.maxX)<=Ta&&Math.abs(t.minY)<=Ta&&Math.abs(t.maxY)<=Ta);if(!n.length)return{core:null,all:null,outliers:0};const r=Ba(n);if(n.length<4){const t=ja(r,i);return{core:t,all:t,outliers:0}}const a=n.map(t=>(t.minX+t.maxX)/2),l=n.map(t=>(t.minY+t.maxY)/2),c=Ua(a),h=Ua(l),d=n.map((t,e)=>Math.max(Math.abs(a[e]-c),Math.abs(l[e]-h))),p=Math.max(((t,e)=>t.length?t[Math.min(t.length-1,Math.max(0,Math.round(e*(t.length-1))))]:0)([...d].sort((t,e)=>t-e),.75),o),u=d.map(t=>t>s*p),_=u.filter(Boolean).length,m=_&&_<=n.length*Fa?n.filter((t,e)=>!u[e]):n;return{core:ja(Ba(m)||r,i),all:ja(r,i),outliers:m===n?0:_}}function Wa(t,e,i=.05){const s=Ga(qa(t,e),{pad:i});if(s.core)return s.core;const o=t.vb&&4===t.vb.length&&t.vb[2]>0&&t.vb[3]>0?t.vb:[0,0,wa,wa];return{x:o[0],y:o[1],w:o[2],h:o[3]}}function Va(t){const e=Wa(t);return{x:e.x+e.w/2,y:e.y+e.h/2}}function Ka(t){const e=[];for(const i of t.rooms||[]){const t=Ha(i);t&&e.push(t)}const i=Ga(e,{pad:0}).core;return i?Math.max(wa,Math.min(Ta,Math.max(i.w,i.h))):wa}function Ya(t,e,i,s=1){const o=Number(i),n=Number.isFinite(s)&&s>0?s:1;return!Number.isFinite(o)||o<=0?t*n:t*Ka(e)*n/o}const Ja=[1,2,5,10,20,50,100,200,500,1e3];function Za(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Xa(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}const Qa=["furniture","appliance","sanitary","other"],tl=[{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]]}],el=new Map(tl.map(t=>[t.id,t]));function il(t){return t&&el.get(t)||null}function sl(t){return tl.filter(e=>e.group===t)}function ol(t,e,i=Ra,s=1e3){const o=Number(e)>0?Number(e):5;return(Number(t)||0)/o*i/s}const nl=5e-4,rl=Da;function al(t){return Number.isFinite(t)?Math.max(nl,Math.min(rl,t)):nl}const ll=t=>{const e=Math.round(1e3*t)/1e3;return Object.is(e,-0)?"0":String(e)};function cl(t,e,i){const s=il(t);if(!(s&&e>0&&i>0))return"";const o=t=>ll(t*e),n=t=>ll(t*i),r=[];for(const t of s.g)if("r"===t[0]){const[,e,i,s,a]=t;r.push(`M${o(e)} ${n(i)}H${o(e+s)}V${n(i+a)}H${o(e)}Z`)}else if("l"===t[0]){const[,e,i,s,a]=t;r.push(`M${o(e)} ${n(i)}L${o(s)} ${n(a)}`)}else if("e"===t[0]){const[,s,a,l,c]=t;r.push(`M${o(s-l)} ${n(a)}A${ll(l*e)} ${ll(c*i)} 0 0 1 ${o(s+l)} ${n(a)}A${ll(l*e)} ${ll(c*i)} 0 0 1 ${o(s-l)} ${n(a)}Z`)}else{const e=t.slice(1);if(e.length<4)continue;let i=`M${o(e[0])} ${n(e[1])}`;for(let t=2;t+1{let e=(t%360+360)%360;return e>180&&(e-=360),e};function dl(t,e,i,s,o,n=0){let r=null,a=o;for(const o of s){const[s,l,c,h]=o,d=c-s,p=h-l,u=d*d+p*p;if(!u)continue;const _=Math.sqrt(u);let m=((t-s)*d+(e-l)*p)/u;m=Math.max(0,Math.min(1,m));let g=s+m*d,f=l+m*p;const v=Math.hypot(t-g,e-f);if(!(v0){const t=Math.round(m*_/n)*n;g=s+t/_*d,f=l+t/_*p}r={cx:g+y*(i/2),cy:f+b*(i/2),angle:hl(180*Math.atan2(-y,b)/Math.PI),dist:v}}return r}function pl(t,e,i){const s=i;return{a:[t[0]/s,t[1]/s],b:[e[0]/s,e[1]/s]}}function ul(t,e){const i=e;return[t.a[0]*i,t.a[1]*i,t.b[0]*i,t.b[1]*i]}function _l(t){return Array.isArray(t)&&t.length>=2&&Number.isFinite(Number(t[0]))&&Number.isFinite(Number(t[1]))}function ml(t){if(!Array.isArray(t))return[];const e=[];for(const i of t){if(!i||"object"!=typeof i)continue;const t=i;if(!_l(t.a)||!_l(t.b))continue;const s=[Number(t.a[0]),Number(t.a[1])],o=[Number(t.b[0]),Number(t.b[1])];Math.hypot(o[0]-s[0],o[1]-s[1])<.001||e.push({a:s,b:o})}return e}function gl(t,e){const i=e[0],s=e[1],o=e[2]-i,n=e[3]-s,r=o*o+n*n;if(r<1e-18){return{q:[i,s],t:0,d:Math.hypot(t[0]-i,t[1]-s)}}let a=((t[0]-i)*o+(t[1]-s)*n)/r;a=Math.max(0,Math.min(1,a));const l=[i+a*o,s+a*n];return{q:l,t:a,d:Math.hypot(t[0]-l[0],t[1]-l[1])}}function fl(t,e){return gl(t,e).q}function vl(t,e,i,s,o){const n=gl(t,e).q;let r=n,a=1/0;for(const t of i){const e=Math.hypot(n[0]-t[0],n[1]-t[1]);e<=o&&e0?s:1,m=Math.round(u/_)*_,g=Math.max(0,Math.min(p,m))/p;return[l+h*g,c+d*g]}function yl(t,e){const i=[],s=(t||[]).filter(t=>t?.id);for(let t=0;tt?.id),r=ml(e);if(r.length)return Dl(r,t,i,s).map(t=>ul(t,i));if(!o)return[];const a=[],l=(t,e)=>(t.open_to||[]).includes(e.id)||(e.open_to||[]).includes(t.id);for(let t=0;tpl([t[0],t[1]],[t[2],t[3]],e))}function kl(t,e,i,s){for(const e of t||[])e.open_to&&delete e.open_to;if(!i.length)return;const o=(e||[]).filter(t=>t?.id);new Map(o.map(t=>[t.id,t]));const n=new Map((t||[]).filter(t=>t?.id).map(t=>[t.id,t])),r=(t,e)=>{const i=n.get(t),s=n.get(e);i&&s&&((i.open_to||[]).includes(e)||(i.open_to=[...i.open_to||[],e]),(s.open_to||[]).includes(t)||(s.open_to=[...s.open_to||[],t]))};for(let t=0;tSs(i,t)<4*s)){r(o[t].id,o[e].id);break}}}for(const e of t||[])e.open_to&&!e.open_to.length&&delete e.open_to}function $l(t,e,i,s=0){const o=e[2]-e[0],n=e[3]-e[1],r=Math.hypot(o,n);if(!(r>1e-9))return null;const a=o/r,l=n/r,c=(t[0]-e[0])*a+(t[1]-e[1])*l;if(c<-s||c>r+s)return null;return Math.abs((t[0]-e[0])*l-(t[1]-e[1])*a)>i?null:Ss(t,e)}function xl(t,e,i){const s=t[2]-t[0],o=t[3]-t[1],n=e[2]-e[0],r=e[3]-e[1],a=Math.hypot(s,o),l=Math.hypot(n,r);return a>1e-9&&l>1e-9&&(!(Math.abs(s/a*(r/l)-o/a*(n/l))>1e-6)&&(Ss([e[0],e[1]],t)<=4*i||Ss([t[0],t[1]],e)<=4*i))}function Sl(t,e,i){if(!t.length)return null;t.sort((t,e)=>t.distance-e.distance);const s=t[0];for(let o=1;oe)break;if(n.semantic!==s.semantic||!xl(s.seg,n.seg,i))return"ambiguous"}return s}function Ml(t,e,i,s,o=1,n=15){const r=function(t,e,i,s,o=1,n=15){const[r,a]=ur([e[0],e[1]],[e[2],e[3]]);let l=0,c=1/0;const h=[(e[0]+e[2])/2,(e[1]+e[3])/2];for(const e of i){const[i,n]=ur([e[0],e[1]],[e[2],e[3]]);if(Math.abs(r*n-a*i)>.05)continue;const d=kr(t,[e[0],e[1]],[e[2],e[3]],s,o);if(!(d>0))continue;const p=[(e[0]+e[2])/2,(e[1]+e[3])/2],u=Math.hypot(p[0]-h[0],p[1]-h[1]);u0?l:n}(t,e,i,s,o,n);return Mr(t,[e[0],e[1]],[e[2],e[3]],r,s,o)}function Cl(t,e,i){const s=[[t[0],t[1]],[t[2],t[3]]],o=[(t[0]+t[2])/2,(t[1]+t[3])/2];for(const n of e)if(!(Ss(o,n)>Math.hypot(t[2]-t[0],t[3]-t[1])&&Ss([n[0],n[1]],t)>i))for(const e of[[n[0],n[1]],[n[2],n[3]]])Ss(e,t)<=2*i&&s.push(e);return s}function Dl(t,e,i,s){const o=ml(t);if(!o.length)return[];const n=yl(e,s);if(!n.length)return[];const r=[],a=Math.max(4*s,1e-6);for(const t of o){const e=ul(t,i),o=e[0],l=e[1],c=e[2]-o,h=e[3]-l,d=Math.hypot(c,h);if(d4*s||n>4*s)continue;const r=(t[0]-o)*p+(t[1]-l)*u,c=(t[2]-o)*p+(t[3]-l)*u,h=Math.max(0,Math.min(r,c)),m=Math.min(d,Math.max(r,c));if(m-ht.lo-e.lo||t.hi-e.hi);const i=[];for(const t of e){const e=i[i.length-1];e&&t.lo<=e.hi+a?e.hi=Math.max(e.hi,t.hi):i.push({...t})}for(const e of i){const i=[o+p*e.lo,l+u*e.lo],s=[o+p*e.hi,l+u*e.hi];Math.hypot(s[0]-i[0],s[1]-i[1])i.pair===t&&Math.abs(i.ux*r-i.uy*n)<=1e-6&&Math.abs((e[0]-i.origin[0])*i.uy-(e[1]-i.origin[1])*i.ux)<=c&&Math.abs((e[2]-i.origin[0])*i.uy-(e[3]-i.origin[1])*i.ux)<=c);h||(h={pair:t,origin:[e[0],e[1]],ux:n,uy:r,ranges:[]},l.push(h));const d=(e[0]-h.origin[0])*h.ux+(e[1]-h.origin[1])*h.uy,p=(e[2]-h.origin[0])*h.ux+(e[3]-h.origin[1])*h.uy;h.ranges.push({lo:Math.min(d,p),hi:Math.max(d,p)})}const h=[];for(const t of l){t.ranges.sort((t,e)=>t.lo-e.lo||t.hi-e.hi);const e=[];for(const i of t.ranges){const t=e[e.length-1];t&&i.lo<=t.hi+a?t.hi=Math.max(t.hi,i.hi):e.push({...i})}for(const s of e){const e=[t.origin[0]+t.ux*s.lo,t.origin[1]+t.uy*s.lo],o=[t.origin[0]+t.ux*s.hi,t.origin[1]+t.uy*s.hi];Math.hypot(o[0]-e[0],o[1]-e[1])>=a&&h.push(pl(e,o,i))}}return h}function Tl(t,e,i,s){const o=ml(t);if(!o.length)return[];if(e.length!==i.length)return o;const n=[];for(const t of o){const o=ul(t,s),r=[o[0],o[1]],a=[o[2],o[3]];let l=r,c=a;for(let t=0;t{const e=gl(t,[s[0],s[1],o[0],o[1]]);if(e.d>.001)return t;const i=h[0]-n[0],r=h[1]-n[1];return[n[0]+i*e.t,n[1]+r*e.t]};if(gl([(r[0]+a[0])/2,(r[1]+a[1])/2],[s[0],s[1],o[0],o[1]]).d<.01){l=d(r),c=d(a);break}}n.push(pl(l,c,s))}return n}class Pl{constructor(t,e=()=>Date.now()){this.onUpdate=t,this.now=e,this.signed={},this.queued=new Set,this.inFlight=new Map,this.retry=new Map,this.disposed=!1}start(t,e){this.disposed=!1,this.stopTimer(),this.resignTimer=setInterval(()=>this.resign(t(),e()),288e5)}dispose(){this.disposed=!0,this.stopTimer(),clearTimeout(this.batchTimer),this.queued.clear(),this.inFlight.clear()}stopTimer(){void 0!==this.resignTimer&&clearInterval(this.resignTimer),this.resignTimer=void 0}display(t,e){const i=ys(e);if(!i.startsWith("/api/houseplan/content/"))return i;const s=this.signed[i],o=s?this.now()-s.at:1/0;return o{const e=[...this.queued];this.queued.clear(),this.sign(t,e)},30))}sign(t,e){if(e.length&&t?.callWS)for(const i of function(t,e){const i=Math.max(1,Math.floor(e)),s=[];for(let e=0;e{if(this.disposed)return;const e=this.now(),s={...this.signed};let o=0;for(const n of i){const i=t?.urls?.[n];"string"==typeof i&&i?(s[n]={url:i,at:e},this.retry.delete(n),o++):this.backOff(n)}o&&(this.signed=s,this.onUpdate())}).catch(()=>{for(const t of i)this.backOff(t)}).finally(()=>{for(const t of i)this.inFlight.get(t)===e&&this.inFlight.delete(t)})}}backOff(t){const e=this.retry.get(t)?.delay||0,i=Math.min(6e4,e?2*e:2e3);this.retry.set(t,{notBefore:this.now()+i,delay:i})}resign(t,e){const i=this.now(),s={};for(const[t,o]of Object.entries(this.signed))e.has(t)&&i-o.at{if(null==t||""===t)return null;const e=Number(t);return Number.isFinite(e)?e:null},Il=t=>{const e=t,i=zl(Array.isArray(e)?e[0]:e?.x),s=zl(Array.isArray(e)?e[1]:e?.y);return null==i||null==s?null:[i,s]};function El(t){if(!Array.isArray(t))return null;const e=[];for(const i of t){const t=Il(i);if(!t)return null;e.push(t)}if(e.length>1){const t=e[0],i=e[e.length-1];t[0]===i[0]&&t[1]===i[1]&&e.pop()}if(e.length<3)return null;let i=0,s=0,o=0;for(let t=0;tt+e[0],0)/e.length,e.reduce((t,e)=>t+e[1],0)/e.length]:[s/(3*i),o/(3*i)]}function Nl(t){if(!Array.isArray(t))return null;const e=[];for(const i of t){const t=Il(i);if(!t)return null;e.push(t)}if(e.length>1){const t=e[0],i=e[e.length-1];t[0]===i[0]&&t[1]===i[1]&&e.pop()}const i=El(e);if(!i)return null;const s=e.map(t=>t[0]),o=e.map(t=>t[1]);return{centroid:i,bbox:[Math.min(...s),Math.min(...o),Math.max(...s),Math.max(...o)]}}function Fl(t){let e=t;if(e&&"object"==typeof e&&!Array.isArray(e)){const t=e;e=t.path??t.points??e}if(!Array.isArray(e)||!e.length)return[];let i=(e.some(t=>(t=>Array.isArray(t)?t.length>=2&&!Array.isArray(t[0])&&("object"!=typeof t[0]||null==t[0]):!!t&&"object"==typeof t&&("x"in t||"y"in t))(t))?[e]:e.filter(Array.isArray)).map(t=>t.map(Il).filter(t=>null!=t)).filter(t=>t.length>=2);i.length>64&&(i=i.slice(-64));if(i.reduce((t,e)=>t+e.length,0)<=4e3)return i.map(t=>t.slice());const s=4e3-2*i.length,o=i.map(t=>t.length-2),n=o.reduce((t,e)=>t+e,0),r=o.map(t=>s*t/n),a=r.map(Math.floor);let l=s-a.reduce((t,e)=>t+e,0);const c=r.map((t,e)=>({index:e,fraction:t-Math.floor(t)})).sort((t,e)=>e.fraction-t.fraction||t.index-e.index);for(let t=0;tfunction(t,e){if(e>=t.length)return t.slice();if(e<=2)return[t[0],t[t.length-1]];const i=[];for(let s=0;st.slice()),i=e[e.length-1];return i?(i.length>2?i.pop():e.pop(),e):[]}function Hl(t){if(!t)return null;const e=t.vacuum_position||t.robot_position||null,i=e&&null!=zl(e.x)&&null!=zl(e.y)?{x:zl(e.x),y:zl(e.y),a:zl(e.a??e.angle??e.theta)}:null,s=Fl(t.path?.path??t.path?.points??t.path),o=[],n=t.rooms,r=Array.isArray(n)?n.map((t,e)=>[String(t?.id??e),t]):n&&"object"==typeof n?Object.entries(n):[];for(const[t,e]of r){if(!e||"object"!=typeof e)continue;const i=String(e.name??e.label??"").trim(),s=[[zl(e.cx),zl(e.cy)],[zl(e.center?.x),zl(e.center?.y)],[zl(e.x),zl(e.y)]].find(([t,e])=>null!=t&&null!=e);let n=s?.[0]??null,r=s?.[1]??null;const a=Nl(e.outline),l=zl(e.x0),c=zl(e.y0),h=zl(e.x1),d=zl(e.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)||a?.bbox||null;if(null!=n&&null!=r||!a||([n,r]=a.centroid),null!=n&&null!=r||!p||(n=(p[0]+p[2])/2,r=(p[1]+p[3])/2),i&&null!=n&&null!=r){const e={id:t,name:i,cx:n,cy:r};p&&([e.x0,e.y0,e.x1,e.y1]=p),o.push(e)}}const a=function(t){return String(t.map_name??t.current_map??t.map_index??t.selected_map??"default")}(t);return i||o.length||s.length?{pos:i,path:s,rooms:o,mapId:a}:null}function ql(t,e,i){if(!t||!e)return null;const s=e.attributes||{},o=t.split(".")[0]||"",n=null!=i?.platform?String(i.platform):null,r=!!Il(s.vacuum_position??s.robot_position),a=!(!s.rooms||"object"!=typeof s.rooms),l=Fl(s.path?.path??s.path?.points??s.path).length>0,c=[s.map_name,s.current_map,s.map_index,s.selected_map].some(t=>null!=t),h="xiaomi_cloud_map_extractor"===n,d=a||l||c;let p=null;if(r?p="compatible":h?p="known_xcme_incomplete":d?p="partial":"camera"===o&&(p="camera"),!p)return null;const u=r?"camera"===o?300:200:h?100:d?50:0;return{entityId:t,name:String(s.friendly_name||t),platform:n,category:p,hasPosition:r,hasRooms:a,hasPath:l,hasMapId:c,score:u}}const Ul=t=>t.toLowerCase().replace(/[\s_\-.,]+/g,"");function Bl(t,e){const i=new Map(e.map(t=>[Ul(t.name),t])),s=[],o=[];for(const e of t){const t=i.get(Ul(e.name));t&&(s.push([[e.cx,e.cy],[t.cx,t.cy]]),o.push(e.name))}if(s.length<3)return null;const n=function(t){if(t.length<3)return null;let e=0,i=0,s=0,o=0,n=0,r=0,a=0,l=0,c=0,h=0,d=0,p=0;for(const[[u,_],[m,g]]of t){if(![u,_,m,g].every(Number.isFinite))return null;e+=u*u,i+=u*_,s+=u,o+=_*_,n+=_,r+=1,a+=u*m,l+=_*m,c+=m,h+=u*g,d+=_*g,p+=g}const u=[e,i,s,i,o,n,s,n,r],_=t=>{const[e,i,s,o,n,r,a,l,c]=u,h=e*(n*c-r*l)-i*(o*c-r*a)+s*(o*l-n*a);if(!Number.isFinite(h)||Math.abs(h)<1e-9)return null;const d=[(n*c-r*l)/h,(s*l-i*c)/h,(i*r-s*n)/h,(r*a-o*c)/h,(e*c-s*a)/h,(s*o-e*r)/h,(o*l-n*a)/h,(i*a-e*l)/h,(e*n-i*o)/h];return[d[0]*t[0]+d[1]*t[1]+d[2]*t[2],d[3]*t[0]+d[4]*t[1]+d[5]*t[2],d[6]*t[0]+d[7]*t[1]+d[8]*t[2]]},m=_([a,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 n?{matrix:n,matched:o,residual:Al(n,s)}:null}function jl(t,e,i){const s=t[t.length-1];if(s&&s[0]===e[0]&&s[1]===e[1])return t;if(t.push(e),t.length<=600)return t;let o=function(t,e){if(t.length<3)return t.slice();const i=new Uint8Array(t.length);i[0]=i[t.length-1]=1;const s=[[0,t.length-1]];for(;s.length;){const[o,n]=s.pop(),[r,a]=t[o],[l,c]=t[n],h=l-r,d=c-a,p=Math.hypot(h,d)||1e-9;let u=0,_=-1;for(let e=o+1;eu&&(u=i,_=e)}_>0&&u>e&&(i[_]=1,s.push([o,_],[_,n]))}const o=[];for(let e=0;e600&&(o=o.filter((t,e)=>e%2==0||e===o.length-1)),o}function Gl(t){return"cleaning"===t||"returning"===t||"on"===t}const Wl={0:[1,0],90:[0,1],180:[-1,0],270:[0,-1]};function Vl(t){const[e,i]=Wl[t.rot]||[1,0],s=t.mir?-1:1;return[t.s*e*s,-t.s*i,t.ox,t.s*i*s,t.s*e,t.oy]}function Kl(t){const e=t[0]*t[4]-t[1]*t[3];if(!Number.isFinite(e)||Math.abs(e)<1e-12)return null;const i=e<0,s=Math.sqrt(Math.abs(e));let o=180*Math.atan2(-t[1],t[4])/Math.PI;return o=(90*Math.round(o/90)%360+360)%360,{ox:t[2],oy:t[5],s:s,rot:o,mir:i}}function Yl(t,e,i,s){const[o,n]=Rl(Vl(e),i,s),r=Vl({...t,ox:0,oy:0}),[a,l]=Rl(r,i,s);return{...t,ox:o-a,oy:n-l}}function Jl(t){const e=t?.trail_mode;return"never"===e||"cleaning"===e||"always"===e?e:!1===t?.trail?"never":"cleaning"}const Zl=1e-5;function Xl(t){const e=t.map(()=>[]);for(let i=0;i=1-1e-9||p<=1e-9||p>=1-1e-9||(e[i].push(d),e[r].push(p))}}const i=[];for(let s=0;st-e),1];for(let t=1;t{const i=e[2]-e[0],s=e[3]-e[1],o=i*i+s*s;if(!(o>0))return Math.hypot(t[0]-e[0],t[1]-e[1]);const n=Math.max(0,Math.min(1,((t[0]-e[0])*i+(t[1]-e[1])*s)/o));return Math.hypot(t[0]-(e[0]+n*i),t[1]-(e[1]+n*s))},ec=(t,e,i,s)=>{const o=s[2]-s[0],n=s[3]-s[1],r=e*n-i*o;if(Math.abs(r)<1e-12)return 1/0;const a=s[0]-t[0],l=s[1]-t[1],c=(a*n-l*o)/r;if(!(c>1e-9))return 1/0;const h=(a*i-l*e)/r;return h<-1e-9||h>1+1e-9?1/0:c};function ic(t,e,i,s=96){if(!(e>0&&Number.isFinite(t[0])&&Number.isFinite(t[1])))return[];const o=[];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=tc(t,s);i>e||i<1e-7||o.push(s)}const n=[],r=Math.max(12,Math.round(s));for(let t=0;tt-e);const a=[];let l=Number.NEGATIVE_INFINITY;for(const i of n){if(i-l<1e-9)continue;l=i;const s=Math.cos(i),n=Math.sin(i);let r=e;for(const e of o){const i=ec(t,s,n,e);i=3?a:[]}class sc extends ct{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return hn(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[],e=this._lang;return[{name:"title",selector:{text:{}}},t.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:t}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:[{value:"",label:dn(e,"editor.lang_auto")},{value:"en",label:dn(e,"editor.lang_en")},{value:"ru",label:dn(e,"editor.lang_ru")}]}}},{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 W;this._loadSpaces();const t=this._lang,e={title:dn(t,"editor.title"),default_floor:dn(t,"editor.default_floor"),language:dn(t,"editor.language"),icon_size:dn(t,"editor.icon_size"),show_temperature:dn(t,"editor.show_temperature"),live_states:dn(t,"editor.live_states"),show_signal:dn(t,"editor.show_signal"),kiosk:dn(t,"editor.kiosk"),cycle:dn(t,"editor.cycle")};return B`[t[0]-e[0],t[1]-e[1]],_o=(t,e)=>[t[0]+e[0],t[1]+e[1]],mo=(t,e)=>t[0]*e[0]+t[1]*e[1],go=t=>Math.hypot(t[0],t[1]);function fo(t){let e=0;for(let i=0;it[1]!=l>t[1]&&t[0]<(a-o)*(t[1]-r)/(l-r)+o&&(i=!i)}return i}([a[0]+r[0]*l,a[1]+r[1]*l],t)&&(r=[-r[0],-r[1]]),r}function bo(t,e,i,s){const n=s||yo(t,e),o=(e+1)%t.length;return t.map((t,s)=>s===e||s===o?[t[0]+n[0]*i,t[1]+n[1]*i]:[...t])}function wo(t,e,i,s){const n=[],o=uo(i,e),r=go(o);if(rs||h>s)continue;const d=mo(uo(o,e),a),p=mo(uo(l,e),a),u=Math.max(0,Math.min(d,p)),_=Math.min(r,Math.max(d,p));_-u>s&&n.push([[e[0]+a[0]*u,e[1]+a[1]*u],[e[0]+a[0]*_,e[1]+a[1]*_]])}return n}function ko(t,e,i,s,n){const o=wo(t,e,i,n);if(!o.length)return null;const r=t=>o.some(([e,i])=>vo(t,e,i)<=n),a=t.length,l=[];for(let o=0;on||f>n)continue;const v=mo(uo(e,c),u),y=mo(uo(i,c),u),b=Math.max(0,Math.min(v,y)),w=Math.min(p,Math.max(v,y));if(!(w-b<=n)){if(b>n&&bn&&wgo(uo(i,t[(s+1)%t.length]))>e);for(let t=0;t<2;t++)i=i.filter((t,s)=>{const n=i[(s-1+i.length)%i.length],o=i[(s+1)%i.length],r=(t[0]-n[0])*(o[1]-n[1])-(t[1]-n[1])*(o[0]-n[0]),a=go(uo(o,n))||1;return Math.abs(r)/a>e});return i.length>=3?i:t}function xo(t){const e=t.length;if(e<3)return!1;for(let i=0;i(t[0]-n[0])*a[1]-(t[1]-n[1])*a[0],c=t=>(t[0]-n[0])*a[0]+(t[1]-n[1])*a[1],h=i,d=r-i;for(const e of t){const t=Math.abs(l(e));if(t<=i)continue;const n=c(e);n<=h||n>=d||tMath.abs(r+(t-p)/g*(a-r));s=Math.min(s,f(_),f(m))}}return s}function Mo(t,e,i){if(ui(t,e,i))return!0;if(pi(t,e,i)||pi(e,t,i))return!1;let s=0;try{const i=We([[...t.map(t=>[t[0],t[1]]),[t[0][0],t[0][1]]]],[[...e.map(t=>[t[0],t[1]]),[e[0][0],e[0][1]]]]);for(const t of i)t?.[0]&&(s+=mi(t[0]))}catch{return!1}return s>Math.max(1e-7,i*i)}function Co(t,e,i,s,n){const o=[i.n[0]*s,i.n[1]*s],r={polys:{},openings:{},movedSpans:{}};if(Math.abs(s)<1e-9)return r;for(const e of t){if(e.id===i.roomId){r.polys[e.id]=bo(e.poly,i.edge,s,i.n),r.movedSpans[e.id]=[[_o(i.a,o),_o(i.b,o)]];continue}const t=wo(e.poly,i.a,i.b,n);if(!t.length)continue;const a=ko(e.poly,i.a,i.b,o,n);a&&(r.polys[e.id]=a,r.movedSpans[e.id]=t.map(([t,e])=>[_o(t,o),_o(e,o)]))}for(const t of e)vo([t.x,t.y],i.a,i.b)<=n&&(r.openings[t.id]=[t.x+o[0],t.y+o[1]]);return r}function Do(t,e,i){for(const s of e)for(let e=0;ei)continue;const c=(t.x-n[0])*l[0]+(t.y-n[1])*l[1];if(c-t.length/2>=-i&&c+t.length/2<=a+i)return!0}return!1}function To(t,e,i){return t.filter(t=>e.some(e=>{for(let s=0;sa.polys[t.id]||t.poly;for(const e of l){const s=t.find(t=>t.id===e),n=a.polys[e];if(!xo(n))return!1;const l=fo(s.poly),h=fo(n);if(Math.abs(h)t.find(t=>t.id===e).poly),d=t.map(c);for(const t of To(e,h,2*r)){const e=a.openings[t.id];if(!Do(e?{...t,x:e[0],y:e[1]}:t,d,2*r))return!1}return!0}function Po(t,e,i,s,n,o){const r=t=>[s[0]+(t[0]-s[0])*n,s[1]+(t[1]-s[1])*n],a={poly:t.poly.map(r),openings:{}};for(const s of e){let e=!1;for(let i=0;i{for(let e=0;et.id===i);if(!l)return!1;const c=t.filter(t=>t.id!==i).map(t=>t.poly),h=Po(l,e,c,s,n,2*a),d=h.poly,p=function(t){const e=function(t){const e=[...t].sort((t,e)=>t[0]-e[0]||t[1]-e[1]);if(e.length<3)return e;const i=(t,e,i)=>(e[0]-t[0])*(i[1]-t[1])-(e[1]-t[1])*(i[0]-t[0]),s=[];for(const t of e){for(;s.length>=2&&i(s[s.length-2],s[s.length-1],t)<=0;)s.pop();s.push(t)}const n=[];for(let t=e.length-1;t>=0;t--){const s=e[t];for(;n.length>=2&&i(n[n.length-2],n[n.length-1],s)<=0;)n.pop();n.push(s)}return s.pop(),n.pop(),s.concat(n)}(t);if(e.length<3)return 0;let i=1/0;for(let t=0;tt.id===i?d:t.poly);for(const t of To(e,[l.poly],2*a)){const e=h.openings[t.id];if(!Do(e?{...t,x:e[0],y:e[1]}:t,u,2*a))return!1}return!0}function zo(t,e,i){const s=i/e;return mi(t)*s*s/1e4}function Io(t,e){return e?`${Math.round(10.7639*t)} ft²`:`${(Math.round(10*t)/10).toFixed(1)} m²`}const Eo=[[-90,"#070c14"],[-12,"#070c14"],[-4,"#131a28"],[0,"#4a3527"],[10,"#e8ddcf"],[30,"#ffffff"],[90,"#ffffff"]],Fo=t=>Math.min(1,Math.max(0,t));function No(t){const e=Math.min(90,Math.max(-90,Number(t)||0));let i=Eo[Eo.length-1][1];for(let t=1;t{const o=[t.x+n[0]*i*s,t.y+n[1]*i*s];return e.find(t=>t.poly.length>=3&&si(o,t.poly))||null},r=o(1),a=o(-1);return r&&a?null:r||a?r?{normal:[-n[0],-n[1]],roomId:r.id}:{normal:n,roomId:a.id}:null}function Lo(t,e,i){return i>0&&t[0]*e[0]+t[1]*e[1]>.05}function Ho(t,e,i,s){return[[t[0],t[1]],[e[0],e[1]],[e[0]+i[0]*s,e[1]+i[1]*s],[t[0]+i[0]*s,t[1]+i[1]*s]]}function qo(t,e){try{const i=We([[...t.map(t=>[t[0],t[1]]),[t[0][0],t[0][1]]]],[[...e.map(t=>[t[0],t[1]]),[e[0][0],e[0][1]]]]),s=[];for(const t of i){const e=t?.[0];!Array.isArray(e)||e.length<4||s.push(e.slice(0,e.length-1).map(t=>[t[0],t[1]]))}return s}catch{return[]}}function Uo(t,e,i,s,n,o,r){if(!(s>0))return[];const a=function(t,e){const i=function(t,e){return function(t){const e=t%360;return e<0?e+360:e}(t-e)}(t,e)*Math.PI/180;return[Math.sin(i),-Math.cos(i)]}(i,n),l=[-a[0],-a[1]],c=function(t){const e=Math.min(90,Math.max(0,t));return.7*(.8+1.7*Math.pow(1-e/90,1.6))}(s),h=[];for(const i of e){if(!(i.length>0))continue;const e=Oo(i,t);if(!e||!Lo(e.normal,a,s))continue;const n=t.find(t=>t.id===e.roomId);if(!n)continue;const d=o&&o[e.roomId]||n.poly,p=i.angle*Math.PI/180,u=i.length/2,_=[-e.normal[0],-e.normal[1]],m=Math.max(0,r?.[i.id]||0),g=i.x+_[0]*m/2,f=i.y+_[1]*m/2,v=Math.cos(p)*u,y=Math.sin(p)*u,b=[g-v,f-y],w=[g+v,f+y],k=c*i.length,$=qo(Ho(b,w,l,k),d);if(!$.length)continue;const x=l[0]*_[0]+l[1]*_[1];h.push({openingId:i.id,roomId:e.roomId,polys:$,a:b,b:w,dir:l,len:k,normal:_,depth:k*x})}return h}function Bo(t){return Math.round(10*(Number(t)||0))/10}const Wo=t=>"number"==typeof t&&Number.isInteger(t)&&t>=0&&t<=359?t:null;function jo(t,e){const i=Wo(e?.north_deg);return null!==i?i:Wo(t?.north_deg)}function Go(t,e){const i=t=>"static"===t||"daynight"===t?t:null;return i(e?.bg_mode)??i(t?.bg_mode)??"static"}function Vo(t,e){const i=e?.sun_rays;return"boolean"==typeof i?i:!0===t?.sun_rays}function Ko(t){const e=t?.states?.["sun.sun"]?.attributes,i=Number(e?.azimuth),s=Number(e?.elevation);return Number.isFinite(i)&&Number.isFinite(s)?{azimuth:i,elevation:s}:null}const Yo={color:"#607d8b",opacity:1,widthCm:3.6,fill:!1,fillColor:"#607d8b",fillOpacity:.25},Jo=(t,e=1)=>{const i=Number(t);return Number.isFinite(i)?Math.min(1,Math.max(0,i)):e},Zo=t=>{let e=Number(t);return Number.isFinite(e)?(e=(e%360+360)%360,e>180?e-360:e):0};function Xo(t,e,i){return Zo(e)?[t.x,t.y]:i([t.x,t.y])}const Qo=(t,e,i)=>{const s=Number(t),n=Number.isFinite(e)&&e>0?e:5;return Number.isFinite(s)&&s>0?s/n*i:0},tr=(t,e,i)=>{const s=Number(t),n=Number.isFinite(e)&&e>0?e:5;return Number.isFinite(s)&&i>0?s/i*n:0},er=(t,e,i,s=Yo.widthCm)=>{const n=Number(t?.width_cm);if(Number.isFinite(n)&&n>0)return n;const o=Number(t?.width);return Number.isFinite(o)&&o>0?tr(o,e,i):s};function ir(t,e){return{color:t.color,opacity:Jo(t.opacity),width_cm:Math.max(.1,Math.min(100,Number(t.widthCm)||.1)),...e?{fill:t.fill,fill_color:t.fillColor,fill_opacity:Jo(t.fillOpacity,.25)}:{}}}function sr(t){const e=t.x+t.w/2,i=t.y+t.h/2,s=Zo(t.angle)*Math.PI/180,n=Math.cos(s),o=Math.sin(s),r=(t,s)=>{const r=t-e,a=s-i;return[e+r*n-a*o,i+r*o+a*n]};return[r(t.x,t.y),r(t.x+t.w,t.y),r(t.x+t.w,t.y+t.h),r(t.x,t.y+t.h)]}function nr(t){const e=sr(t),i=(t,e)=>[(t[0]+e[0])/2,(t[1]+e[1])/2],s=[(e[0][0]+e[2][0])/2,(e[0][1]+e[2][1])/2];return{points:[...e,i(e[0],e[1]),i(e[1],e[2]),i(e[2],e[3]),i(e[3],e[0]),s],segments:e.map((t,i)=>({a:t,b:e[(i+1)%4]}))}}function or(t,e,i,s,n,o,r,a){const l=Zo(t.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l),d=-Math.sin(l),p=Math.cos(l),u=t.x+t.w/2,_=t.y+t.h/2,m=e>0?-t.w/2:t.w/2,g=i>0?-t.h/2:t.h/2,f=u+m*c+g*d,v=_+m*h+g*p,y=s-f,b=n-v;let w=(y*c+b*h)*(e>0?1:-1),k=(y*d+b*p)*(i>0?1:-1),$=null;if(o){const e=w/Math.max(t.w,a),i=k/Math.max(t.h,a);$=Math.max(a/Math.max(t.w,a),a/Math.max(t.h,a),e,i),w=t.w*$,k=t.h*$}if(r>0)if(o){const e=Math.max(1,Math.round(t.w/r)),i=Math.max(1,Math.round(t.h/r));if(Math.abs(t.w-e*r)<1e-6&&Math.abs(t.h-i*r)<1e-6){const s=(t,e)=>{let i=Math.abs(t),s=Math.abs(e);for(;s;)[i,s]=[s,i%s];return Math.max(1,i)},n=1/s(e,i),o=Math.max(a/t.w,a/t.h),r=Math.max(o,Math.round(($??1)/n)*n);w=t.w*r,k=t.h*r}else{t.w>=t.h?(w=Math.round(w/r)*r,k=w*(t.h/Math.max(t.w,a))):(k=Math.round(k/r)*r,w=k*(t.w/Math.max(t.h,a)))}}else w=Math.round(w/r)*r,k=Math.round(k/r)*r;w=Math.max(a,w),k=Math.max(a,k);return{x:f+(e>0?w/2:-w/2)*c+(i>0?k/2:-k/2)*d-w/2,y:v+(e>0?w/2:-w/2)*h+(i>0?k/2:-k/2)*p-k/2,w:w,h:k,angle:Zo(t.angle)||void 0}}const rr=(t,e,i)=>{const s=i[0]-e[0],n=i[1]-e[1],o=s*s+n*n;if(o<1e-12)return[...e];const r=Math.max(0,Math.min(1,((t[0]-e[0])*s+(t[1]-e[1])*n)/o));return[e[0]+s*r,e[1]+n*r]};const ar=15;function lr(t,e){return Number.isFinite(t)&&t>0&&Number.isFinite(e)&&e>0&&t*e<3}function cr(t){return Number.isFinite(t)?Math.max(1,Math.min(100,t)):1}function hr(t,e){return!Number.isFinite(t)||t<=0?"":String(e?Math.round(t/2.54*100)/100:Math.round(100*t)/100)}function dr(t,e,i){const s=Number(e)>0?Number(e):5;return cr(t)/s*i}function pr(t,e){return e>0&&Number.isFinite(t)?Math.round(t/e)*e:t}function ur(t,e){let i=e[0]-t[0],s=e[1]-t[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 _r(t,e,i){const s=pr((t[0]+e[0])/2,i),n=pr((t[1]+e[1])/2,i),[o,r]=ur(t,e);let a=Math.atan2(r,o);a<0&&(a+=Math.PI);const l=Math.round(1800*a)/1800,c=i>0&&i<.01?6:i<1?4:2;return`${s.toFixed(c)},${n.toFixed(c)}@${l.toFixed(4)}`}function mr(t,e,i,s){return 1===s?_r(t,e,i):_r([t[0]/s,t[1]/s],[e[0]/s,e[1]/s],i)}function gr(t,e){if(!Array.isArray(t.a)||!Array.isArray(t.b)||t.a.length<2||t.b.length<2)return null;const i=[Number(t.a[0]),Number(t.a[1]),Number(t.b[0]),Number(t.b[1])];if(!i.every(Number.isFinite))return null;const s=e>0?e:1;return[[i[0]*s,i[1]*s],[i[2]*s,i[3]*s]]}function fr(t,e,i,s,n){const o=n>0?n:1;return{key:mr(t,e,s,o),cm:cr(i),a:[t[0]/o,t[1]/o],b:[e[0]/o,e[1]/o]}}function vr(t,e){const i=e>0?e:1,s=[];for(const e of t){const t=e.key.lastIndexOf("@");if(t<0)continue;const[n,o]=e.key.slice(0,t).split(",").map(Number),r=Number(e.key.slice(t+1));[n,o,r].every(Number.isFinite)&&s.push({w:e,x:n*i,y:o*i,ang:r})}return s}function yr(t,e){const[i,s]=ur(t,e);let n=Math.atan2(s,i);return n<0&&(n+=Math.PI),n}function br(t,e){let i=Math.abs(t-e);return i>Math.PI/2&&(i=Math.PI-i),i<.02}function wr(t,e,i,s,n=1){if(!t?.length)return null;const o=mr(e,i,s,n),r=t.find(t=>t.key===o);if(r)return r;const a=n>0?n:1,l=(e[0]+i[0])/2,c=(e[1]+i[1])/2,h=yr(e,i),d=Math.max(.5*s,1e-9)*a;for(const e of vr(t,a))if(br(e.ang,h)&&Math.hypot(e.x-l,e.y-c)<=d)return e.w;return null}function kr(t,e,i,s,n=1){const o=wr(t,e,i,s,n);return o&&o.cm>0?cr(o.cm):0}function $r(t,e,i,s=1,n=[]){if(!t?.length)return[];const o=new Set,r=Xe(e);for(const t of r)o.add(mr([t[0],t[1]],[t[2],t[3]],i,s));const a=e||[],l=Math.max(i*s*.02,1e-9);for(let t=0;t(o.has(t.key)||(t=>{const e=gr(t,s);if(!e)return!1;const[i,o]=e,a=o[0]-i[0],c=o[1]-i[1],h=Math.hypot(a,c);if(h<=l)return!1;if(!r.some(t=>{const e=[t[0],t[1]],s=[t[2],t[3]];return br(yr(i,o),yr(e,s))&&Br(i[0],i[1],e[0],e[1],s[0],s[1])<=l&&Br(o[0],o[1],e[0],e[1],s[0],s[1])<=l}))return!1;const d=(n||[]).some(t=>{const e=[t[0],t[1]],s=[t[2],t[3]];if(!br(yr(i,o),yr(e,s)))return!1;const n=t=>Math.abs((t[0]-i[0])*c-(t[1]-i[1])*a)/h;if(n(e)>l||n(s)>l)return!1;const r=h*h,d=((e[0]-i[0])*a+(e[1]-i[1])*c)/r,p=((s[0]-i[0])*a+(s[1]-i[1])*c)/r;return Math.min(1,Math.max(d,p))-Math.max(0,Math.min(d,p))>l/h});return!d})(t))&&t.cm>=1&&t.cm<=100)}function xr(t,e,i,s=8){const[n,o]=ur(t,e);let r=Math.atan2(o,n);r<0&&(r+=Math.PI);let a=i*Math.PI/180%Math.PI;a<0&&(a+=Math.PI);let l=Math.abs(r-a);return l>Math.PI/2&&(l=Math.PI-l),l<=s*Math.PI/180}function Sr(t,e,i,s,n=1){if(!t?.length)return[];if(e.length!==i.length)return t.slice();const o=new Map;for(let t=0;t0?n:1,a=Math.max(.5*s,1e-9)*r,l=new Set,c=[];for(const n of t){let t="",h=null;const d=gr(n,r);if(d)for(let n=0;na||Br(d[1][0],d[1][1],o[0],o[1],l[0],l[1])>a)continue;const u=l[0]-o[0],_=l[1]-o[1],m=u*u+_*_;if(m<1e-18)continue;const g=t=>{const e=Math.max(0,Math.min(1,((t[0]-o[0])*u+(t[1]-o[1])*_)/m));return[c[0]+(p[0]-c[0])*e,c[1]+(p[1]-c[1])*e]};h=[g(d[0]),g(d[1])],t=mr(h[0],h[1],s,r);break}if(d||(t=o.get(n.key)||""),!t){const o=vr([n],r)[0];if(o)for(let n=0;n1.000001)continue;if(Br(o.x,o.y,l[0],l[1],c[0],c[1])>a)continue;const g=h[0]+(d[0]-h[0])*Math.max(0,Math.min(1,m)),f=h[1]+(d[1]-h[1])*Math.max(0,Math.min(1,m)),[v,y]=ur(h,d),b=Math.max(s*r,1e-6);t=mr([g-v*b,f-y*b],[g+v*b,f+y*b],s,r);break}}t||(t=n.key),l.has(t)||(l.add(t),c.push(h?fr(h[0],h[1],n.cm,s,r):{...n,key:t,cm:cr(n.cm)}))}return c}function Mr(t,e,i,s,n,o=1){const r=mr(e,i,n,o),a=(t||[]).filter(t=>t.key!==r);return null==s||s<1?a:[...a,fr(e,i,s,n,o)]}function Cr(t,e,i,s,n,o=[],r=1){let a=t?t.slice():[];for(const t of function(t,e,i,s,n=1,o=[]){const r=Nr(t,e,i,s,n,o);if(!r)return[];const a=[];for(let t=0;t0)||!t||t.length<2)return"";if(i&&t.length>=3){let i=t;const s=t[t.length-1];if(t.length>=4&&Math.hypot(t[0][0]-s[0],t[0][1]-s[1])<1e-9&&(i=t.slice(0,-1)),i.length>=3){const t=i.map(()=>e),s=Kr(i,t),n=Ir(i,t);if(s&&n)return`${Er(s)} ${Er(Fr(n))}`}}let s="";for(let i=0;i.05)&&Br(r,a,t[0],t[1],t[2],t[3])<=o)return!0}return!1}function Rr(t,e){return Math.max(t*(e>0?e:1)*.04,1e-9)}function Pr(t,e){const i=t[e],s=t[(e+1)%t.length],n=s[0]-i[0],o=s[1]-i[1],r=Math.hypot(n,o)||1;let a=-o/r,l=n/r;const c=[(i[0]+s[0])/2,(i[1]+s[1])/2];return function(t,e){let i=!1;for(let s=0,n=e.length-1;st[1]!=l>t[1]&&t[0]<(a-o)*(t[1]-r)/(l-r+0)+o&&(i=!i)}return i}([c[0]+.001*a,c[1]+.001*l],t)||(a=-a,l=-l),[a,l]}function Ar(t,e){const i=t[0]*e[1]-t[1]*e[0],s=t[0]*e[0]+t[1]*e[1];return Math.abs(i)<1e-9&&s>0}function zr(t,e,i,s){const n=e[0]*s[1]-e[1]*s[0];if(Math.abs(n)<1e-12)return null;const o=[i[0]-t[0],i[1]-t[1]],r=(o[0]*s[1]-o[1]*s[0])/n;return[t[0]+r*e[0],t[1]+r*e[1]]}function Ir(t,e){const i=t?.length||0;if(i<3||e.length!==i)return null;if(e.every(t=>!(t>0)))return t.map(t=>[t[0],t[1]]);const s=[];for(let n=0;n0||d>0)){s.push([t[n][0],t[n][1]]);continue}if(Ar(b,w)){const e=t[n],i=[e[0]+p*h,e[1]+u*h],o=[e[0]+_*d,e[1]+m*d];s.push(i),Math.hypot(o[0]-i[0],o[1]-i[1])>1e-9&&s.push(o);continue}const x=zr(k,b,$,w),S=Math.max(h,d,1e-9);if(x){if(Math.hypot(x[0]-t[n][0],x[1]-t[n][1])<=4*S){s.push(x);continue}}h>0&&s.push([t[n][0]+p*h,t[n][1]+u*h]),d>0&&s.push([t[n][0]+_*d,t[n][1]+m*d]),h>0||d>0||s.push([t[n][0],t[n][1]])}return s.length>=3?s:null}function Er(t,e=!0){if(!t.length)return"";let i=`M ${t[0][0]} ${t[0][1]}`;for(let e=1;et?.id===e),a=Je(r);if(!a||a.length<3)return null;const l=Rr(s,n),c=[];for(const i of t||[]){if(!i||i.id===e)continue;const t=Je(i);if(t)for(const e of us(a,t,l))c.push([e[0],e[1]],[e[2],e[3]])}for(const t of i||[])c.push([t[0],t[1]],[t[2],t[3]]);for(const t of o||[]){const e=gr(t,n);e&&c.push(e[0],e[1])}const h=[],d=[];for(let t=0;tl)continue;const r=((t[0]-e[0])*(i[0]-e[0])+(t[1]-e[1])*(i[1]-e[1]))/(s*s);r<=n||r>=1-n||(o.some(t=>Math.abs(t-r)*s<=2*l)||o.push(r))}o.sort((t,e)=>t-e);for(const s of o)h.push([e[0]+(i[0]-e[0])*s,e[1]+(i[1]-e[1])*s]),d.push(t)}return{poly:h,parent:d,orig:a}}function Or(t,e,i,s){const n=e.poly.length,o=new Array(n).fill(0);if(!t?.length)return o;const r=new Set,a=[];for(let l=0;l0?(o[l]=cr(c.cm),r.add(c.key)):a.push(l)}if(!a.length)return o;const l=s>0?s:1,c=Math.max(.5*i,1e-9)*l,h=vr(t,l).filter(t=>t.w.cm>0),d=new Map;for(const t of a){const i=e.parent[t],s=d.get(i);s?s.push(t):d.set(i,[t])}for(const[t,i]of d){const s=e.orig[t],n=e.orig[(t+1)%e.orig.length],a=yr(s,n),d=(s[0]+n[0])/2,p=(s[1]+n[1])/2;let u=null;const _=Math.hypot(n[0]-s[0],n[1]-s[1]);for(const t of h){if(r.has(t.w.key))continue;if(!br(t.ang,a))continue;const e=gr(t.w,l);let i=!1,o=0;if(e){if(!br(yr(e[0],e[1]),a))continue;if(Br(s[0],s[1],e[0][0],e[0][1],e[1][0],e[1][1])>c||Br(n[0],n[1],e[0][0],e[0][1],e[1][0],e[1][1])>c)continue;i=!0,o=Math.max(0,Math.hypot(e[1][0]-e[0][0],e[1][1]-e[0][1])-_)}else{if(Br(t.x,t.y,s[0],s[1],n[0],n[1])>c)continue;o=Math.hypot(t.x-d,t.y-p)}(!u||i&&!u.exact||i===u.exact&&ot?.id===e),n=Je(s);if(!n)return[];const o=[];for(const s of t||[]){if(!s||s.id===e)continue;const t=Je(s);if(t)for(const e of us(n,t,i))o.push(e)}return o}(t,e,Rr(n,a)),h=function(t,e,i,s,n){const o=Rr(s,n),r=[];for(let a=0;aBr(h,d,t[0],t[1],t[2],t[3])<=o);r.push(p?"shared":"outer")}return r}(l.poly,c,s,n,a),d=Or(i,l,n,a),p=d.map((t,e)=>h[e]&&t>0?dr(t,o,r)/2:0);return{...l,kinds:h,cms:d,offsets:p}}function Hr(t,e,i,s,n,o,r=1){const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,n,o,r);if(c)for(let t=0;t0&&!l.has(c.key)&&(l.add(c.key),a.push(c));const c=[];for(const a of t||[]){if(!a?.id)continue;const l=Lr(t,a.id,e,i,s,n,o,r);if(l)for(let t=0;t0)||null===l.kinds[i]){t++;continue}let o=t;for(;o+10&&c.push({a:[h[0],h[1]],b:[d[0],d[1]],key:mr(h,d,s,r),cm:n,len:p}),t=o+1}}}c.sort((t,e)=>e.len-t.len||t.key.localeCompare(e.key));const h=[],d=new Set,p=new Set,u=4*Rr(s,r);for(const t of c){const e=a.filter(e=>!p.has(e.key)&&e.cm===t.cm&&br(yr(e.a,e.b),yr(t.a,t.b))&&Br(e.a[0],e.a[1],t.a[0],t.a[1],t.b[0],t.b[1])<=u&&Br(e.b[0],e.b[1],t.a[0],t.a[1],t.b[0],t.b[1])<=u);if(e.length){for(const t of e)p.add(t.key);d.has(t.key)||(d.add(t.key),h.push(fr(t.a,t.b,t.cm,s,r)))}}for(const t of a)p.has(t.key)||d.has(t.key)||(d.add(t.key),h.push(fr(t.a,t.b,t.cm,s,r)));return h}function Ur(t,e,i,s,n,o,r,a=1){const l=Rr(n,a),c=(s[0]+s[2])/2,h=(s[1]+s[3])/2,d=yr([s[0],s[1]],[s[2],s[3]]);let p=null;for(const s of Hr(t,e,i,n,o,r,a)){if(!br(yr(s.a,s.b),d))continue;const t=Br(c,h,s.a[0],s.a[1],s.b[0],s.b[1]);t>4*l||(!p||tt?.id===e),c=Je(l);if(!c||c.length<3)return null;if(!i?.length)return c.map(t=>[t[0],t[1]]);const h=Lr(t,e,i,s,n,o,r,a);return h&&h.offsets.some(t=>t>0)&&Ir(h.poly,h.offsets)||c.map(t=>[t[0],t[1]])}function jr(t){const e=t.map(t=>[t[0],t[1]]);return e.push([t[0][0],t[0][1]]),[e]}function Gr(t,e,i,s=[],n,o,r,a=1,l=[]){if(!e?.length&&!l.length)return null;const c=[];let h=0;for(const s of t||[]){if(!s?.id)continue;const l=Lr(t,s.id,e,i,n,o,r,a);if(!l||l.poly.length<3||!l.offsets.some(t=>t>0))continue;for(const t of l.offsets)t>0&&(h=Math.max(h,2*t));const d=Kr(l.poly,l.offsets),p=Ir(l.poly,l.offsets);d&&c.push({outset:d,inset:p})}for(const t of l){const e=t.map(t=>t[0]),i=t.map(t=>t[1]);if(e.length){const s=Math.min(Math.max(...e)-Math.min(...e),Math.max(...i)-Math.min(...i)),n=Math.min(...t.map((e,i)=>{const s=t[(i+1)%t.length];return Math.hypot(s[0]-e[0],s[1]-e[1])}));h=Math.max(h,t.length>16?s:n)}}const d=function(t,e,i,s,n,o,r){if(!e?.length||!i?.length)return[];const a=4*Rr(s,r),l=new Map;for(const a of Hr(t,e,i,s,n,o,r))!a.open&&a.half>0&&!l.has(a.key)&&l.set(a.key,a);const c=[...l.values()];if(c.length<2)return[];const h=[];for(const t of i)for(const e of[[t[0],t[1]],[t[2],t[3]]])h.some(t=>Math.hypot(t[0]-e[0],t[1]-e[1])<=a)||h.push(e);const d=[],p=(t,e)=>{let i=0,s=0;if(Math.hypot(t.a[0]-e[0],t.a[1]-e[1])<=a)i=t.b[0]-t.a[0],s=t.b[1]-t.a[1];else{if(!(Math.hypot(t.b[0]-e[0],t.b[1]-e[1])<=a))return null;i=t.a[0]-t.b[0],s=t.a[1]-t.b[1]}const n=Math.hypot(i,s);return n>a?[i/n,s/n]:null};for(const t of h){const e=c.map(e=>({iv:e,u:p(e,t)})).filter(t=>!!t.u);for(let i=0;i4*_||d.push(r>0?[t.slice(),h,u,p]:[t.slice(),p,u,h])}}return d}(t,e,i,n,o,r,a),p=s.length?Jr(t,e,i,n,o,r,a):null;try{const t=t=>{const e=jr(t.outset);return t.inset?je(e,jr(t.inset)):e};let e=c.length?t(c[0]):null;for(let i=1;i0))continue;const i=Qr(p,t,!0);if(!i.negative&&!i.positive)continue;const s=t.angle*Math.PI/180,o=Math.cos(s),r=Math.sin(s),l=-r,c=o,d=t.length/2,u=1.25*Math.max(h,n*a),_=[[t.x-o*d-l*u,t.y-r*d-c*u],[t.x+o*d-l*u,t.y+r*d-c*u],[t.x+o*d+l*u,t.y+r*d+c*u],[t.x-o*d+l*u,t.y-r*d+c*u]];e&&(e=je(e,jr(_)))}for(const t of l)t.length<3||(e=e?Be(e,jr(t)):[jr(t)]);return{geom:e||[],depthUnits:h}}catch{return null}}function Vr(t,e,i,s=[],n,o,r,a=1,l=[]){if(!e?.length&&!l.length)return null;const c=Gr(t,e,i,s,n,o,r,a,l),h=c?function(t){if(!t)return"";let e="";for(const i of t)if(Array.isArray(i))for(const t of i){if(!Array.isArray(t)||t.length<4)continue;const i=t.slice(0,t.length-1);i.length<3||(e+=(e?" ":"")+Er(i.map(t=>[t[0],t[1]])))}return e}(c.geom):"";if(c&&h)return{d:h,depthUnits:c.depthUnits,fillRule:"evenodd"};if(c)return null;const d=function(t,e,i,s,n,o,r=1){if(!e?.length)return[];const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,n,o,r);if(!c||c.poly.length<3||!c.offsets.some(t=>t>0))continue;const h=Kr(c.poly,c.offsets),d=Ir(c.poly,c.offsets);if(!h||!d)continue;const p=`${Er(h)} ${Er(Fr(d))}`;let u="",_="outer",m=0,g=0;for(let t=0;t0){u=mr(c.poly[t],c.poly[(t+1)%c.poly.length],s,r),_=c.kinds[t]||"outer",m=c.cms[t],g=dr(m,n,o);break}a.push({d:p,key:u,kind:_,cm:m,depthUnits:g})}return a}(t,e,i,n,o,r,a),p=l.map(t=>Er(t)).join(" ");if(!d.length&&!p)return null;let u=0;for(const t of d)u=Math.max(u,t.depthUnits);return{d:[d.map(t=>t.d).join(" "),p].filter(Boolean).join(" "),depthUnits:u,fillRule:"nonzero"}}function Kr(t,e){const i=t?.length||0;if(i<3||e.length!==i)return null;if(e.every(t=>!(t>0)))return t.map(t=>[t[0],t[1]]);Fr(t),e.slice().reverse();const s=[];for(let n=0;n0||a>0)){s.push([t[n][0],t[n][1]]);continue}if(Ar(b,w)){const e=t[n],i=[e[0]-l*r,e[1]-c*r],o=[e[0]-h*a,e[1]-d*a];s.push(i),Math.hypot(o[0]-i[0],o[1]-i[1])>1e-9&&s.push(o);continue}const x=zr(k,b,$,w),S=Math.max(r,a,1e-9);if(x){if(Math.hypot(x[0]-t[n][0],x[1]-t[n][1])<=4*S){s.push(x);continue}}r>0&&s.push([t[n][0]-l*r,t[n][1]-c*r]),a>0&&s.push([t[n][0]-h*a,t[n][1]-d*a])}return s.length>=3?s:null}function Yr(t,e,i,s,n,o,r=1){const a=[];for(const l of t||[]){const c=Je(l);if(c&&c.length>=3){const h=Lr(t,l.id,e,i,s,n,o,r),d=h&&h.offsets.some(t=>t>0)?Kr(h.poly,h.offsets):null,p=d||c;a.push({poly:p.map(t=>t.join(",")).join(" ")})}else l&&null!=l.x&&null!=l.y&&null!=l.w&&null!=l.h&&a.push({rect:{x:l.x,y:l.y,w:l.w,h:l.h,rx:.03*Math.min(l.w,l.h)}})}return a}function Jr(t,e,i,s,n,o,r=1){const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,n,o,r);if(!c)continue;const h=Math.abs(mi(c.poly));for(let t=0;t[Math.max(e,t.x0),Math.min(i,t.x1)]).filter(t=>t[1]-t[0]>s).sort((t,e)=>t[0]-e[0]||t[1]-e[1]);if(!n.length)return{coverage:0,full:!1};let o=n[0][0],r=n[0][1],a=0,l=o<=e+s;for(let t=1;t=i-s,{coverage:a,full:l}}function Xr(t,e){return Number(e.full)-Number(t.full)||t.faceDistance-e.faceDistance||t.area-e.area||t.roomId.localeCompare(e.roomId)}function Qr(t,e,i=!1){const s=Number(e?.x),n=Number(e?.y),o=Number(e?.angle),r=Number(e?.length);if(!([s,n,o,r].every(Number.isFinite)&&r>0))return{negative:null,positive:null};const a=o*Math.PI/180,l=Math.cos(a),c=Math.sin(a),h=-c,d=l,p=r/2,u=Math.max(1e-9,t.adjacencyEps),_=new Map;let m=0;for(const e of t.edges){if(i&&!(e.half>0))continue;if(!xr(e.a,e.b,o))continue;const[t,r]=ur(e.a,e.b);if(Math.abs((s-e.a[0])*r-(n-e.a[1])*t)>u)continue;const a=(e.a[0]-s)*l+(e.a[1]-n)*c,g=(e.b[0]-s)*l+(e.b[1]-n)*c,f=Math.max(-p,Math.min(a,g)),v=Math.min(p,Math.max(a,g));if(v-f<=u)continue;const y=e.inward[0]*h+e.inward[1]*d>=0?1:-1,b=((e.a[0]+e.b[0])/2-s)*h+((e.a[1]+e.b[1])/2-n)*d,w=Math.abs(b+y*e.half),k=`${y}|${e.roomId}`,$={x0:f,x1:v,half:e.half,cm:e.cm,key:e.key,axis:[t,r]},x=_.get(k);x?(x.pieces.push($),x.faceDistance=Math.min(x.faceDistance,w)):_.set(k,{roomId:e.roomId,side:y,order:m++,pieces:[$],faceDistance:w,area:e.area,coverage:0,full:!1})}for(const t of _.values()){const e=Zr(t.pieces,-p,p,u);t.coverage=e.coverage,t.full=e.full}const g=t=>{const e=[..._.values()].filter(e=>e.side===t&&e.coverage>u);return e.sort(Xr),e[0]||null};return{negative:g(-1),positive:g(1)}}function ta(t,e){const i=Qr(t,e),s=[i.negative,i.positive].filter(t=>!!t).sort((t,e)=>t.order-e.order);if(!s.length)return{ox:0,oy:0,cm:0,side:-1};const n=s[0],o=e.flip_v?-n.side:n.side,r=(-1===o?i.negative:i.positive)||n,a=[...r.pieces].sort((t,e)=>(t.x0<=0&&t.x1>=0?0:Math.min(Math.abs(t.x0),Math.abs(t.x1)))-(e.x0<=0&&e.x1>=0?0:Math.min(Math.abs(e.x0),Math.abs(e.x1)))||e.x1-e.x0-(t.x1-t.x0)||t.key.localeCompare(e.key))[0];if(!(a.half>0&&a.cm>0))return{ox:0,oy:0,cm:0,side:o};const l=e.angle*Math.PI/180,c=-Math.sin(l),h=Math.cos(l);return{ox:c*o*a.half,oy:h*o*a.half,cm:a.cm,side:o}}function ea(t,e){const i=1e-9,s=[];for(const t of[...e].filter(t=>t.x1>t.x0&&t.half>0).sort((t,e)=>t.x0-e.x0||t.x1-e.x1||t.half-e.half)){const e=s[s.length-1];e&&t.x0<=e.x1+i&&Math.abs(t.half-e.half)<=i?e.x1=Math.max(e.x1,t.x1):s.push({...t})}const n=[];for(let e=0;e=n-l?i.push([s,n]):(t>s+l&&i.push([s,Math.min(n,t)]),et[0]-e[0]||t[1]-e[1]),b=[];for(const t of y){const e=b[b.length-1];e&&t[0]<=e[1]+l?e[1]=Math.max(e[1],t[1]):b.push([t[0],t[1]])}s.set(g,b)}return a}function sa(t,e){const i=new Map;return e.map(e=>function(t,e,i){const s=Qr(t,e,!0),n=s.negative,o=s.positive;if(!n&&!o)return null;let r;if(n&&o)r=[{candidate:n,side:-1},{candidate:o,side:1}];else{const t=n||o;r=[{candidate:t,side:-1},{candidate:t,side:1}]}const a=r.map(({candidate:t,side:s})=>({candidate:t,side:s,pieces:ia(e,s,t.pieces,i)})),l=a.map(({candidate:t,side:e,pieces:i})=>({side:e,roomId:t.roomId,d:ea(e,i)})),c=a.flatMap(({pieces:t})=>t);if(!c.length)return null;const h=Math.max(...c.map(t=>t.half));return{faces:l,minY:-h,maxY:h,wallKey:[...new Set(c.map(t=>t.key))].sort().join("|")}}(t,e,i))}const na=150;function oa(t){return Number.isFinite(t)?Math.max(1,Math.min(na,t)):1}function ra(t){return((Number.isFinite(Number(t))?Number(t):0)%90+90)%90}const aa=t=>{const e=t.map(t=>[t[0],t[1]]);return!e.length||e[0][0]===e[e.length-1][0]&&e[0][1]===e[e.length-1][1]||e.push([...e[0]]),[e]};function la(t){const e=[];for(const i of t||[])for(const t of i||[]){const i=(t||[]).filter(t=>Array.isArray(t)&&t.length>=2);i.length<4||e.push(`M ${i.slice(0,-1).map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`)}return e.join(" ")}function ca(t,e,i,s,n){const o=e[0]-t[0],r=e[1]-t[1],a=Math.hypot(o,r);if(!(a>1e-9))return null;const l=dr(i,s,n)/2,c=-r/a*l,h=o/a*l;return[[t[0]+c,t[1]+h],[e[0]+c,e[1]+h],[e[0]-c,e[1]-h],[t[0]-c,t[1]-h]]}function ha(t,e,i){const s=Number(e)>0?Number(e):5,n=oa(t.cm)/s*i,o=t.center[0],r=t.center[1];if("circle"===t.shape){const t=n/2;return Array.from({length:96},(e,i)=>{const s=i/96*Math.PI*2;return[o+Math.cos(s)*t,r+Math.sin(s)*t]})}const a=n/2,l=ra(t.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l);return[[-a,-a],[a,-a],[a,a],[-a,a]].map(([t,e])=>[o+t*c-e*h,r+t*h+e*c])}function da(t,e,i){const s=[];for(let n=0;n+1t.length>=3).map(t=>aa(t));return e.length?Be(e[0],...e.slice(1)):null}catch{return null}}const ua=t=>`M ${t.map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`;function _a(t,e){const i=pa(t.filter(t=>t.length>=3)),s=pa(e.filter(t=>t.length>=3));if(!i)return[];if(!s)return[];try{return function(t){const e=[];for(const i of t||[]){if(ga([i])<=1e-6)continue;const t=[];for(const e of i||[]){const i=(e||[]).filter(t=>Array.isArray(t)&&t.length>=2);i.length<4||t.push(ua(i.slice(0,-1)))}t.length&&e.push(t.join(" "))}return e}(We(i,s))}catch{return[]}}function ma(t,e){if(!e.length)return[aa(t)];try{const i=pa(e);if(i)return je(aa(t),i)}catch{}let i=[aa(t)];for(const t of e)if(!(t.length<3))try{i=je(i,aa(t))}catch{}return i}function ga(t){let e=0;for(const i of t||[])if(i?.length){e+=mi(i[0]||[]);for(let t=1;t=4&&e.push(t.slice(0,-1).map(t=>[t[0],t[1]]))}return e}function va(t,e){let i=!1;for(let s=0,n=e.length-1;st[1]!=l>t[1]&&t[0]<(a-o)*(t[1]-r)/(l-r||1e-12)+o&&(i=!i)}return i}function ya(t,e,i){if(Math.hypot(t.center[0]-e.center[0],t.center[1]-e.center[1])>i)return!1;if(Math.abs(oa(t.cm)-oa(e.cm))>1e-6)return!1;if(t.shape!==e.shape)return!0;if("circle"===t.shape||"circle"===e.shape)return!0;const s=Math.abs(ra(t.angle)-ra(e.angle));return Math.min(s,90-s)<=1e-6}const ba=1e3;function wa(t,e){const i=Number(t),s=Number.isFinite(i)&&i>0?i:1,n=s>=1?e:e*s,o=s>=1?e/s:e;return{x:(e-n)/2,y:(e-o)/2,w:n,h:o}}const ka=.01,$a=100;function xa(t,e=1e3){const i=wa(t?.plan_aspect,e),s=Number(t?.plan_scale),n=Number.isFinite(s)&&s>0?Math.min($a,Math.max(ka,s)):1,o=Number(t?.plan_scale_x),r=Number(t?.plan_scale_y),a=Number.isFinite(o)&&o>0?Math.min($a,Math.max(ka,o)):n,l=Number.isFinite(r)&&r>0?Math.min($a,Math.max(ka,r)):n,c=Number(t?.plan_x),h=Number(t?.plan_y),d=Zo(t?.plan_angle);return{x:i.x+(Number.isFinite(c)?Ea(c):0)*e,y:i.y+(Number.isFinite(h)?Ea(h):0)*e,w:i.w*a,h:i.h*l,...d?{angle:d}:{}}}function Sa(t){if(null==t.x||null==t.y)return{x:t.x,y:t.y,w:t.w,h:t.h};const e=Number(t.w)||0,i=Number(t.h)||0;return{x:e<0?t.x+e:t.x,y:i<0?t.y+i:t.y,w:Math.abs(e),h:Math.abs(i)}}function Ma(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=ba,i=function(t){return Array.isArray(t)&&4===t.length&&t.every(t=>Number.isFinite(t))&&t[2]>1e-6&&t[3]>1e-6?t:[0,0,1,1]}(t.view_box);return{id:t.id,title:t.title,vb:[i[0]*ba,i[1]*e,i[2]*ba,i[3]*e],bg:t.plan_url?{href:ys(t.plan_url),...xa(t,ba)}:null,rooms:(t.rooms||[]).map(t=>{const i={...t,...Sa(t)};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*ba:void 0,y:null!=i.y?i.y*e:void 0,w:null!=i.w?i.w*ba:void 0,h:null!=i.h?i.h*e:void 0,poly:i.poly?i.poly.map(t=>[t[0]*ba,t[1]*e]):void 0}}),room_drafts:(t.room_drafts||[]).map(t=>({id:t.id,points:(t.points||[]).map(t=>[t[0]*ba,t[1]*e]),segments:(t.segments||[]).map(t=>({cm:Number(t.cm)}))})),partitions:(t.partitions||[]).map(t=>({id:t.id,a:[t.a[0]*ba,t.a[1]*e],b:[t.b[0]*ba,t.b[1]*e],cm:Number(t.cm)})),wall_columns:(t.wall_columns||[]).map(t=>({id:t.id,shape:"circle"===t.shape?"circle":"square",center:[t.center[0]*ba,t.center[1]*e],cm:Number(t.cm),..."circle"===t.shape?{}:{angle:ra(t.angle)}}))}}):[]}const Ca=5e3,Da=Ca*ba,Ta=240,Ra=ba/Ta,Pa=1/Ta;function Aa(t){if(!Number.isFinite(t))return t;const e=Math.round(t*Ta/ba)*ba/Ta;return Math.abs(e-t)<=1e-9*Ra?t:e}function za(t){return{x:Aa(t.x),y:Aa(t.y)}}function Ia(t){return Number.isFinite(t)?Math.min(Da,Math.max(-Da,t)):0}function Ea(t){return Number.isFinite(t)?Math.min(Ca,Math.max(-Ca,t)):0}const Fa=1/3,Na=200;function Oa(t){let e=1/0,i=1/0,s=-1/0,n=-1/0;for(const o of t){const t=Number(o[0]),r=Number(o[1]);Number.isFinite(t)&&Number.isFinite(r)&&(ts&&(s=t),r>n&&(n=r))}return e>s?null:{minX:e,minY:i,maxX:s,maxY:n}}function La(t){return t.poly&&t.poly.length?Oa(t.poly):null==t.x||null==t.y?null:Oa([[t.x,t.y],[t.x+(t.w||0),t.y+(t.h||0)]])}function Ha(t,e){const i=[];for(const e of t.rooms||[]){const t=La(e);t&&i.push(t)}if(t.bg){const e=Oa(sr(t.bg));e&&i.push(e)}for(const t of e||[])if(Array.isArray(t)){const e=Oa([t]);e&&i.push(e)}else i.push(t);return i}const qa=t=>{if(!t.length)return 0;const e=[...t].sort((t,e)=>t-e),i=e.length>>1;return e.length%2?e[i]:(e[i-1]+e[i])/2},Ua=t=>{let e=1/0,i=1/0,s=-1/0,n=-1/0;for(const o of t)o.minXs&&(s=o.maxX),o.maxY>n&&(n=o.maxY);return e>s||i>n?null:{x:e,y:i,w:s-e,h:n-i}};function Ba(t,e){let{x:i,y:s,w:n,h:o}=t;n<30&&(i=i+n/2-100,n=Na),o<30&&(s=s+o/2-100,o=Na);const r=Math.max(n,o)*e;return{x:i-r,y:s-r,w:n+2*r,h:o+2*r}}function Wa(t,e={}){const i=e.pad??.05,s=e.k??10,n=e.minSpread??50,o=t.filter(t=>Number.isFinite(t.minX)&&Number.isFinite(t.minY)&&Number.isFinite(t.maxX)&&Number.isFinite(t.maxY)&&Math.abs(t.minX)<=Da&&Math.abs(t.maxX)<=Da&&Math.abs(t.minY)<=Da&&Math.abs(t.maxY)<=Da);if(!o.length)return{core:null,all:null,outliers:0};const r=Ua(o);if(o.length<4){const t=Ba(r,i);return{core:t,all:t,outliers:0}}const a=o.map(t=>(t.minX+t.maxX)/2),l=o.map(t=>(t.minY+t.maxY)/2),c=qa(a),h=qa(l),d=o.map((t,e)=>Math.max(Math.abs(a[e]-c),Math.abs(l[e]-h))),p=Math.max(((t,e)=>t.length?t[Math.min(t.length-1,Math.max(0,Math.round(e*(t.length-1))))]:0)([...d].sort((t,e)=>t-e),.75),n),u=d.map(t=>t>s*p),_=u.filter(Boolean).length,m=_&&_<=o.length*Fa?o.filter((t,e)=>!u[e]):o;return{core:Ba(Ua(m)||r,i),all:Ba(r,i),outliers:m===o?0:_}}function ja(t,e,i=.05){const s=Wa(Ha(t,e),{pad:i});if(s.core)return s.core;const n=t.vb&&4===t.vb.length&&t.vb[2]>0&&t.vb[3]>0?t.vb:[0,0,ba,ba];return{x:n[0],y:n[1],w:n[2],h:n[3]}}function Ga(t){const e=ja(t);return{x:e.x+e.w/2,y:e.y+e.h/2}}function Va(t){const e=[];for(const i of t.rooms||[]){const t=La(i);t&&e.push(t)}const i=Wa(e,{pad:0}).core;return i?Math.max(ba,Math.min(Da,Math.max(i.w,i.h))):ba}function Ka(t,e,i,s=1){const n=Number(i),o=Number.isFinite(s)&&s>0?s:1;return!Number.isFinite(n)||n<=0?t*o:t*Va(e)*o/n}const Ya=[1,2,5,10,20,50,100,200,500,1e3];function Ja(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),n=Math.min(...i);return{x:s,y:n,w:Math.max(...e)-s,h:Math.max(...i)-n}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Za(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}const Xa=["furniture","appliance","sanitary","other"],Qa=[{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]]}],tl=new Map(Qa.map(t=>[t.id,t]));function el(t){return t&&tl.get(t)||null}function il(t){return Qa.filter(e=>e.group===t)}function sl(t,e,i=Ra,s=1e3){const n=Number(e)>0?Number(e):5;return(Number(t)||0)/n*i/s}const nl=5e-4,ol=Ca;function rl(t){return Number.isFinite(t)?Math.max(nl,Math.min(ol,t)):nl}const al=t=>{const e=Math.round(1e3*t)/1e3;return Object.is(e,-0)?"0":String(e)};function ll(t,e,i){const s=el(t);if(!(s&&e>0&&i>0))return"";const n=t=>al(t*e),o=t=>al(t*i),r=[];for(const t of s.g)if("r"===t[0]){const[,e,i,s,a]=t;r.push(`M${n(e)} ${o(i)}H${n(e+s)}V${o(i+a)}H${n(e)}Z`)}else if("l"===t[0]){const[,e,i,s,a]=t;r.push(`M${n(e)} ${o(i)}L${n(s)} ${o(a)}`)}else if("e"===t[0]){const[,s,a,l,c]=t;r.push(`M${n(s-l)} ${o(a)}A${al(l*e)} ${al(c*i)} 0 0 1 ${n(s+l)} ${o(a)}A${al(l*e)} ${al(c*i)} 0 0 1 ${n(s-l)} ${o(a)}Z`)}else{const e=t.slice(1);if(e.length<4)continue;let i=`M${n(e[0])} ${o(e[1])}`;for(let t=2;t+1{let e=(t%360+360)%360;return e>180&&(e-=360),e};function hl(t,e,i,s,n,o=0){let r=null,a=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=((t-s)*d+(e-l)*p)/u;m=Math.max(0,Math.min(1,m));let g=s+m*d,f=l+m*p;const v=Math.hypot(t-g,e-f);if(!(v0){const t=Math.round(m*_/o)*o;g=s+t/_*d,f=l+t/_*p}r={cx:g+y*(i/2),cy:f+b*(i/2),angle:cl(180*Math.atan2(-y,b)/Math.PI),dist:v}}return r}function dl(t,e,i){const s=i;return{a:[t[0]/s,t[1]/s],b:[e[0]/s,e[1]/s]}}function pl(t,e){const i=e;return[t.a[0]*i,t.a[1]*i,t.b[0]*i,t.b[1]*i]}function ul(t){return Array.isArray(t)&&t.length>=2&&Number.isFinite(Number(t[0]))&&Number.isFinite(Number(t[1]))}function _l(t){if(!Array.isArray(t))return[];const e=[];for(const i of t){if(!i||"object"!=typeof i)continue;const t=i;if(!ul(t.a)||!ul(t.b))continue;const s=[Number(t.a[0]),Number(t.a[1])],n=[Number(t.b[0]),Number(t.b[1])];Math.hypot(n[0]-s[0],n[1]-s[1])<.001||e.push({a:s,b:n})}return e}function ml(t,e){const i=e[0],s=e[1],n=e[2]-i,o=e[3]-s,r=n*n+o*o;if(r<1e-18){return{q:[i,s],t:0,d:Math.hypot(t[0]-i,t[1]-s)}}let a=((t[0]-i)*n+(t[1]-s)*o)/r;a=Math.max(0,Math.min(1,a));const l=[i+a*n,s+a*o];return{q:l,t:a,d:Math.hypot(t[0]-l[0],t[1]-l[1])}}function gl(t,e){return ml(t,e).q}function fl(t,e,i,s,n){const o=ml(t,e).q;let r=o,a=1/0;for(const t of i){const e=Math.hypot(o[0]-t[0],o[1]-t[1]);e<=n&&e0?s:1,m=Math.round(u/_)*_,g=Math.max(0,Math.min(p,m))/p;return[l+h*g,c+d*g]}function vl(t,e){const i=[],s=(t||[]).filter(t=>t?.id);for(let t=0;tt?.id),r=_l(e);if(r.length)return Cl(r,t,i,s).map(t=>pl(t,i));if(!n)return[];const a=[],l=(t,e)=>(t.open_to||[]).includes(e.id)||(e.open_to||[]).includes(t.id);for(let t=0;tdl([t[0],t[1]],[t[2],t[3]],e))}function wl(t,e,i,s){for(const e of t||[])e.open_to&&delete e.open_to;if(!i.length)return;const n=(e||[]).filter(t=>t?.id);new Map(n.map(t=>[t.id,t]));const o=new Map((t||[]).filter(t=>t?.id).map(t=>[t.id,t])),r=(t,e)=>{const i=o.get(t),s=o.get(e);i&&s&&((i.open_to||[]).includes(e)||(i.open_to=[...i.open_to||[],e]),(s.open_to||[]).includes(t)||(s.open_to=[...s.open_to||[],t]))};for(let t=0;tSs(i,t)<4*s)){r(n[t].id,n[e].id);break}}}for(const e of t||[])e.open_to&&!e.open_to.length&&delete e.open_to}function kl(t,e,i,s=0){const n=e[2]-e[0],o=e[3]-e[1],r=Math.hypot(n,o);if(!(r>1e-9))return null;const a=n/r,l=o/r,c=(t[0]-e[0])*a+(t[1]-e[1])*l;if(c<-s||c>r+s)return null;return Math.abs((t[0]-e[0])*l-(t[1]-e[1])*a)>i?null:Ss(t,e)}function $l(t,e,i){const s=t[2]-t[0],n=t[3]-t[1],o=e[2]-e[0],r=e[3]-e[1],a=Math.hypot(s,n),l=Math.hypot(o,r);return a>1e-9&&l>1e-9&&(!(Math.abs(s/a*(r/l)-n/a*(o/l))>1e-6)&&(Ss([e[0],e[1]],t)<=4*i||Ss([t[0],t[1]],e)<=4*i))}function xl(t,e,i){if(!t.length)return null;t.sort((t,e)=>t.distance-e.distance);const s=t[0];for(let n=1;ne)break;if(o.semantic!==s.semantic||!$l(s.seg,o.seg,i))return"ambiguous"}return s}function Sl(t,e,i,s,n=1,o=15){const r=function(t,e,i,s,n=1,o=15){const[r,a]=ur([e[0],e[1]],[e[2],e[3]]);let l=0,c=1/0;const h=[(e[0]+e[2])/2,(e[1]+e[3])/2];for(const e of i){const[i,o]=ur([e[0],e[1]],[e[2],e[3]]);if(Math.abs(r*o-a*i)>.05)continue;const d=kr(t,[e[0],e[1]],[e[2],e[3]],s,n);if(!(d>0))continue;const p=[(e[0]+e[2])/2,(e[1]+e[3])/2],u=Math.hypot(p[0]-h[0],p[1]-h[1]);u0?l:o}(t,e,i,s,n,o);return Mr(t,[e[0],e[1]],[e[2],e[3]],r,s,n)}function Ml(t,e,i){const s=[[t[0],t[1]],[t[2],t[3]]],n=[(t[0]+t[2])/2,(t[1]+t[3])/2];for(const o of e)if(!(Ss(n,o)>Math.hypot(t[2]-t[0],t[3]-t[1])&&Ss([o[0],o[1]],t)>i))for(const e of[[o[0],o[1]],[o[2],o[3]]])Ss(e,t)<=2*i&&s.push(e);return s}function Cl(t,e,i,s){const n=_l(t);if(!n.length)return[];const o=vl(e,s);if(!o.length)return[];const r=[],a=Math.max(4*s,1e-6);for(const t of n){const e=pl(t,i),n=e[0],l=e[1],c=e[2]-n,h=e[3]-l,d=Math.hypot(c,h);if(d4*s||o>4*s)continue;const r=(t[0]-n)*p+(t[1]-l)*u,c=(t[2]-n)*p+(t[3]-l)*u,h=Math.max(0,Math.min(r,c)),m=Math.min(d,Math.max(r,c));if(m-ht.lo-e.lo||t.hi-e.hi);const i=[];for(const t of e){const e=i[i.length-1];e&&t.lo<=e.hi+a?e.hi=Math.max(e.hi,t.hi):i.push({...t})}for(const e of i){const i=[n+p*e.lo,l+u*e.lo],s=[n+p*e.hi,l+u*e.hi];Math.hypot(s[0]-i[0],s[1]-i[1])i.pair===t&&Math.abs(i.ux*r-i.uy*o)<=1e-6&&Math.abs((e[0]-i.origin[0])*i.uy-(e[1]-i.origin[1])*i.ux)<=c&&Math.abs((e[2]-i.origin[0])*i.uy-(e[3]-i.origin[1])*i.ux)<=c);h||(h={pair:t,origin:[e[0],e[1]],ux:o,uy:r,ranges:[]},l.push(h));const d=(e[0]-h.origin[0])*h.ux+(e[1]-h.origin[1])*h.uy,p=(e[2]-h.origin[0])*h.ux+(e[3]-h.origin[1])*h.uy;h.ranges.push({lo:Math.min(d,p),hi:Math.max(d,p)})}const h=[];for(const t of l){t.ranges.sort((t,e)=>t.lo-e.lo||t.hi-e.hi);const e=[];for(const i of t.ranges){const t=e[e.length-1];t&&i.lo<=t.hi+a?t.hi=Math.max(t.hi,i.hi):e.push({...i})}for(const s of e){const e=[t.origin[0]+t.ux*s.lo,t.origin[1]+t.uy*s.lo],n=[t.origin[0]+t.ux*s.hi,t.origin[1]+t.uy*s.hi];Math.hypot(n[0]-e[0],n[1]-e[1])>=a&&h.push(dl(e,n,i))}}return h}function Dl(t,e,i,s){const n=_l(t);if(!n.length)return[];if(e.length!==i.length)return n;const o=[];for(const t of n){const n=pl(t,s),r=[n[0],n[1]],a=[n[2],n[3]];let l=r,c=a;for(let t=0;t{const e=ml(t,[s[0],s[1],n[0],n[1]]);if(e.d>.001)return t;const i=h[0]-o[0],r=h[1]-o[1];return[o[0]+i*e.t,o[1]+r*e.t]};if(ml([(r[0]+a[0])/2,(r[1]+a[1])/2],[s[0],s[1],n[0],n[1]]).d<.01){l=d(r),c=d(a);break}}o.push(dl(l,c,s))}return o}const Tl=15e3,Rl=new WeakMap;class Pl{constructor(t,e=()=>Date.now()){this.onUpdate=t,this.now=e,this.fallbackAuthority={},this.referenceOwner={},this.referenced=new Set,this.queued=new Set,this.disposed=!1,this.sharedUpdate=()=>{this.disposed||this.onUpdate()}}bind(t){const e=((t,e)=>{const i=t?.connection||t;return!i||"object"!=typeof i&&"function"!=typeof i?e:i})(t,this.fallbackAuthority);return this.shared&&this.authority===e||(this.shared&&(this.shared.listeners.delete(this.sharedUpdate),this.shared.references.delete(this.referenceOwner)),this.authority=e,this.shared=(t=>{let e=Rl.get(t);return e||(e={signed:{},queued:new Set,inFlight:new Map,retry:new Map,listeners:new Set,references:new Map},Rl.set(t,e)),e})(e),this.disposed||(this.shared.listeners.add(this.sharedUpdate),this.shared.references.set(this.referenceOwner,new Set(this.referenced)))),this.shared}start(t,e){this.disposed=!1,this.referenced=new Set(e());const i=this.bind(t());i.listeners.add(this.sharedUpdate),i.references.set(this.referenceOwner,new Set(this.referenced)),this.stopTimer(),this.resignTimer=setInterval(()=>this.resign(t(),e()),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 t of this.queued)this.shared.queued.delete(t)}this.queued.clear()}stopTimer(){void 0!==this.resignTimer&&clearInterval(this.resignTimer),this.resignTimer=void 0}display(t,e){const i=ys(e);if(!i.startsWith("/api/houseplan/content/"))return i;const s=this.bind(t);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 e=[...this.queued];this.queued.clear();for(const t of e)i.queued.delete(t);this.sign(t,e)},30))}sign(t,e){if(!e.length||!t?.callWS)return;const i=this.bind(t);for(const s of function(t,e){const i=Math.max(1,Math.floor(e)),s=[];for(let e=0;e{const s=i.inFlight.get(t);return!(void 0!==s&&e-s{const e=this.now();let s=0,o=!1;for(const r of n){const n=t?.urls?.[r];if("string"==typeof n&&n){const t=i.signed[r];t?.loaded&&t.url!==n?(t.pending={url:n,at:e},this.preloadReplacement(i,r,n,e)):(i.signed[r]={url:n,at:e,loaded:t?.url===n&&!!t.loaded},o=!0),i.retry.delete(r),s++}else this.backOff(i,r)}if(s&&(this.trimShared(i),o))for(const t of[...i.listeners])t()}).catch(()=>{for(const t of n)this.backOff(i,t)}).finally(()=>{for(const t of n)i.inFlight.get(t)===e&&i.inFlight.delete(t)})}}preloadReplacement(t,e,i,s){const n=()=>{const n=t.signed[e];if(n?.pending?.url===i){t.signed[e]={url:i,at:s,loaded:!0},t.retry.delete(e),this.trimShared(t);for(const e of[...t.listeners])e()}},o=()=>{const s=t.signed[e];s?.pending?.url===i&&(delete s.pending,this.backOff(t,e))};if("undefined"==typeof Image)return void n();const r=new Image;r.onload=()=>{("function"==typeof r.decode?r.decode():Promise.resolve()).then(n).catch(o)},r.onerror=o,r.src=i}preloadCurrentImage(t,e,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,r=o=>{const r=t.signed[e];if(r===i&&delete r.preload,o&&r===i&&r.url===s){r.loaded=!0;for(const e of[...t.listeners])e();n(!0)}else o||r!==i||this.backOff(t,e),n(!1)};o.onload=()=>{("function"==typeof o.decode?o.decode():Promise.resolve()).then(()=>r(!0)).catch(()=>r(!1))},o.onerror=()=>r(!1),o.src=s}),i.preload}prepareImage(t,e){const i=ys(e);if(!i.startsWith("/api/houseplan/content/"))return Promise.resolve(!0);const s=this.bind(t),n=()=>{const e=s.signed[i];return e&&this.now()-e.at{let e=!1,i=null;const o=i=>{e||(e=!0,clearTimeout(a),s.listeners.delete(r),t(i))},r=()=>{e||i||(i=n(),i&&i.then(o).catch(()=>o(!1)))},a=setTimeout(()=>o(!1),Tl);s.listeners.add(r),r()})}backOff(t,e){const i=t.retry.get(e)?.delay||0,s=Math.min(6e4,i?2*i:2e3);t.retry.set(e,{notBefore:this.now()+s,delay:s})}trimShared(t){const e=Object.entries(t.signed);e.length<=96||(e.sort((t,e)=>e[1].at-t[1].at),t.signed=Object.fromEntries(e.slice(0,96)))}resign(t,e){const i=this.bind(t);this.referenced=new Set(e),i.references.set(this.referenceOwner,new Set(this.referenced));const s=this.now(),n=new Set;for(const t of i.references.values())for(const e of t)n.add(e);const o={};for(const[t,e]of Object.entries(i.signed))n.has(t)&&s-e.at!!o[t]&&!o[t].pending))}markLoaded(t,e){const i=ys(e);if(!i.startsWith("/api/houseplan/content/"))return;const s=this.bind(t).signed[i];if(s&&!s.loaded){s.loaded=!0;for(const e of[...this.bind(t).listeners])e()}}isReady(t,e){const i=ys(e);if(!i.startsWith("/api/houseplan/content/"))return!0;const s=this.bind(t).signed[i];return!!s&&this.now()-s.at{if(null==t||""===t)return null;const e=Number(t);return Number.isFinite(e)?e:null},El=t=>{const e=t,i=Il(Array.isArray(e)?e[0]:e?.x),s=Il(Array.isArray(e)?e[1]:e?.y);return null==i||null==s?null:[i,s]};function Fl(t){if(!Array.isArray(t))return null;const e=[];for(const i of t){const t=El(i);if(!t)return null;e.push(t)}if(e.length>1){const t=e[0],i=e[e.length-1];t[0]===i[0]&&t[1]===i[1]&&e.pop()}if(e.length<3)return null;let i=0,s=0,n=0;for(let t=0;tt+e[0],0)/e.length,e.reduce((t,e)=>t+e[1],0)/e.length]:[s/(3*i),n/(3*i)]}function Nl(t){if(!Array.isArray(t))return null;const e=[];for(const i of t){const t=El(i);if(!t)return null;e.push(t)}if(e.length>1){const t=e[0],i=e[e.length-1];t[0]===i[0]&&t[1]===i[1]&&e.pop()}const i=Fl(e);if(!i)return null;const s=e.map(t=>t[0]),n=e.map(t=>t[1]);return{centroid:i,bbox:[Math.min(...s),Math.min(...n),Math.max(...s),Math.max(...n)]}}function Ol(t){let e=t;if(e&&"object"==typeof e&&!Array.isArray(e)){const t=e;e=t.path??t.points??e}if(!Array.isArray(e)||!e.length)return[];let i=(e.some(t=>(t=>Array.isArray(t)?t.length>=2&&!Array.isArray(t[0])&&("object"!=typeof t[0]||null==t[0]):!!t&&"object"==typeof t&&("x"in t||"y"in t))(t))?[e]:e.filter(Array.isArray)).map(t=>t.map(El).filter(t=>null!=t)).filter(t=>t.length>=2);i.length>64&&(i=i.slice(-64));if(i.reduce((t,e)=>t+e.length,0)<=4e3)return i.map(t=>t.slice());const s=4e3-2*i.length,n=i.map(t=>t.length-2),o=n.reduce((t,e)=>t+e,0),r=n.map(t=>s*t/o),a=r.map(Math.floor);let l=s-a.reduce((t,e)=>t+e,0);const c=r.map((t,e)=>({index:e,fraction:t-Math.floor(t)})).sort((t,e)=>e.fraction-t.fraction||t.index-e.index);for(let t=0;tfunction(t,e){if(e>=t.length)return t.slice();if(e<=2)return[t[0],t[t.length-1]];const i=[];for(let s=0;st.slice()),i=e[e.length-1];return i?(i.length>2?i.pop():e.pop(),e):[]}function ql(t){if(!t)return null;const e=t.vacuum_position||t.robot_position||null,i=e&&null!=Il(e.x)&&null!=Il(e.y)?{x:Il(e.x),y:Il(e.y),a:Il(e.a??e.angle??e.theta)}:null,s=Ol(t.path?.path??t.path?.points??t.path),n=[],o=t.rooms,r=Array.isArray(o)?o.map((t,e)=>[String(t?.id??e),t]):o&&"object"==typeof o?Object.entries(o):[];for(const[t,e]of r){if(!e||"object"!=typeof e)continue;const i=String(e.name??e.label??"").trim(),s=[[Il(e.cx),Il(e.cy)],[Il(e.center?.x),Il(e.center?.y)],[Il(e.x),Il(e.y)]].find(([t,e])=>null!=t&&null!=e);let o=s?.[0]??null,r=s?.[1]??null;const a=Nl(e.outline),l=Il(e.x0),c=Il(e.y0),h=Il(e.x1),d=Il(e.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)||a?.bbox||null;if(null!=o&&null!=r||!a||([o,r]=a.centroid),null!=o&&null!=r||!p||(o=(p[0]+p[2])/2,r=(p[1]+p[3])/2),i&&null!=o&&null!=r){const e={id:t,name:i,cx:o,cy:r};p&&([e.x0,e.y0,e.x1,e.y1]=p),n.push(e)}}const a=function(t){return String(t.map_name??t.current_map??t.map_index??t.selected_map??"default")}(t);return i||n.length||s.length?{pos:i,path:s,rooms:n,mapId:a}:null}function Ul(t,e,i){if(!t||!e)return null;const s=e.attributes||{},n=t.split(".")[0]||"",o=null!=i?.platform?String(i.platform):null,r=!!El(s.vacuum_position??s.robot_position),a=!(!s.rooms||"object"!=typeof s.rooms),l=Ol(s.path?.path??s.path?.points??s.path).length>0,c=[s.map_name,s.current_map,s.map_index,s.selected_map].some(t=>null!=t),h="xiaomi_cloud_map_extractor"===o,d=a||l||c;let p=null;if(r?p="compatible":h?p="known_xcme_incomplete":d?p="partial":"camera"===n&&(p="camera"),!p)return null;const u=r?"camera"===n?300:200:h?100:d?50:0;return{entityId:t,name:String(s.friendly_name||t),platform:o,category:p,hasPosition:r,hasRooms:a,hasPath:l,hasMapId:c,score:u}}const Bl=t=>t.toLowerCase().replace(/[\s_\-.,]+/g,"");function Wl(t,e){const i=new Map(e.map(t=>[Bl(t.name),t])),s=[],n=[];for(const e of t){const t=i.get(Bl(e.name));t&&(s.push([[e.cx,e.cy],[t.cx,t.cy]]),n.push(e.name))}if(s.length<3)return null;const o=function(t){if(t.length<3)return null;let e=0,i=0,s=0,n=0,o=0,r=0,a=0,l=0,c=0,h=0,d=0,p=0;for(const[[u,_],[m,g]]of t){if(![u,_,m,g].every(Number.isFinite))return null;e+=u*u,i+=u*_,s+=u,n+=_*_,o+=_,r+=1,a+=u*m,l+=_*m,c+=m,h+=u*g,d+=_*g,p+=g}const u=[e,i,s,i,n,o,s,o,r],_=t=>{const[e,i,s,n,o,r,a,l,c]=u,h=e*(o*c-r*l)-i*(n*c-r*a)+s*(n*l-o*a);if(!Number.isFinite(h)||Math.abs(h)<1e-9)return null;const d=[(o*c-r*l)/h,(s*l-i*c)/h,(i*r-s*o)/h,(r*a-n*c)/h,(e*c-s*a)/h,(s*n-e*r)/h,(n*l-o*a)/h,(i*a-e*l)/h,(e*o-i*n)/h];return[d[0]*t[0]+d[1]*t[1]+d[2]*t[2],d[3]*t[0]+d[4]*t[1]+d[5]*t[2],d[6]*t[0]+d[7]*t[1]+d[8]*t[2]]},m=_([a,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:zl(o,s)}:null}function jl(t,e,i){const s=t[t.length-1];if(s&&s[0]===e[0]&&s[1]===e[1])return t;if(t.push(e),t.length<=600)return t;let n=function(t,e){if(t.length<3)return t.slice();const i=new Uint8Array(t.length);i[0]=i[t.length-1]=1;const s=[[0,t.length-1]];for(;s.length;){const[n,o]=s.pop(),[r,a]=t[n],[l,c]=t[o],h=l-r,d=c-a,p=Math.hypot(h,d)||1e-9;let u=0,_=-1;for(let e=n+1;eu&&(u=i,_=e)}_>0&&u>e&&(i[_]=1,s.push([n,_],[_,o]))}const n=[];for(let e=0;e600&&(n=n.filter((t,e)=>e%2==0||e===n.length-1)),n}function Gl(t){return"cleaning"===t||"returning"===t||"on"===t}const Vl={0:[1,0],90:[0,1],180:[-1,0],270:[0,-1]};function Kl(t){const[e,i]=Vl[t.rot]||[1,0],s=t.mir?-1:1;return[t.s*e*s,-t.s*i,t.ox,t.s*i*s,t.s*e,t.oy]}function Yl(t){const e=t[0]*t[4]-t[1]*t[3];if(!Number.isFinite(e)||Math.abs(e)<1e-12)return null;const i=e<0,s=Math.sqrt(Math.abs(e));let n=180*Math.atan2(-t[1],t[4])/Math.PI;return n=(90*Math.round(n/90)%360+360)%360,{ox:t[2],oy:t[5],s:s,rot:n,mir:i}}function Jl(t,e,i,s){const[n,o]=Al(Kl(e),i,s),r=Kl({...t,ox:0,oy:0}),[a,l]=Al(r,i,s);return{...t,ox:n-a,oy:o-l}}function Zl(t){const e=t?.trail_mode;return"never"===e||"cleaning"===e||"always"===e?e:!1===t?.trail?"never":"cleaning"}const Xl=1e-5;function Ql(t){const e=t.map(()=>[]);for(let i=0;i=1-1e-9||p<=1e-9||p>=1-1e-9||(e[i].push(d),e[r].push(p))}}const i=[];for(let s=0;st-e),1];for(let t=1;t{const i=e[2]-e[0],s=e[3]-e[1],n=i*i+s*s;if(!(n>0))return Math.hypot(t[0]-e[0],t[1]-e[1]);const o=Math.max(0,Math.min(1,((t[0]-e[0])*i+(t[1]-e[1])*s)/n));return Math.hypot(t[0]-(e[0]+o*i),t[1]-(e[1]+o*s))},ic=(t,e,i,s)=>{const n=s[2]-s[0],o=s[3]-s[1],r=e*o-i*n;if(Math.abs(r)<1e-12)return 1/0;const a=s[0]-t[0],l=s[1]-t[1],c=(a*o-l*n)/r;if(!(c>1e-9))return 1/0;const h=(a*i-l*e)/r;return h<-1e-9||h>1+1e-9?1/0:c};function sc(t,e,i,s=96){if(!(e>0&&Number.isFinite(t[0])&&Number.isFinite(t[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=ec(t,s);if(i<1e-7)return[];i>e||n.push(s)}const o=[],r=Math.max(12,Math.round(s));for(let t=0;tt-e);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 r=e;for(const e of n){const i=ic(t,s,o,e);i=3?l:[]}class nc extends ct{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return co(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[],e=this._lang;return[{name:"title",selector:{text:{}}},t.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:t}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:[{value:"",label:ho(e,"editor.lang_auto")},{value:"en",label:ho(e,"editor.lang_en")},{value:"ru",label:ho(e,"editor.lang_ru")}]}}},{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 t=this._lang,e={title:ho(t,"editor.title"),default_floor:ho(t,"editor.default_floor"),language:ho(t,"editor.language"),icon_size:ho(t,"editor.icon_size"),show_temperature:ho(t,"editor.show_temperature"),live_states:ho(t,"editor.live_states"),show_signal:ho(t,"editor.show_signal"),kiosk:ho(t,"editor.kiosk"),cycle:ho(t,"editor.cycle")};return B`e[t.name]||t.name} @value-changed=${this._valueChanged} - >`}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}function oc(t){const e=Ca(t.cfg),i={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(i[t.area]=e.id);const s=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):Et,o=Ft(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:Nt);return Fo({hass:t.hass,registry:t.registry,areaToSpace:i,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:s,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>dn(t.lang,e),iconRules:o})}function nc(t){const e=Ca(t.cfg).find(e=>e.id===t.spaceId);if(!e)return null;const i=Yi(t.cfg.spaces.find(e=>e.id===t.spaceId)),s=Xi(t.cfg.settings),o=t.iconSize??2.5,n=o>8?2.5:o,r=t.registry?no(t.hass,t.registry):t.hass,a=(t.devices||oc(t)).filter(e=>e.space===t.spaceId),l=a.filter(t=>!t.hidden),c=function(t,e,i){const s={},o=i/100*Ka(e)*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const n=Za(i),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,d=l/Math.max(Math.ceil(e.length/c),1),p=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));ki(p,n,o,.5*r),e.forEach((t,e)=>s[t.id]=Ia(p[e]))}return s}(a,e,n),h=[];for(const e of l){const i=t.layout[e.id];if(i&&i.s===t.spaceId){const t=i.x*wa,e=i.y*wa;h.push({minX:t,minY:e,maxX:t,maxY:e})}}const d=t.cfg.spaces.find(e=>e.id===t.spaceId)||{},p=Array.isArray(d.walls)?d.walls:[],u=Number(d.cell_cm)>0?Number(d.cell_cm):5,_=function(t,e,i){const s=[];for(const o of t.partitions||[]){const t=ca(o.a,o.b,o.cm,e,i);t&&s.push(t)}for(const o of t.room_drafts||[])s.push(...da(o,e,i));for(const o of t.wall_columns||[])s.push(ha(o,e,i));return s}(e,u,Ra);for(const t of _){const e=t.map(t=>t[0]),i=t.map(t=>t[1]);e.length&&h.push({minX:Math.min(...e),minY:Math.min(...i),maxX:Math.max(...e),maxY:Math.max(...i)})}const m=Wa(e,h),g=[m.x,m.y,m.w,m.h],f=new Map(e.rooms.map(t=>{const e=bs(i.fill,t);return[t,ts(e,"lqi"===e&&t.area?Ho(r,a,t.area):null,"light"===e?Mo(xo(r,a,t)):"none","temp"===e&&t.area?qo(r,a,t.area):null,i.tempMin,i.tempMax,s,Ki(i.customFill,t))]})),v=e.rooms.filter(t=>t.area||i.showBorders||"none"!==bs(i.fill,t)).map(t=>{let s="room "+(e.bg?"overlay":"yard"),o="";const n=bs(i.fill,t);if(i.showBorders||"none"!==n){s+=" styled";const e=[`--room-stroke:${i.color}`,`--room-stroke-op:${i.showBorders?i.opacity:0}`],n=f.get(t)||null;n?(s+=" filled",e.push(`--room-fill:${n.color}`,`--room-fill-op:${n.opacity.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),o=e.join(";")}const r=t.id||W,a=t.area||W,l=t.poly?j``:j``;return l}),y=e.rooms.filter(t=>{const e=f.get(t);return ws(i.glow,t)&&(!e||e.opacity<=0)}).map(t=>t.poly?j``}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}function oc(t){const e=Ma(t.cfg),i={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(i[t.area]=e.id);const s=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):Et,n=Nt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:Ft);return Nn({hass:t.hass,registry:t.registry,areaToSpace:i,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:s,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>ho(t.lang,e),iconRules:n})}function rc(t){const e=Ma(t.cfg).find(e=>e.id===t.spaceId);if(!e)return null;const i=Yi(t.cfg.spaces.find(e=>e.id===t.spaceId)),s=Xi(t.cfg.settings),n=t.iconSize??2.5,o=n>8?2.5:n,r=t.registry?rn(t.hass,t.registry):t.hass,a=(t.devices||oc(t)).filter(e=>e.space===t.spaceId),l=a.filter(t=>!t.hidden),c=function(t,e,i){const s={},n=i/100*Va(e)*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const o=Ja(i),r=.1*Math.min(o.w,o.h),a=o.w-2*r,l=o.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,d=l/Math.max(Math.ceil(e.length/c),1),p=e.map((t,e)=>({x:o.x+r+h*(e%c+.5),y:o.y+r+d*(Math.floor(e/c)+.5)}));ki(p,o,n,.5*r),e.forEach((t,e)=>s[t.id]=za(p[e]))}return s}(a,e,o),h=[];for(const e of l){const i=t.layout[e.id];if(i&&i.s===t.spaceId){const t=i.x*ba,e=i.y*ba;h.push({minX:t,minY:e,maxX:t,maxY:e})}}const d=t.cfg.spaces.find(e=>e.id===t.spaceId)||{},p=Array.isArray(d.walls)?d.walls:[],u=Number(d.cell_cm)>0?Number(d.cell_cm):5,_=function(t,e,i){const s=[];for(const n of t.partitions||[]){const t=ca(n.a,n.b,n.cm,e,i);t&&s.push(t)}for(const n of t.room_drafts||[])s.push(...da(n,e,i));for(const n of t.wall_columns||[])s.push(ha(n,e,i));return s}(e,u,Ra);for(const t of _){const e=t.map(t=>t[0]),i=t.map(t=>t[1]);e.length&&h.push({minX:Math.min(...e),minY:Math.min(...i),maxX:Math.max(...e),maxY:Math.max(...i)})}const m=ja(e,h),g=[m.x,m.y,m.w,m.h],f=new Map(e.rooms.map(t=>{const e=bs(i.fill,t);return[t,ts(e,"lqi"===e&&t.area?Hn(r,a,t.area):null,"light"===e?Mn(xn(r,a,t)):"none","temp"===e&&t.area?qn(r,a,t.area):null,i.tempMin,i.tempMax,s,Ki(i.customFill,t))]})),v=e.rooms.filter(t=>t.area||i.showBorders||"none"!==bs(i.fill,t)).map(t=>{let s="room "+(e.bg?"overlay":"yard"),n="";const o=bs(i.fill,t);if(i.showBorders||"none"!==o){s+=" styled";const e=[`--room-stroke:${i.color}`,`--room-stroke-op:${i.showBorders?i.opacity:0}`],o=f.get(t)||null;o?(s+=" filled",e.push(`--room-fill:${o.color}`,`--room-fill-op:${o.opacity.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),n=e.join(";")}const r=t.id||G,a=t.area||G,l=t.poly?W``:W``;return l}),y=e.rooms.filter(t=>{const e=f.get(t);return ws(i.glow,t)&&(!e||e.opacity<=0)}).map(t=>t.poly?W``:j``:G}`}if("ellipse"===n.kind){const i=(n.x+n.w/2)*t,h=(n.y+n.h/2)*e,d=Zo(n.angle);return W` - ${s?j``:W}`}if("furniture"===o.kind){const i=o.w*t,h=o.h*e,d=cl(o.symbol,i,h);if(!d)return W;const p=Number(o.angle)||0,u=o.x*t+i/2,_=o.y*e+h/2,m=`${p?`rotate(${p} ${u} ${_}) `:""}translate(${o.x*t} ${o.y*e})`;return j``:G}`}if("furniture"===n.kind){const i=n.w*t,h=n.h*e,d=ll(n.symbol,i,h);if(!d)return G;const p=Number(n.angle)||0,u=n.x*t+i/2,_=n.y*e+h/2,m=`${p?`rotate(${p} ${u} ${_}) `:""}translate(${n.x*t} ${n.y*e})`;return W` - ${s?j``:W}`}if("text"===o.kind){const i=this._decorTextUnits(o),s=function(t){return String(t??"").replace(/\r\n?/g,"\n").split("\n")}(function(t,e,i,s=()=>!0){const o=t??"";let n=!1;const r=o.replace(/\{([^{}\r\n]+)\}/g,(t,e)=>{const o=Ni(e);return o?(n=!0,s(o.entity||"")?Hi(i,o):Ii):t});if(n)return r;const a=(e?.entity||"").trim();if(!a)return o;if(!s(a)){const t=o.indexOf(Ei);return t>=0?o.slice(0,t)+Ii+o.slice(t+2):o?`${o} ${Ii}`:Ii}const l=Hi(i,e),c=o.indexOf(Ei);return c>=0?o.slice(0,c)+l+o.slice(c+2):o?`${o} ${l}`:l}(o.text,o,this.hass,t=>this._planEntityAvailable(t))),a=o.x*t,h=o.y*e,d=Number(o.angle)||0,p=h-(s.length-1)*i*1.2/2;return j``:G}`}if("text"===n.kind){const i=this._decorTextUnits(n),s=function(t){return String(t??"").replace(/\r\n?/g,"\n").split("\n")}(function(t,e,i,s=()=>!0){const n=t??"";let o=!1;const r=n.replace(/\{([^{}\r\n]+)\}/g,(t,e)=>{const n=Fi(e);return n?(o=!0,s(n.entity||"")?Hi(i,n):Ii):t});if(o)return r;const a=(e?.entity||"").trim();if(!a)return n;if(!s(a)){const t=n.indexOf(Ei);return t>=0?n.slice(0,t)+Ii+n.slice(t+2):n?`${n} ${Ii}`:Ii}const l=Hi(i,e),c=n.indexOf(Ei);return c>=0?n.slice(0,c)+l+n.slice(c+2):n?`${n} ${l}`:l}(n.text,n,this.hass,t=>this._planEntityAvailable(t))),a=n.x*t,h=n.y*e,d=Number(n.angle)||0,p=h-(s.length-1)*i*1.2/2;return W`${s.map((t,e)=>j`${t}`)}`}return W});let n=W;const r=this._decorDraft;if(r){const t=this._decorStyle,e=Qn(t.widthCm,this._cellCm,this._gridPitch);if("line"===r.kind)n=j``;else{const i=Math.min(r.a[0],r.b[0]),s=Math.min(r.a[1],r.b[1]),o=Math.abs(r.b[0]-r.a[0]),a=Math.abs(r.b[1]-r.a[1]);n="rect"===r.kind?j``:j``}}return j`${o}${n}`}get _editorToolbarGroups(){return[]}_renderEditorGroupLauncher(t){return this._editorSecondary.renderGroupLauncher(t,this._editorToolbarGroups,this._editorSecondaryCopy)}get _editorSecondaryContextId(){const t=`editor:${this._mode}:${this._space}:${this._cfgEpoch}`,e=this._editorSecondary.activeGroup(this._editorToolbarGroups);if(e)return`${t}:group:${e.id}:${this._editorSecondary.groupGeneration}`;if("plan"===this._mode){const e=this._physicalSel;return e?`${t}:selection:${e.kind}:${e.id}:${e.segment??""}`:`${t}:tool:${this._tool}:${this._path.length}:${this._openWallAnchor?1:0}`}return"decor"===this._mode?"furniture"===this._decorTool?`${t}:palette:furniture:${this._furnPalette?.symbol||"none"}`:"select"===this._decorTool&&this._decorSel?`${t}:selection:decor:${this._decorSel}`:`${t}:tool:${this._decorTool}:${this._bdMoved?1:0}:${this._bdDrag?1:0}`:`${t}:none`}_runEditorContext(t,e){this._editorSecondary.runContext(t,this._editorSecondaryContextId,e)}_renderEditorGroupModel(t){return this._editorSecondary.renderGroupModel(t,this._editorSecondaryContextId,this._editorSecondaryCopy)}_renderDrawWallControl(){return B``:G} + `:G} ${this._kiosk&&this._kioskDots&&this._model.length>1?B`
${this._model.map(t=>B``)} -
`:W} - ${this._kioskDialog?this._renderKioskDialog():W} + `:G} + ${this._kioskDialog?this._renderKioskDialog():G} ${this._vacFit?B`
${this._t("vac.fit_hint")} -
`:W} + `:G} ${this._tapConfirm?B`this._tapConfirm=null}>

${this._tapConfirm.text}

@@ -4681,30 +4751,30 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 ${this._t("btn.run")} -
`:W} - ${this._toast?B``:W} + `:G} + ${this._toast?B``:G} - `}_vacCandidateStatus(t,e){const i=this._bindingStatus("entity:"+t);if("ha_disabled"===i.kind)return"disabled";if("orphaned"===i.kind)return"missing";if("unverified"===i.kind)return"unverified";const s=this.hass?.states?.[t];return"unavailable"===s?.state?"unavailable":e?.hasPosition?"ok":"unsupported"}_vacOpenAllCameras(t){const e=this._haRegistry.entities||{},i=[];for(const[t,s]of Object.entries(this.hass?.states||{})){if(!t.startsWith("camera."))continue;const o=ql(t,s,e[t]);o&&i.push(o)}this._vacAllCameraCache={devId:t.id,candidates:i},this._vacAllCamerasFor=t.id}_vacSourceResolution(t,e=!1){const i=t.marker?.vacuum,s="string"==typeof i?.source&&!!i.source,o=new Set(t.entities||[]),n=new Set(o);s&&n.add(i.source);const r=this._haRegistry.entities||{},a=[];for(const t of n){const e=this.hass?.states?.[t],o=ql(t,e,r[t]);o?a.push(o):s&&t===i.source&&a.push({entityId:t,name:String(e?.attributes?.friendly_name||t),platform:r[t]?.platform?String(r[t].platform):null,category:t.startsWith("camera.")?"camera":"partial",hasPosition:!1,hasRooms:!1,hasPath:!1,hasMapId:!1,score:0})}if(e&&this._vacAllCameraCache?.devId===t.id){const t=new Set(a.map(t=>t.entityId));for(const e of this._vacAllCameraCache.candidates)t.has(e.entityId)||a.push(e)}const l={};for(const t of a)l[t.entityId]=this._vacCandidateStatus(t.entityId,t);return function(t,e,i,s){const o="string"==typeof t&&t.length>0,n=new Set(e),r=Array.from(i).sort((e,i)=>o&&e.entityId===t?-1:o&&i.entityId===t?1:i.score-e.score||e.entityId.localeCompare(i.entityId));if(o){const e=r.find(e=>e.entityId===t);return{entityId:t,status:s[t]||(e?.hasPosition?"ok":"unverified"),pinned:!0,candidates:r}}const a=r.find(t=>n.has(t.entityId)&&t.hasPosition&&"ok"===s[t.entityId]);return a?{entityId:a.entityId,status:"ok",pinned:!1,candidates:r}:{entityId:null,status:"none",pinned:!1,candidates:r}}(i?.source,o,a,l)}_vacSource(t){if(!1===t.marker?.vacuum?.live)return null;const e=this._vacSourceResolution(t);return"ok"===e.status||"unsupported"===e.status?e.entityId:null}_vacEntity(t){return t.primary?.startsWith("vacuum.")?t.primary:(t.entities||[]).find(t=>t.startsWith("vacuum."))||null}_isVacDev(t){return!!this._vacEntity(t)}_activitySourceKey(t){return Yo(this._planHass,t,!1!==this._config?.show_temperature)}_activitySnapshot(t){const e=Wo(this._planHass,t);return{samples:e.samples,sourceKey:Yo(this._planHass,t,!1!==this._config?.show_temperature,e)}}_stampActivity(t,e,i){let s=this._activityRt.get(t);s||(s={sources:i||"",last:{},flashTs:0,flashKind:null,timer:0,gen:0},this._activityRt.set(t,s)),null!=i&&(s.sources=i),s.flashTs&&Date.now()-s.flashTs<3300&&"event"===s.flashKind&&"transition"===e||(s.flashTs=Date.now(),s.flashKind=e,s.gen++,clearTimeout(s.timer),s.timer=window.setTimeout(()=>this.requestUpdate(),3360))}_syncActivityRuntime(){const t=new Map;if(!this.hass)return t;const e=new Set;for(const i of this._devices){if(i.hidden)continue;if("static_icon"===Si(i.marker?.display))continue;e.add(i.id);const s=this._activitySnapshot(i);t.set(i.id,s);const{samples:o,sourceKey:n}=s;let r=this._activityRt.get(i.id);if(r){if(r.sources!==n){clearTimeout(r.timer),r.sources=n,r.last={},r.flashTs=0,r.flashKind=null;for(const t of o)r.last[t.eid]=t.state}}else{r={sources:n,last:{},flashTs:0,flashKind:null,timer:0,gen:0};for(const t of o)r.last[t.eid]=t.state;this._activityRt.set(i.id,r)}}for(const[t,i]of this._activityRt)e.has(t)||(clearTimeout(i.timer),this._activityRt.delete(t));return t}_activityTick(){if(!this.hass)return;const t=this._syncActivityRuntime();for(const e of this._devices){if(e.hidden)continue;if("static_icon"===Si(e.marker?.display))continue;const i=t.get(e.id)||this._activitySnapshot(e),{samples:s,sourceKey:o}=i,n=this._activityRt.get(e.id);if(!n||n.sources!==o)continue;"transition"===n.flashKind&&s.some(t=>"transition"===t.activity)&&(clearTimeout(n.timer),n.flashTs=0,n.flashKind=null);let r=null;for(const t of s){const e=Bs(n.last[t.eid],t);("event"===e||!r&&e)&&(r=e),n.last[t.eid]=t.state}r&&this._stampActivity(e.id,r,o)}}_vacTick(){if(this.hass)for(const t of this._devices){if(t.hidden||!this._isVacDev(t))continue;if("static_icon"===Si(t.marker?.display)){this._vacRt.delete(t.id);continue}const e=this._vacSource(t);if(!e)continue;const i=this._vacEntity(t),s=Gl(this.hass.states[i||""]?.state),o=Hl(this.hass.states[e]?.attributes);let n=this._vacRt.get(t.id);n||(n={trail:[],lastKey:"",lastTs:0,moving:!1,jump:!1,endedTs:0,lastPos:null},this._vacRt.set(t.id,n)),s&&!n.moving&&(n.trail=[],n.lastPos=null);const r="never"!==Jl(t.marker?.vacuum)&&!o?.path.length;!s&&n.moving&&(n.endedTs=Date.now(),r&&n.lastPos&&(n.trail=jl(n.trail,n.lastPos,40)),n.lastPos=null),n.moving=s;const a=o?.pos;if(s&&a){const t=a.x+":"+a.y;if(t!==n.lastKey){const e=Date.now();n.jump=n.lastTs>0&&e-n.lastTs>1e4,n.lastKey=t,n.lastTs=e,r&&n.lastPos&&(n.trail=jl(n.trail,n.lastPos,40)),n.lastPos=[a.x,a.y]}}}}_vacEnsureMarker(t){const e=this._serverCfg;if(!e)return null;e.markers=e.markers||[];const i=e.markers.find(e=>e.id===t.id);if(i)return i;if("device"!==t.bindingKind&&"entity"!==t.bindingKind||!t.bindingRef)return null;const s={id:t.id,binding:t.bindingKind+":"+t.bindingRef,space:t.space||null,area:t.area||null,hidden:!!t.hidden};return e.markers.push(s),s}_renderVacSection(t){const e=this._devices.find(e=>e.id===t.devId);if(!e||!this._isVacDev(e))return W;const i=e.marker?.vacuum||{},s=this._vacAllCamerasFor===e.id,o=this._vacSourceResolution(e,s),n=o.entityId,r=o.candidates.find(t=>t.entityId===n)||null,a=n?Hl(this.hass?.states[n]?.attributes):null,l=!1!==i.live&&("ok"===o.status||"unsupported"===o.status),c=this._vacPlanRoomAnchors(e.space),h=a?function(t,e){const i=new Set(e.map(Ul).filter(Boolean)),s=new Set;for(const e of t){const t=Ul(e.name||"");t&&i.has(t)&&s.add(t)}return s.size}(a.rooms,c.map(t=>t.name)):0,d=!!(l&&a&&h>=3),p=Object.keys(i.calibration||{}),u=t=>{const i=this._vacEnsureMarker(e);i&&(i.vacuum={...i.vacuum||{},...t},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate())},_=new Set(e.entities||[]),m=o.candidates.filter(t=>_.has(t.entityId)||t.entityId===i.source),g=s?o.candidates.filter(t=>t.entityId.startsWith("camera.")&&!m.some(e=>e.entityId===t.entityId)):[],f=t=>[t.hasPosition?this._t("vac.cap_position"):"",t.hasRooms?this._t("vac.cap_rooms_short"):"",t.hasPath?this._t("vac.cap_path"):"",t.hasMapId?this._t("vac.cap_map"):""].filter(Boolean).join(" · ")||this._t("vac.cap_none"),v=t=>B` - `,y=o.candidates.some(t=>_.has(t.entityId)&&"known_xcme_incomplete"===t.category)||!!o.pinned&&!!n?.startsWith("camera.")&&!r?.hasPosition;return B` + `,y=n.candidates.some(t=>_.has(t.entityId)&&"known_xcme_incomplete"===t.category)||!!n.pinned&&!!o?.startsWith("camera.")&&!r?.hasPosition;return B`
-
${this._t("vac.diag_source")}${n||this._t("vac.source_none")}
- ${r?.platform?B`
${this._t("vac.diag_platform")}${r.platform}
`:W} -
${this._t("vac.diag_status")}${this._t(`vac.source_status_${o.status}`)}
+
${this._t("vac.diag_source")}${o||this._t("vac.source_none")}
+ ${r?.platform?B`
${this._t("vac.diag_platform")}${r.platform}
`:G} +
${this._t("vac.diag_status")}${this._t(`vac.source_status_${n.status}`)}
${this._t("vac.diag_position")}${a?.pos?this._t("common.yes"):this._t("common.no")}
${this._t("vac.diag_rooms")}${Bi(this._t("vac.diag_rooms_value"),{total:String(a?.rooms.length||0),matched:String(h),readiness:this._t(h>=3?"vac.autocal_ready":"vac.autocal_not_ready")})}
${this._t("vac.diag_path")}${a?.path.length?this._t("common.yes"):this._t("common.no")}
${this._t("vac.diag_map")}${a?.mapId??"default"}
- ${"missing"===o.status||"disabled"===o.status||"unverified"===o.status?B`
- ${this._t(`vac.source_banner_${o.status}`)} + ${"missing"===n.status||"disabled"===n.status||"unverified"===n.status?B`
+ ${this._t(`vac.source_banner_${n.status}`)} -
`:W} +
`:G} ${y?B`
${this._t("vac.xcme_hint")}
attributes:
@@ -4712,11 +4782,11 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
   - rooms
   - path
   - map_name
-
`:W} +
`:G}
${this._t("vac.choose_source")}
- @@ -4725,13 +4795,13 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 @toggle=${t=>{t.currentTarget.open?this._vacOpenAllCameras(e):(this._vacAllCamerasFor=null,this._vacAllCameraCache=null)}}> ${this._t("vac.all_cameras")}
${this._t("vac.all_cameras_warn")}
- ${s?g.length?g.map(v):B`
${this._t("vac.all_cameras_empty")}
`:W} + ${s?g.length?g.map(v):B`
${this._t("vac.all_cameras_empty")}
`:G}
@@ -4744,35 +4814,35 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 - ${p.length?B`
${Bi(this._t("vac.cal_maps"),{maps:p.join(", ")})}
`:W} - `:W} - `}_vacMapId(t,e){const i=this._vacEntity(t),s=i?this.hass?.states[i]?.attributes?.selected_map:null;return o=e.mapId,n=s,"default"!==o?o:null!=n?String(n):"default";var o,n}_vacSaveMatrix(t,e,i,s){const o=this._devices.find(e=>e.id===t),n=o?this._vacEnsureMarker(o):this._serverCfg?.markers?.find(e=>e.id===t);if(!n)return!1;const r={...n.vacuum||{}};return r.source=e,r.calibration={...r.calibration||{},[i]:s.map(t=>Number(t.toFixed(6)))},n.vacuum=r,this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate(),!0}_vacPlanRoomAnchors(t){return(this._spaceModel(t||void 0)?.rooms||[]).map(t=>({room:t,poly:Je(t)})).filter(({room:t,poly:e})=>t.name&&e).map(({room:t,poly:e})=>{const i=El(e);return i?{name:String(t.name),cx:i[0],cy:i[1]}:null}).filter(Boolean)}_vacAutoCalibrate(t){const e=this._vacSource(t),i=e?Hl(this.hass?.states[e]?.attributes):null;if(!e||!i||i.rooms.length<3)return void this._showToast(this._t("vac.autocal_no_rooms"));const s=this._vacPlanRoomAnchors(t.space),o=Bl(i.rooms,s);if(!o)return void this._showToast(this._t("vac.autocal_no_match"));const n=this._serverCfg?.spaces?.find(e=>e.id===t.space),r=Number(n?.cell_cm),a=Number.isFinite(r)&&r>0?r:5,l=function(t,e,i){return![t,e,i].every(Number.isFinite)||t<0||e<=0||i<=0?Number.POSITIVE_INFINITY:t/e*i}(o.residual,this._gridPitch,a),c=this._vacMapId(t,i);l>40?this._vacCalConfirm={markerId:t.id,source:e,mapId:c,matrix:o.matrix,rooms:o.matched.length,error:Ke(l,"mi"===this.hass?.config?.unit_system?.length)}:this._vacSaveMatrix(t.id,e,c,o.matrix)&&this._showToast(Bi(this._t("vac.autocal_done"),{rooms:String(o.matched.length)}))}_vacApplyCalibrationProposal(t){const e=this._vacCalConfirm;if(e){if(this._vacCalConfirm=null,t){const t=this._devices.find(t=>t.id===e.markerId),i=Kl(e.matrix);if(!t||!i)return;return this._markerDialog=null,t.space!==this._space&&(this._space=t.space),void(this._vacFit={markerId:e.markerId,source:e.source,mapId:e.mapId,p:i,drag:null})}this._vacSaveMatrix(e.markerId,e.source,e.mapId,e.matrix)&&this._showToast(Bi(this._t("vac.autocal_done"),{rooms:String(e.rooms)}))}}_vacStartFit(t){const e=this._vacSource(t),i=e?Hl(this.hass?.states[e]?.attributes):null;if(!e||!i)return void this._showToast(this._t("vac.cal_need_pos"));const s=this._vacMapId(t,i),o=t.marker?.vacuum?.calibration?.[s],n=this._spaceModel(t.space),r=n?.vb||[0,0,eh,eh],a=o&&6===o.length&&Kl(o)||function(t,e){const i=[],s=[];for(const e of t)null!=e.x0?(i.push(e.x0,e.x1),s.push(e.y0,e.y1)):(i.push(e.cx),s.push(e.cy));if(!i.length)return{ox:e[0]+e[2]/2,oy:e[1]+e[3]/2,s:e[2]/1e4,rot:0,mir:!0};const o=Math.min(...i),n=Math.max(...i),r=Math.min(...s),a=Math.max(...s),l=Math.max(n-o,a-r)||1,c={ox:0,oy:0,s:.6*Math.min(e[2],e[3])/l,rot:0,mir:!0},h=Vl(c),[d,p]=Rl(h,(o+n)/2,(r+a)/2);return c.ox=e[0]+e[2]/2-d,c.oy=e[1]+e[3]/2-p,c}(i.rooms,r);this._markerDialog=null,t.space!==this._space&&(this._space=t.space),this._vacFit={markerId:t.id,source:e,mapId:s,p:a,drag:null}}_vacFitSave(){const t=this._vacFit;if(!t)return;const e=this._vacSaveMatrix(t.markerId,t.source,t.mapId,Vl(t.p));this._vacFit=null,e&&this._showToast(this._t("vac.cal_done"))}_vacFitTurn(t){const e=this._vacFit;if(!e)return;const i=Hl(this.hass?.states[e.source]?.attributes),s=this._vacGhostCentre(i?.rooms||[]),o={...e.p,...t};this._vacFit={...e,p:Yl(o,e.p,s[0],s[1])}}_vacGhostCentre(t){const e=[],i=[];for(const s of t)e.push(s.x0??s.cx,s.x1??s.cx),i.push(s.y0??s.cy,s.y1??s.cy);return e.length?[(Math.min(...e)+Math.max(...e))/2,(Math.min(...i)+Math.max(...i))/2]:[0,0]}_vacDelta(t,e,i){const s=this._stageEl,o=s?.clientWidth||1,n=s?.clientHeight||1;return[e/o*t.w,i/n*t.h]}_vacFitPointer(t,e){const i=this._vacFit;if(!i)return;if(t.stopPropagation(),"pointerdown"===t.type){const e=t.target,s=e.getAttribute?.("data-corner");try{t.currentTarget.setPointerCapture?.(t.pointerId)}catch{}return void(this._vacFit={...i,drag:s?{kind:"scale",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:Number(s.split(",")[0]),fy:Number(s.split(",")[1])}:{kind:"move",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:0,fy:0}})}const s=i.drag;if(s){if("pointermove"===t.type){const[o,n]=this._vacDelta(e,t.clientX-s.sx,t.clientY-s.sy);if("move"===s.kind)this._vacFit={...i,p:{...s.p0,ox:s.p0.ox+o,oy:s.p0.oy+n}};else{const t=Hl(this.hass?.states[i.source]?.attributes),e=this._vacGhostCentre(t?.rooms||[]),r=Vl(s.p0),[a,l]=Rl(r,e[0],e[1]),[c,h]=Rl(r,s.fx,s.fy),d=Math.hypot(a-c,l-h)||1,[p,u]=[2*a-c,2*l-h],_=Math.hypot(p+2*o-c,u+2*n-h)/2,m=Math.max(.05,_/d),g={...s.p0,s:s.p0.s*m};this._vacFit={...i,p:Yl(g,s.p0,s.fx,s.fy)}}return}"pointerup"!==t.type&&"pointercancel"!==t.type||(this._vacFit={...i,drag:null})}}_renderVacFit(t){const e=this._vacFit;if(!e)return W;const i=Hl(this.hass?.states[e.source]?.attributes);if(!i)return W;const s=Vl(e.p),o=[],n=[],r=[];for(const t of i.rooms){if(null==t.x0)continue;const e=[[t.x0,t.y0],[t.x1,t.y0],[t.x1,t.y1],[t.x0,t.y1]].map(([t,e])=>Rl(s,t,e));e.forEach(([t,e])=>{n.push(t),r.push(e)});const[i,a]=Rl(s,t.cx,t.cy);o.push(j` - ${t.name}`)}let a=W;if(i.pos){const[e,o]=Rl(s,i.pos.x,i.pos.y);a=j``}const l=[];if(n.length){const e=(()=>{const t=s[0]*s[4]-s[1]*s[3];return(e,i)=>[(s[4]*(e-s[2])-s[1]*(i-s[5]))/t,(-s[3]*(e-s[2])+s[0]*(i-s[5]))/t]})(),i=Math.min(...n),o=Math.max(...n),a=Math.min(...r),c=Math.max(...r),h=.022*t.w,d=h/4;for(const[t,s,n,r]of[[i,a,o,c],[o,a,i,c],[o,c,i,a],[i,c,o,a]]){const i=e(n,r);l.push(j`${Bi(this._t("vac.cal_maps"),{maps:p.join(", ")})}`:G} + `:G} + `}_vacMapId(t,e){const i=this._vacEntity(t),s=i?this.hass?.states[i]?.attributes?.selected_map:null;return n=e.mapId,o=s,"default"!==n?n:null!=o?String(o):"default";var n,o}_vacSaveMatrix(t,e,i,s){const n=this._devices.find(e=>e.id===t),o=n?this._vacEnsureMarker(n):this._serverCfg?.markers?.find(e=>e.id===t);if(!o)return!1;const r={...o.vacuum||{}};return r.source=e,r.calibration={...r.calibration||{},[i]:s.map(t=>Number(t.toFixed(6)))},o.vacuum=r,this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate(),!0}_vacPlanRoomAnchors(t){return(this._spaceModel(t||void 0)?.rooms||[]).map(t=>({room:t,poly:Je(t)})).filter(({room:t,poly:e})=>t.name&&e).map(({room:t,poly:e})=>{const i=Fl(e);return i?{name:String(t.name),cx:i[0],cy:i[1]}:null}).filter(Boolean)}_vacAutoCalibrate(t){const e=this._vacSource(t),i=e?ql(this.hass?.states[e]?.attributes):null;if(!e||!i||i.rooms.length<3)return void this._showToast(this._t("vac.autocal_no_rooms"));const s=this._vacPlanRoomAnchors(t.space),n=Wl(i.rooms,s);if(!n)return void this._showToast(this._t("vac.autocal_no_match"));const o=this._serverCfg?.spaces?.find(e=>e.id===t.space),r=Number(o?.cell_cm),a=Number.isFinite(r)&&r>0?r:5,l=function(t,e,i){return![t,e,i].every(Number.isFinite)||t<0||e<=0||i<=0?Number.POSITIVE_INFINITY:t/e*i}(n.residual,this._gridPitch,a),c=this._vacMapId(t,i);l>40?this._vacCalConfirm={markerId:t.id,source:e,mapId:c,matrix:n.matrix,rooms:n.matched.length,error:Ke(l,"mi"===this.hass?.config?.unit_system?.length)}:this._vacSaveMatrix(t.id,e,c,n.matrix)&&this._showToast(Bi(this._t("vac.autocal_done"),{rooms:String(n.matched.length)}))}_vacApplyCalibrationProposal(t){const e=this._vacCalConfirm;if(e){if(this._vacCalConfirm=null,t){const t=this._devices.find(t=>t.id===e.markerId),i=Yl(e.matrix);if(!t||!i)return;return this._markerDialog=null,t.space!==this._space&&(this._space=t.space),void(this._vacFit={markerId:e.markerId,source:e.source,mapId:e.mapId,p:i,drag:null})}this._vacSaveMatrix(e.markerId,e.source,e.mapId,e.matrix)&&this._showToast(Bi(this._t("vac.autocal_done"),{rooms:String(e.rooms)}))}}_vacStartFit(t){const e=this._vacSource(t),i=e?ql(this.hass?.states[e]?.attributes):null;if(!e||!i)return void this._showToast(this._t("vac.cal_need_pos"));const s=this._vacMapId(t,i),n=t.marker?.vacuum?.calibration?.[s],o=this._spaceModel(t.space),r=o?.vb||[0,0,oh,oh],a=n&&6===n.length&&Yl(n)||function(t,e){const i=[],s=[];for(const e of t)null!=e.x0?(i.push(e.x0,e.x1),s.push(e.y0,e.y1)):(i.push(e.cx),s.push(e.cy));if(!i.length)return{ox:e[0]+e[2]/2,oy:e[1]+e[3]/2,s:e[2]/1e4,rot:0,mir:!0};const n=Math.min(...i),o=Math.max(...i),r=Math.min(...s),a=Math.max(...s),l=Math.max(o-n,a-r)||1,c={ox:0,oy:0,s:.6*Math.min(e[2],e[3])/l,rot:0,mir:!0},h=Kl(c),[d,p]=Al(h,(n+o)/2,(r+a)/2);return c.ox=e[0]+e[2]/2-d,c.oy=e[1]+e[3]/2-p,c}(i.rooms,r);this._markerDialog=null,t.space!==this._space&&(this._space=t.space),this._vacFit={markerId:t.id,source:e,mapId:s,p:a,drag:null}}_vacFitSave(){const t=this._vacFit;if(!t)return;const e=this._vacSaveMatrix(t.markerId,t.source,t.mapId,Kl(t.p));this._vacFit=null,e&&this._showToast(this._t("vac.cal_done"))}_vacFitTurn(t){const e=this._vacFit;if(!e)return;const i=ql(this.hass?.states[e.source]?.attributes),s=this._vacGhostCentre(i?.rooms||[]),n={...e.p,...t};this._vacFit={...e,p:Jl(n,e.p,s[0],s[1])}}_vacGhostCentre(t){const e=[],i=[];for(const s of t)e.push(s.x0??s.cx,s.x1??s.cx),i.push(s.y0??s.cy,s.y1??s.cy);return e.length?[(Math.min(...e)+Math.max(...e))/2,(Math.min(...i)+Math.max(...i))/2]:[0,0]}_vacDelta(t,e,i){const s=this._stageEl,n=s?.clientWidth||1,o=s?.clientHeight||1;return[e/n*t.w,i/o*t.h]}_vacFitPointer(t,e){const i=this._vacFit;if(!i)return;if(t.stopPropagation(),"pointerdown"===t.type){const e=t.target,s=e.getAttribute?.("data-corner");try{t.currentTarget.setPointerCapture?.(t.pointerId)}catch{}return void(this._vacFit={...i,drag:s?{kind:"scale",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:Number(s.split(",")[0]),fy:Number(s.split(",")[1])}:{kind:"move",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:0,fy:0}})}const s=i.drag;if(s){if("pointermove"===t.type){const[n,o]=this._vacDelta(e,t.clientX-s.sx,t.clientY-s.sy);if("move"===s.kind)this._vacFit={...i,p:{...s.p0,ox:s.p0.ox+n,oy:s.p0.oy+o}};else{const t=ql(this.hass?.states[i.source]?.attributes),e=this._vacGhostCentre(t?.rooms||[]),r=Kl(s.p0),[a,l]=Al(r,e[0],e[1]),[c,h]=Al(r,s.fx,s.fy),d=Math.hypot(a-c,l-h)||1,[p,u]=[2*a-c,2*l-h],_=Math.hypot(p+2*n-c,u+2*o-h)/2,m=Math.max(.05,_/d),g={...s.p0,s:s.p0.s*m};this._vacFit={...i,p:Jl(g,s.p0,s.fx,s.fy)}}return}"pointerup"!==t.type&&"pointercancel"!==t.type||(this._vacFit={...i,drag:null})}}_renderVacFit(t){const e=this._vacFit;if(!e)return G;const i=ql(this.hass?.states[e.source]?.attributes);if(!i)return G;const s=Kl(e.p),n=[],o=[],r=[];for(const t of i.rooms){if(null==t.x0)continue;const e=[[t.x0,t.y0],[t.x1,t.y0],[t.x1,t.y1],[t.x0,t.y1]].map(([t,e])=>Al(s,t,e));e.forEach(([t,e])=>{o.push(t),r.push(e)});const[i,a]=Al(s,t.cx,t.cy);n.push(W` + ${t.name}`)}let a=G;if(i.pos){const[e,n]=Al(s,i.pos.x,i.pos.y);a=W``}const l=[];if(o.length){const e=(()=>{const t=s[0]*s[4]-s[1]*s[3];return(e,i)=>[(s[4]*(e-s[2])-s[1]*(i-s[5]))/t,(-s[3]*(e-s[2])+s[0]*(i-s[5]))/t]})(),i=Math.min(...o),n=Math.max(...o),a=Math.min(...r),c=Math.max(...r),h=.022*t.w,d=h/4;for(const[t,s,o,r]of[[i,a,n,c],[n,a,i,c],[n,c,i,a],[i,c,n,a]]){const i=e(o,r);l.push(W` `)}}return B`this._vacFitPointer(e,t)} @pointermove=${e=>this._vacFitPointer(e,t)} @pointerup=${e=>this._vacFitPointer(e,t)} - @pointercancel=${e=>this._vacFitPointer(e,t)}>${o}${a}${l}`}_vacRafLoop(){this._vacRaf=requestAnimationFrame(()=>{const t=this.renderRoot,e=this._stageEl,i=this._vacLastView,s=t?.querySelectorAll?.(".vacpuck")||[];if(!e||!i||!s.length)return void(this._vacRaf=0);const o=e.getBoundingClientRect();for(const e of s){const s=e.getAttribute("data-mid"),n=e.getBoundingClientRect(),r=i.x+(n.left+n.width/2-o.left)/o.width*i.w,a=i.y+(n.top+n.height/2-o.top)/o.height*i.h;for(const e of t.querySelectorAll(`line.tip[data-mid="${s}"]`))e.setAttribute("x2",r.toFixed(1)),e.setAttribute("y2",a.toFixed(1))}this._vacRafLoop()})}_renderVacuums(t,e){if(this._markup||"decor"===this._mode)return W;const i=this._space+"|"+e.x+"|"+e.y+"|"+e.w+"|"+e.h,s=this._vacJumpOnce||i!==this._vacViewKey;this._vacViewKey=i,this._vacJumpOnce=!1;const o=[],n=[];for(const i of t){if(i.hidden||!this._isVacDev(i))continue;if("static_icon"===Si(i.marker?.display))continue;const t=this._vacSource(i);if(!t)continue;const r=Hl(this.hass?.states[t]?.attributes);if(!r)continue;const a=i.marker?.vacuum?.calibration?.[this._vacMapId(i,r)];if(!a||6!==a.length)continue;const l=this._vacRt.get(i.id),c=l?.moving??!1,h=Jl(i.marker?.vacuum),d="always"===h||"cleaning"===h&&c,p=this._vacSrvTrails[i.id],u=this._vacMapId(i,r),_=p?.current?.map_id===u&&Array.isArray(p.current.points)?p.current:null,m=p?.previous?.map_id===u&&Array.isArray(p.previous.points)?p.previous:null;if("always"===h&&m&&m.points.length>1){const t=m.points.map(([t,e])=>{const[i,s]=Rl(a,t,e);return i.toFixed(1)+","+s.toFixed(1)}).join(" ");n.push(j``)}if(d){const t=Ol(r,_,l?.trail||[]),e=!c||"integration"!==t.source&&"server"!==t.source?t.path:Ll(t.path);if(e.length){const t=e.map(t=>t.map(([t,e],i)=>{const[s,o]=Rl(a,t,e);return`${i?"L":"M"} ${s.toFixed(1)} ${o.toFixed(1)}`}).join(" ")).join(" ");n.push(j``);const s=e[e.length-1];if(c&&s?.length>=2){const t=s[s.length-1],[e,o]=Rl(a,t[0],t[1]),r=e.toFixed(1),l=o.toFixed(1);n.push(j``)}}}if(!c||!r.pos)continue;const[g,f]=Rl(a,r.pos.x,r.pos.y),v=(g-e.x)/e.w*100,y=(f-e.y)/e.h*100,b=l&&l.lastTs>0&&Date.now()-l.lastTs>6e4,w=i.marker?.icon||i.icon||"mdi:robot-vacuum";o.push(B`
this._vacFitPointer(e,t)}>${n}${a}${l}`}_vacRafLoop(){this._vacRaf=requestAnimationFrame(()=>{const t=this.renderRoot,e=this._stageEl,i=this._vacLastView,s=t?.querySelectorAll?.(".vacpuck")||[];if(!e||!i||!s.length)return void(this._vacRaf=0);const n=e.getBoundingClientRect();for(const e of s){const s=e.getAttribute("data-mid"),o=e.getBoundingClientRect(),r=i.x+(o.left+o.width/2-n.left)/n.width*i.w,a=i.y+(o.top+o.height/2-n.top)/n.height*i.h;for(const e of t.querySelectorAll(`line.tip[data-mid="${s}"]`))e.setAttribute("x2",r.toFixed(1)),e.setAttribute("y2",a.toFixed(1))}this._vacRafLoop()})}_renderVacuums(t,e){if(this._markup||"decor"===this._mode)return G;const i=this._space+"|"+e.x+"|"+e.y+"|"+e.w+"|"+e.h,s=this._vacJumpOnce||i!==this._vacViewKey;this._vacViewKey=i,this._vacJumpOnce=!1;const n=[],o=[];for(const i of t){if(i.hidden||!this._isVacDev(i))continue;if("static_icon"===Si(i.marker?.display))continue;const t=this._vacSource(i);if(!t)continue;const r=ql(this.hass?.states[t]?.attributes);if(!r)continue;const a=i.marker?.vacuum?.calibration?.[this._vacMapId(i,r)];if(!a||6!==a.length)continue;const l=this._vacRt.get(i.id),c=l?.moving??!1,h=Zl(i.marker?.vacuum),d="always"===h||"cleaning"===h&&c,p=this._vacSrvTrails[i.id],u=this._vacMapId(i,r),_=p?.current?.map_id===u&&Array.isArray(p.current.points)?p.current:null,m=p?.previous?.map_id===u&&Array.isArray(p.previous.points)?p.previous:null;if("always"===h&&m&&m.points.length>1){const t=m.points.map(([t,e])=>{const[i,s]=Al(a,t,e);return i.toFixed(1)+","+s.toFixed(1)}).join(" ");o.push(W``)}if(d){const t=Ll(r,_,l?.trail||[]),e=!c||"integration"!==t.source&&"server"!==t.source?t.path:Hl(t.path);if(e.length){const t=e.map(t=>t.map(([t,e],i)=>{const[s,n]=Al(a,t,e);return`${i?"L":"M"} ${s.toFixed(1)} ${n.toFixed(1)}`}).join(" ")).join(" ");o.push(W``);const s=e[e.length-1];if(c&&s?.length>=2){const t=s[s.length-1],[e,n]=Al(a,t[0],t[1]),r=e.toFixed(1),l=n.toFixed(1);o.push(W``)}}}if(!c||!r.pos)continue;const[g,f]=Al(a,r.pos.x,r.pos.y),v=(g-e.x)/e.w*100,y=(f-e.y)/e.h*100,b=l&&l.lastTs>0&&Date.now()-l.lastTs>6e4,w=i.marker?.icon||i.icon||"mdi:robot-vacuum";n.push(B`
{t.stopPropagation();const e=this._vacEntity(i);e&&this._openMoreInfo(e)}}> -
`)}return this._vacLastView=e,o.length&&!this._vacRaf&&this._vacRafLoop(),o.length||n.length?B` - ${n.length?j`${n}`:W} - ${o}`:W}_renderDevice(t,e,i=!0){const s=this._pos(t),o=(s.x-e.x)/e.w*100,n=(s.y-e.y)/e.h*100,r=this._devicePresentation(t,i),a=[`left:${o}%`,`top:${n}%`,...zt(r)],l=r.disabledReason,c=r.haDisabled?this._t(`marker.ha_disabled_${l}`):t.userHidden?this._t("marker.hidden_ghost"):t.name,h=[t.model,null!=r.tempText?r.tempText+"°":"",null!=r.humText?r.humText+"%":"",null!=r.lqiText?"LQI "+r.lqiText:""].filter(Boolean).join(" · ");return B`
`)}return this._vacLastView=e,n.length&&!this._vacRaf&&this._vacRafLoop(),n.length||o.length?B` + ${o.length?W`${o}`:G} + ${n}`:G}_renderDevice(t,e,i=!0){const s=this._pos(t),n=(s.x-e.x)/e.w*100,o=(s.y-e.y)/e.h*100,r=this._devicePresentation(t,i),a=[`left:${n}%`,`top:${o}%`,...zt(r)],l=r.disabledReason,c=r.haDisabled?this._t(`marker.ha_disabled_${l}`):t.userHidden?this._t("marker.hidden_ghost"):t.name,h=[t.model,null!=r.tempText?r.tempText+"°":"",null!=r.humText?r.humText+"%":"",null!=r.lqiText?"LQI "+r.lqiText:""].filter(Boolean).join(" · ");return B`
`}_roomArea(t){const e=Je(t);if(!e)return null;const i=this._spaceWalls,s=i.length&&t.id&&jr(this._spaceModel().rooms,t.id,i,this._openPairs().flatMap(t=>t.segs),this._wallKeyPitch,this._cellCm,this._gridPitch,eh)||e,o=this._cleanFloor(t,s),n=this._cellCm/this._gridPitch;return In(o.area*n*n/1e4,"mi"===this.hass?.config?.unit_system?.length)}_roomTemp(t){const e=t.settings?.temp_source;return e?Oo(this._planHass,e,"temp",this._markers):t.area?this._climate().get(t.area)?.temp??null:null}_roomHum(t){const e=t.settings?.hum_source;return e?Oo(this._planHass,e,"hum",this._markers):t.area?this._climate().get(t.area)?.hum??null:null}_climate(){const t=this._serverCfg?.markers,e=this._planHass,i=this._climateCache;if(i&&i.h===e&&i.r===this._iconRules&&i.mk===t)return i.m;const s=function(t,e,i){const s=new Map;if(!t?.entities)return s;const o=Io(i),n=new Set;for(const t of i||[]){if(t?.removed||!0!==t?.use_climate_temp)continue;const e=(t.binding||"").indexOf(":");e>0&&n.add(t.binding.slice(e+1))}const r=new Map;for(const[e,i]of Object.entries(t.entities)){if(i.device_id&&o.devices.has(i.device_id)||!i.device_id&&o.entities.has(e))continue;const s=i.device_id?t.devices?.[i.device_id]:null,a=i.area_id||s?.area_id||null;if(!a)continue;if(i.entity_category)continue;if(!(n.size>0&&e.startsWith("climate.")&&(n.has(e)||i.device_id&&n.has(i.device_id)))){if(Et.has(i.platform))continue;if(Lo.test(e))continue}let l=r.get(a);l||(l=new Map,r.set(a,l));const c=i.device_id||e;let h=l.get(c);if(!h){const o=t.states?.[e];h={name:(s?s.name_by_user||s.name:i.name||o?.attributes?.friendly_name||e)||e,model:s?.model,ents:[]},l.set(c,h)}h.ents.push(e)}for(const[i,o]of r){const r=[],a=[];for(const[i,s]of o){const o=zo(t,s.name,s.model,s.ents,e),l="mdi:thermometer"===o||"mdi:air-filter"===o;if(l){const e=Do(t,s.ents);null!=e&&r.push(e)}if(n.size>0&&(n.has(i)||s.ents.some(t=>n.has(t)))){const e=To(t,s.ents);null!=e&&r.push(e)}if(l||"mdi:water-percent"===o){const e=Ro(t,s.ents);null!=e&&a.push(e)}}(r.length||a.length)&&s.set(i,{temp:r.length?Math.round(r.reduce((t,e)=>t+e,0)/r.length*10)/10:null,hum:a.length?Math.round(a.reduce((t,e)=>t+e,0)/a.length):null})}return s}(e,this._iconRules,t);return this._climateCache={h:e,r:this._iconRules,mk:t,m:s},s}_resetRoomDialogFields(){this._roomEditId=null,this._roomFill="",this._roomCustomFill=null,this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1}_openRoomEdit(t){if(!t.id)return;this._roomEditId=t.id,this._nameSel=t.name||"",this._areaSel=t.area||"",this._roomFill="glow"===t.settings?.fill_mode?"":t.settings?.fill_mode||"";const e=t.settings?.custom_fill;this._roomCustomFill=e&&"object"==typeof e?Vi(e,Yi(this._curSpaceCfg).customFill):null,this._roomTempSrc=t.settings?.temp_source||"",this._roomHumSrc=t.settings?.hum_source||"",this._roomNameScale=ks(t.settings?.name_scale),this._roomLabelScale=ks(t.settings?.label_scale),this._roomSrcOpen=null,this._roomSrcFilter="",this._roomDialog=!0}_roomSettingsFromDialog(){const t={};return this._roomFill&&(t.fill_mode=this._roomFill),this._roomCustomFill&&(t.custom_fill=this._roomCustomFill),this._roomTempSrc&&(t.temp_source=this._roomTempSrc),this._roomHumSrc&&(t.hum_source=this._roomHumSrc),1!==this._roomNameScale&&(t.name_scale=this._roomNameScale),1!==this._roomLabelScale&&(t.label_scale=this._roomLabelScale),Object.keys(t).length?t:null}_saveRoomEdit(){const t=this._curSpaceCfg,e=t?.rooms.find(t=>t.id===this._roomEditId);if(!e)return this._roomDialog=!1,void(this._roomEditId=null);e.name=this._nameSel.trim()||e.name,e.area=this._areaSel||null;const i=e.settings||{},s={...i};this._roomFill?s.fill_mode=this._roomFill:delete s.fill_mode,this._roomCustomFill?s.custom_fill=this._roomCustomFill:delete s.custom_fill,"glow"===i.fill_mode&&"boolean"!=typeof i.glow&&(s.glow=!0),this._roomTempSrc?s.temp_source=this._roomTempSrc:delete s.temp_source,this._roomHumSrc?s.hum_source=this._roomHumSrc:delete s.hum_source,1!==this._roomNameScale?s.name_scale=this._roomNameScale:delete s.name_scale,1!==this._roomLabelScale?s.label_scale=this._roomLabelScale:delete s.label_scale,Object.keys(s).length?e.settings=s:delete e.settings,this._saveConfig(),this._roomDialog=!1,this._roomEditId=null,this._nameSel="",this._areaSel="",this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate(),this._showToast(this._t("toast.room_updated"))}_roomSrcCandidates(){const t=this._planHass,e=Io(this._markers),i=this._roomSrcFilter.trim().toLowerCase(),s=[];for(const o of Object.values(t.devices)){if("service"===o.entry_type)continue;if(e.devices.has(o.id))continue;const t=(o.name_by_user||o.name||o.id).trim();i&&!t.toLowerCase().includes(i)||s.push({value:"device:"+o.id,label:t,sub:o.model||this._t("marker.sub_device")})}for(const[o,n]of Object.entries(t.entities)){if(!o.startsWith("sensor.")||n.hidden)continue;if(Eo(t,o,e))continue;const r=n.name||t.states[o]?.attributes?.friendly_name||o;i&&!(r+" "+o).toLowerCase().includes(i)||s.push({value:"entity:"+o,label:r,sub:o})}return s.sort((t,e)=>t.label.localeCompare(e.label)),s.slice(0,200)}_roomSrcLabel(t){const e=t.indexOf(":"),i=t.slice(0,e),s=t.slice(e+1);return"device"===i?this._fullRegistryHass.devices[s]?.name_by_user||this._fullRegistryHass.devices[s]?.name||s:this._fullRegistryHass.entities[s]?.name||this.hass.states[s]?.attributes?.friendly_name||s}_labelPos(t,e){const i=this._layout["rl_"+(t.id||"")];if(i&&i.s===e)return{x:i.x*eh,y:i.y*eh};const s=this._snap(this._roomCenter(t));return{x:s[0],y:s[1]}}_labelDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=this._labelPos(e,i);this._drag={id:"rl_"+(e.id||""),sx:t.clientX,sy:t.clientY,ox:s.x,oy:s.y,moved:!1},lh(t),this._tip=null}_labelMove(t,e,i){const s="rl_"+(e.id||"");if(!this._drag||this._drag.id!==s)return;const o=this._stageEl;if(!o)return;const n=this._spaceModel(i).vb,r=o.getBoundingClientRect(),a=this._viewOr(n),l=(t.clientX-this._drag.sx)/r.width*a.w,c=(t.clientY-this._drag.sy)/r.height*a.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0);const h=Ea(this._drag.ox+l),d=Ea(this._drag.oy+c);this._savePos({id:s,space:i},h,d)}_labelUp(t){const e="rl_"+(t.id||"");if(!this._drag||this._drag.id!==e)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&window.setTimeout(()=>this._drag=null,0)}_labelScale(t){const e=this._layout["rl_"+(t.id||"")]?.k;return"number"==typeof e&&Number.isFinite(e)?Math.min(3,Math.max(.5,e)):1}_rlResizeDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=t.target.closest(".roomlabel");if(!s)return;const o=s.getBoundingClientRect(),n=o.left+o.width/2,r=o.top+o.height/2,a=Math.max(8,Math.hypot(t.clientX-n,t.clientY-r));this._rlResize={id:"rl_"+(e.id||""),space:i,k0:this._labelScale(e),cx:n,cy:r,d0:a},lh(t)}_rlResizeMove(t){const e=this._rlResize;if(!e)return;t.stopPropagation();const i=Math.max(8,Math.hypot(t.clientX-e.cx,t.clientY-e.cy)),s=Math.min(3,Math.max(.5,e.k0*(i/e.d0))),o=this._layout[e.id];if(o)this._layout={...this._layout,[e.id]:{...o,k:s}};else{const t=e.id.slice(3),i=this._spaceModel(e.space).rooms.find(e=>e.id===t);if(!i)return;const o=this._labelPos(i,e.space);this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/eh,y:o.y/eh,k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomGear(t,e,i){if(!t.id)return W;let s=null;if(t.poly?(s=this._gearPtCache.get(t.poly)||null,s||(s=ci(t.poly),this._gearPtCache.set(t.poly,s))):null!=t.x&&null!=t.y&&(s=[t.x+(t.w||0)/2,t.y+(t.h||0)/2]),!s)return W;const o=(s[0]-i.x)/i.w*100,n=(s[1]-i.y)/i.h*100;return B``}_renderRoomLabel(t,e,i,s){if(!t.name&&!this._markup)return W;const o=this._labelPos(t,e.id),n=(o.x-i.x)/i.w*100,r=(o.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if(t.area||t.settings?.temp_source||t.settings?.hum_source||s.labelLight){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight){const e=(h=xo(this._planHass,this._devices,t)).length?{on:h.filter(t=>t.on).length,total:h.length}:null;if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}var h;return B`
`}_renderRoomLabel(t,e,i,s){if(!t.name&&!this._markup)return G;const n=this._labelPos(t,e.id),o=(n.x-i.x)/i.w*100,r=(n.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if(t.area||t.settings?.temp_source||t.settings?.hum_source||s.labelLight){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight){const e=(h=xn(this._planHass,this._devices,t)).length?{on:h.filter(t=>t.on).length,total:h.length}:null;if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}var h;return B`
this._labelDown(i,t,e.id)} @pointermove=${i=>this._labelMove(i,t,e.id)} @pointerup=${()=>this._labelUp(t)} @@ -4802,38 +4872,38 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 >${t.name||(this._markup?this._t("room.unnamed"):"")}${!this._markup&&t.area?B`{e.stopPropagation(),this._clickRoom(t)}} - @pointerdown=${t=>t.stopPropagation()}>`:W} - ${c.length?B`${c}`:W} + @pointerdown=${t=>t.stopPropagation()}>`:G} + ${c.length?B`${c}`:G} ${"plan"===this._mode?["tl","tr","bl","br"].map(i=>B`this._rlResizeDown(i,t,e.id)} @pointermove=${t=>this._rlResizeMove(t)} @pointerup=${()=>this._rlResizeUp()} - @pointercancel=${()=>this._rlResizeUp()}>`):W} -
`}get _measureAnchor(){return this._markup&&this._cursorPt?"draw"!==this._tool&&"partition"!==this._tool||!this._path.length||this._contourClosed?"split"===this._tool&&this._splitSel?.pts?.length?this._splitSel.pts[this._splitSel.pts.length-1]:null:this._path[this._path.length-1]:null}_renderMeasureLabel(t){const e=this._measureAnchor,i=this._cursorPt,s=(i[0]-t.x)/t.w*100,o=(i[1]-t.y)/t.h*100,n=$s(e,i),r=Math.round(10*n)/10,a=xs(n);return B`
- ${this._fmtLen(e,i)} · ${r}°
`}get _decorMeasure(){const t=this._decorDraft;if(!t||"decor"!==this._mode)return null;const[e,i]=t.a,[s,o]=t.b;if(Math.abs(e-s)<1e-6&&Math.abs(i-o)<1e-6)return null;const n=(e+s)/2,r=(i+o)/2;if("line"===t.kind){const e=$s(t.a,t.b);return{x:n,y:r,on45:xs(e),text:`${this._fmtLen(t.a,t.b)} · ${Math.round(10*e)/10}°`}}const a=this._fmtLen([e,i],[s,i]),l=this._fmtLen([s,i],[s,o]);if("ellipse"===t.kind){const t=this._fmtLen([0,0],[Math.abs(s-e)/2,0]),a=this._fmtLen([0,0],[0,Math.abs(o-i)/2]);return{x:n,y:r,on45:!1,text:Math.abs(Math.abs(s-e)-Math.abs(o-i))<1e-6?`R ${t}`:`Rx ${t} × Ry ${a}`}}return{x:n,y:r,on45:!1,text:`${a} × ${l} · ${In(tr(Math.abs(s-e),this._cellCm,this._gridPitch)*tr(Math.abs(o-i),this._cellCm,this._gridPitch)/1e4,this._imperial)}`}}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=eh,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=Je(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id||"ha_disabled"===e.bindingStatus?.kind)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const e=this._decorMove?.id;return t.push(...this._decorSnapGeometry(e).points),this._decorDraft&&t.push(this._decorDraft.a),t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return j``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,o=null;for(const n of e)if(!(Math.abs(n[0]-t[0])<1e-6&&Math.abs(n[1]-t[1])<1e-6)){if(Math.abs(n[0]-t[0])<=i){const e=Math.abs(n[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!o||e - ${i.map(e=>{const[i,n,r,a]="x"===e.axis?[e.at,e.from[1],e.at,t[1]+Math.sign(t[1]-e.from[1])*o]:[e.from[0],e.at,t[0]+Math.sign(t[0]-e.from[0])*o,e.at];return j` + @pointercancel=${()=>this._rlResizeUp()}>`):G} +
`}get _measureAnchor(){return this._markup&&this._cursorPt?"draw"!==this._tool&&"partition"!==this._tool||!this._path.length||this._contourClosed?"split"===this._tool&&this._splitSel?.pts?.length?this._splitSel.pts[this._splitSel.pts.length-1]:null:this._path[this._path.length-1]:null}_renderMeasureLabel(t){const e=this._measureAnchor,i=this._cursorPt,s=(i[0]-t.x)/t.w*100,n=(i[1]-t.y)/t.h*100,o=$s(e,i),r=Math.round(10*o)/10,a=xs(o);return B`
+ ${this._fmtLen(e,i)} · ${r}°
`}get _decorMeasure(){const t=this._decorDraft;if(!t||"decor"!==this._mode)return null;const[e,i]=t.a,[s,n]=t.b;if(Math.abs(e-s)<1e-6&&Math.abs(i-n)<1e-6)return null;const o=(e+s)/2,r=(i+n)/2;if("line"===t.kind){const e=$s(t.a,t.b);return{x:o,y:r,on45:xs(e),text:`${this._fmtLen(t.a,t.b)} · ${Math.round(10*e)/10}°`}}const a=this._fmtLen([e,i],[s,i]),l=this._fmtLen([s,i],[s,n]);if("ellipse"===t.kind){const t=this._fmtLen([0,0],[Math.abs(s-e)/2,0]),a=this._fmtLen([0,0],[0,Math.abs(n-i)/2]);return{x:o,y:r,on45:!1,text:Math.abs(Math.abs(s-e)-Math.abs(n-i))<1e-6?`R ${t}`:`Rx ${t} × Ry ${a}`}}return{x:o,y:r,on45:!1,text:`${a} × ${l} · ${Io(tr(Math.abs(s-e),this._cellCm,this._gridPitch)*tr(Math.abs(n-i),this._cellCm,this._gridPitch)/1e4,this._imperial)}`}}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=oh,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=Je(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id||"ha_disabled"===e.bindingStatus?.kind)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const e=this._decorMove?.id;return t.push(...this._decorSnapGeometry(e).points),this._decorDraft&&t.push(this._decorDraft.a),t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return W``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,n=null;for(const o of e)if(!(Math.abs(o[0]-t[0])<1e-6&&Math.abs(o[1]-t[1])<1e-6)){if(Math.abs(o[0]-t[0])<=i){const e=Math.abs(o[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!n||e + ${i.map(e=>{const[i,o,r,a]="x"===e.axis?[e.at,e.from[1],e.at,t[1]+Math.sign(t[1]-e.from[1])*n]:[e.from[0],e.at,t[0]+Math.sign(t[0]-e.from[0])*n,e.at];return W` `})} - `}_renderOpeningCenterTick(t){const e=(t.angle+90)*Math.PI/180;return j``}_renderOpeningCenterTick(t){const e=(t.angle+90)*Math.PI/180;return W``}_roomCenter(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}_openingAmt(t){const e=t.contact&&this._planEntityAvailable(t.contact)?this.hass.states[t.contact]?.state:null;return ei(t.type,e,!!t.invert)}_planEntityAvailable(t){return!!t&&(!Eo(this._fullRegistryHass,t,Io(this._markers))&&"active"===this._bindingStatus("entity:"+t).kind)}_renderOpenings(t){const e=this._openingsR;if(!e.length)return j``;const i=t.color,s=this._spaceWalls,o=this._openPairs().flatMap(t=>t.segs),n=this._openingWallIndexFor(this._spaceModel(),o).value;return j`${e.map(t=>{const e=t.rlen/2,o=this._openingAmt(t),r=o>0&&!!t.contact&&this._planEntityAvailable(t.contact)?"var(--hp-open)":i,a="gate"===t.type?!t.flip_v:t.flip_v,l=s.length||"gate"===t.type?ta(n,{x:t.rx,y:t.ry,angle:t.angle,length:t.rlen,flip_v:a}):{ox:0,oy:0,cm:0,side:-1},c=l.cm>0?l.cm/this._cellCm*this._gridPitch/2:4,h=t.flip_h?-1:1,d=t.flip_v?-1:1;let p,u=0,_=0;if(l.cm>0&&(l.ox||l.oy)){const e=-t.angle*Math.PI/180,i=Math.cos(e),s=Math.sin(e);u=l.ox*i-l.oy*s,_=l.ox*s+l.oy*i,_*=d,u*=h}if("window"===t.type){const t=Math.PI/2*e,i=l.cm>0?j``:W;p=j` + x2="${t.x+15*Math.cos(e)}" y2="${t.y+15*Math.sin(e)}">`}_roomCenter(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}_openingAmt(t){const e=t.contact&&this._planEntityAvailable(t.contact)?this.hass.states[t.contact]?.state:null;return ei(t.type,e,!!t.invert)}_planEntityAvailable(t){return!!t&&(!En(this._fullRegistryHass,t,In(this._markers))&&"active"===this._bindingStatus("entity:"+t).kind)}_renderOpenings(t){const e=this._openingsR;if(!e.length)return W``;const i=t.color,s=this._spaceWalls,n=this._openPairs().flatMap(t=>t.segs),o=this._openingWallIndexFor(this._spaceModel(),n).value;return W`${e.map(t=>{const e=t.rlen/2,n=this._openingAmt(t),r=n>0&&!!t.contact&&this._planEntityAvailable(t.contact)?"var(--hp-open)":i,a="gate"===t.type?!t.flip_v:t.flip_v,l=s.length||"gate"===t.type?ta(o,{x:t.rx,y:t.ry,angle:t.angle,length:t.rlen,flip_v:a}):{ox:0,oy:0,cm:0,side:-1},c=l.cm>0?l.cm/this._cellCm*this._gridPitch/2:4,h=t.flip_h?-1:1,d=t.flip_v?-1:1;let p,u=0,_=0;if(l.cm>0&&(l.ox||l.oy)){const e=-t.angle*Math.PI/180,i=Math.cos(e),s=Math.sin(e);u=l.ox*i-l.oy*s,_=l.ox*s+l.oy*i,_*=d,u*=h}if("window"===t.type){const t=Math.PI/2*e,i=l.cm>0?W``:G;p=W` + stroke="${r}" stroke-dasharray="${t}" stroke-dashoffset="${t*(1-n)}"> + stroke="${r}" stroke-dasharray="${t}" stroke-dashoffset="${t*(1-n)}"> - + - + ${i} - `}else if("gate"===t.type){const t=l.side*d*10*o;p=j` + `}else if("gate"===t.type){const t=l.side*d*10*n;p=W` @@ -4845,16 +4915,16 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 - `}else{const i=t.rlen,s=Math.PI/2*i;p=j` + `}else{const i=t.rlen,s=Math.PI/2*i;p=W` + stroke="${r}" stroke-dasharray="${s}" stroke-dashoffset="${s*(1-n)}"> - + - `}const m="gate"===t.type?Math.sin(10*Math.PI/180)*e:0,g=Math.max(16,c+8,m+8),f=Math.max(20,c+10,m+12);return j``}const m="gate"===t.type?Math.sin(10*Math.PI/180)*e:0,g=Math.max(16,c+8,m+8),f=Math.max(20,c+10,m+12);return W` @@ -4868,25 +4938,25 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 @pointermove=${e=>this._opPointerMove(e,t)} @pointerup=${e=>this._opPointerUp(e,t)} @pointercancel=${e=>this._opPointerUp(e,t)}> - `})}`}_renderOpeningLocks(t){const e=this._openingsR.filter(t=>"window"!==t.type&&t.lock&&this._planEntityAvailable(t.lock));if(!e.length)return B``;const i=this._openPairs().flatMap(t=>t.segs),s=this._openingWallIndexFor(this._spaceModel(),i).value;return B`${e.map(e=>{const i=this.hass.states[e.lock]?.state,o="locked"===i,n=o||["unlocked","open","opening","unlocking","locking"].includes(String(i)),r=(e.angle+90)*Math.PI/180,a="gate"===e.type?ta(s,{x:e.rx,y:e.ry,angle:e.angle,length:e.rlen,flip_v:!e.flip_v}):null,l=a?-16*a.side:16*(e.flip_v?-1:1),c=e.rx+Math.cos(r)*l,h=e.ry+Math.sin(r)*l,d=(c-t.x)/t.w*100,p=(h-t.y)/t.h*100;return B`
`})}`}_renderOpeningLocks(t){const e=this._openingsR.filter(t=>"window"!==t.type&&t.lock&&this._planEntityAvailable(t.lock));if(!e.length)return B``;const i=this._openPairs().flatMap(t=>t.segs),s=this._openingWallIndexFor(this._spaceModel(),i).value;return B`${e.map(e=>{const i=this.hass.states[e.lock]?.state,n="locked"===i,o=n||["unlocked","open","opening","unlocking","locking"].includes(String(i)),r=(e.angle+90)*Math.PI/180,a="gate"===e.type?ta(s,{x:e.rx,y:e.ry,angle:e.angle,length:e.rlen,flip_v:!e.flip_v}):null,l=a?-16*a.side:16*(e.flip_v?-1:1),c=e.rx+Math.cos(r)*l,h=e.ry+Math.sin(r)*l,d=(c-t.x)/t.w*100,p=(h-t.y)/t.h*100;return B`
{t.stopPropagation(),"view"===this._mode&&(this._openingInfo=e)}}> - -
`})}`}_lockAction(t,e){if(this._planEntityAvailable(t)){if("unlock"===e){const e=this.hass?.states?.[t]?.attributes?.friendly_name||t;if(!confirm(this._t("confirm.unlock",{name:e})))return}this.hass?.callService?.("lock",e,{entity_id:t})}}_renderOpeningInfoCard(){const t=this._openingInfo,e=t.contact&&this._planEntityAvailable(t.contact)?t.contact:null,i=t.lock&&this._planEntityAvailable(t.lock)?t.lock:null,s=e?this.hass.states[e]?.state:null,o=this._openingAmt(t),n=i?this.hass.states[i]?.state:null,r="door"===t.type?"opening.door":"gate"===t.type?"opening.gate":"opening.window",a="door"===t.type?"mdi:door":"gate"===t.type?"mdi:gate":"mdi:window-closed-variant",l="gate"===t.type?o>0?"mdi:gate-open":"mdi:gate":o>0?"mdi:door-open":"mdi:door-closed",c=(t,e,i,s="")=>B`
${e}${i}
`;return B` +
`})}`}_lockAction(t,e){if(this._planEntityAvailable(t)){if("unlock"===e){const e=this.hass?.states?.[t]?.attributes?.friendly_name||t;if(!confirm(this._t("confirm.unlock",{name:e})))return}this.hass?.callService?.("lock",e,{entity_id:t})}}_renderOpeningInfoCard(){const t=this._openingInfo,e=t.contact&&this._planEntityAvailable(t.contact)?t.contact:null,i=t.lock&&this._planEntityAvailable(t.lock)?t.lock:null,s=e?this.hass.states[e]?.state:null,n=this._openingAmt(t),o=i?this.hass.states[i]?.state:null,r="door"===t.type?"opening.door":"gate"===t.type?"opening.gate":"opening.window",a="door"===t.type?"mdi:door":"gate"===t.type?"mdi:gate":"mdi:window-closed-variant",l="gate"===t.type?n>0?"mdi:gate-open":"mdi:gate":n>0?"mdi:door-open":"mdi:door-closed",c=(t,e,i,s="")=>B`
${e}${i}
`;return B`this._openingInfo=null}>
- ${e?c(l,this._t("opening.contact_label"),"unavailable"===s||null==s?this._t("opening.state_unknown"):this._t(o>0?"opening.open":"opening.closed"),o>0?"warn":"ok"):W} - ${i?c("locked"===n?"mdi:lock":"mdi:lock-open-variant",this._t("opening.lock_label"),"locked"===n?this._t("opening.locked"):["unlocked","open"].includes(String(n))?this._t("opening.unlocked"):this._t("opening.state_unknown"),"locked"===n?"ok":"warn"):W} - ${i&&("locked"===n||["unlocked","open"].includes(String(n)))?B``:i&&["locking","unlocking"].includes(String(n))?B``:i&&["locking","unlocking"].includes(String(o))?B``:W} - ${e||i?W:B`

${this._t("opening.no_entities")}

`} + `:G} + ${e||i?G:B`

${this._t("opening.no_entities")}

`}
@@ -4912,98 +4982,101 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 {const i=Hc(e.target.value);null!=i&&(this._openingDialog={...t,lengthCm:i})}} /> + @input=${e=>{const i=Vc(e.target.value);null!=i&&(this._openingDialog={...t,lengthCm:i})}} /> ${i(this._contactCandidates(),t.contact,e=>this._openingDialog={...t,contact:e})} ${t.contact?B``:W} + ${this._t("opening.invert")}`:G} ${"window"!==t.type?B` - ${i(this._lockCandidates(),t.lock,e=>this._openingDialog={...t,lock:e})}`:W} + ${i(this._lockCandidates(),t.lock,e=>this._openingDialog={...t,lock:e})}`:G} ${"gate"!==t.type?B``:W} + ${this._t("opening.flip_h")}`:G}
-
- ${t.id?B``:W} - - - + - `}_gridLevels(){const t=this._stageEl,e=this._viewOr(this._baseVb()),i=t&&t.clientWidth&&e.w?t.clientWidth/e.w:1;return function(t,e,i=7){if(!(t>0&&e>0&&Number.isFinite(e)))return null;const s=Ja.find(s=>t*s*e>=i);if(void 0===s)return null;const o=Ja.find(t=>t>=5*s)??5*s;return{fine:s,coarse:o}}(this._gridPitch,i)}_renderMarkupDefs(t){const e=this._gridLevels();if(!e)return j``;const i=this._gridPitch*e.fine,s=this._gridPitch*e.coarse,o=this._gridPitch*e.fine*.14;return j` + `}_gridLevels(){const t=this._stageEl,e=this._viewOr(this._baseVb()),i=t&&t.clientWidth&&e.w?t.clientWidth/e.w:1;return function(t,e,i=7){if(!(t>0&&e>0&&Number.isFinite(e)))return null;const s=Ya.find(s=>t*s*e>=i);if(void 0===s)return null;const n=Ya.find(t=>t>=5*s)??5*s;return{fine:s,coarse:n}}(this._gridPitch,i)}_renderMarkupDefs(t){const e=this._gridLevels();if(!e)return W``;const i=this._gridPitch*e.fine,s=this._gridPitch*e.coarse,n=this._gridPitch*e.fine*.14;return W` - - - - + + + + - - - - + + + + - `}_renderPhysicalEditorLayer(){const t=this._spaceModel(),e=this._gridPitch,i=this._viewOr(this._baseVb()),s=this._stageEl,o=s?.clientWidth?i.w/s.clientWidth:e/8,n=Math.max(.22*e,8*o),r=Math.max(n,12*o),a=t=>`M ${t.map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`,l=(t,e)=>this._physicalSel?.kind===t&&this._physicalSel.id===e||"column"===t&&this._duplicateColumnId===e,c=(t.room_drafts||[]).flatMap(t=>t.points.slice(0,-1).map((e,i)=>{const s=t.points[i+1],n=dr(t.segments[i]?.cm||15,this._cellCm,this._gridPitch);return j``}_renderPhysicalEditorLayer(){const t=this._spaceModel(),e=this._gridPitch,i=this._viewOr(this._baseVb()),s=this._stageEl,n=s?.clientWidth?i.w/s.clientWidth:e/8,o=Math.max(.22*e,8*n),r=Math.max(o,12*n),a=t=>`M ${t.map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`,l=(t,e)=>this._physicalSel?.kind===t&&this._physicalSel.id===e||"column"===t&&this._duplicateColumnId===e,c=(t.room_drafts||[]).flatMap(t=>t.points.slice(0,-1).map((e,i)=>{const s=t.points[i+1],o=dr(t.segments[i]?.cm||15,this._cellCm,this._gridPitch);return W`{this._physicalSel={kind:"draft",id:t.id,segment:i},this._registerPhysicalTap("draft",t.id,i)}}>`})),h=(t.partitions||[]).map(t=>{const e=ca(t.a,t.b,t.cm,this._cellCm,this._gridPitch);return e?j`{this._physicalSel={kind:"draft",id:t.id,segment:i},this._registerPhysicalTap("draft",t.id,i)}}>`})),h=(t.partitions||[]).map(t=>{const e=ca(t.a,t.b,t.cm,this._cellCm,this._gridPitch);return e?W`this._physicalDown(e,"partition",t.id)} @pointermove=${t=>this._physicalMove(t)} - @pointerup=${t=>this._physicalUp(t)}>`:W}),d=(t.wall_columns||[]).map(t=>{const e=ha(this._physicalRotate?.id===t.id?{...t,angle:this._physicalRotate.angle}:t,this._cellCm,this._gridPitch);return j`this._physicalUp(t)}>`:G}),d=(t.wall_columns||[]).map(t=>{const e=ha(this._physicalRotate?.id===t.id?{...t,angle:this._physicalRotate.angle}:t,this._cellCm,this._gridPitch);return W`this._physicalDown(e,"column",t.id)} @pointermove=${t=>this._physicalMove(t)} - @pointerup=${t=>this._physicalUp(t)}>`}),p=this._physicalDrag,u=(()=>{if(!p?.moved)return W;const t="partition"===p.kind?ca(p.base.a,p.base.b,p.base.cm,this._cellCm,this._gridPitch):ha(p.base,this._cellCm,this._gridPitch);return t?j``:W})(),_=(()=>{const i=this._physicalSel;if(!i)return W;if("draft"===i.kind){const e=t.room_drafts.find(t=>t.id===i.id);return e?j` + @pointerup=${t=>this._physicalUp(t)}>`}),p=this._physicalDrag,u=(()=>{if(!p?.moved)return G;const t="partition"===p.kind?ca(p.base.a,p.base.b,p.base.cm,this._cellCm,this._gridPitch):ha(p.base,this._cellCm,this._gridPitch);return t?W``:G})(),_=(()=>{const i=this._physicalSel;if(!i)return G;if("draft"===i.kind){const e=t.room_drafts.find(t=>t.id===i.id);return e?W` t.join(",")).join(" ")}> - ${e.points.map(t=>j``)} - `:W}if("partition"===i.kind){const e=t.partitions.find(t=>t.id===i.id);if(!e)return W;const s=ca(e.a,e.b,e.cm,this._cellCm,this._gridPitch);if(!s)return W;const o=(e.a[0]+e.b[0])/2,r=(e.a[1]+e.b[1])/2;return j` + ${e.points.map(t=>W``)} + `:G}if("partition"===i.kind){const e=t.partitions.find(t=>t.id===i.id);if(!e)return G;const s=ca(e.a,e.b,e.cm,this._cellCm,this._gridPitch);if(!s)return G;const n=(e.a[0]+e.b[0])/2,r=(e.a[1]+e.b[1])/2;return W` - - - - `}const s=t.wall_columns.find(t=>t.id===i.id);if(!s)return W;const l=this._physicalRotate?.id===s.id?{...s,angle:this._physicalRotate.angle}:s,c=ha(l,this._cellCm,this._gridPitch);if("square"!==l.shape)return j` + + + + `}const s=t.wall_columns.find(t=>t.id===i.id);if(!s)return G;const l=this._physicalRotate?.id===s.id?{...s,angle:this._physicalRotate.angle}:s,c=ha(l,this._cellCm,this._gridPitch);if("square"!==l.shape)return W` - - `;const h=dr(l.cm,this._cellCm,this._gridPitch),d=ra(l.angle)*Math.PI/180,p=Math.sin(d),u=-Math.cos(d),_=[l.center[0]+p*h/2,l.center[1]+u*h/2],m=[_[0]+p*Math.max(e,24*o),_[1]+u*Math.max(e,24*o)];return j` + + `;const h=dr(l.cm,this._cellCm,this._gridPitch),d=ra(l.angle)*Math.PI/180,p=Math.sin(d),u=-Math.cos(d),_=[l.center[0]+p*h/2,l.center[1]+u*h/2],m=[_[0]+p*Math.max(e,24*n),_[1]+u*Math.max(e,24*n)];return W` - + this._physicalRotateDown(t,s)} @pointermove=${t=>this._physicalRotateMove(t)} @pointerup=${t=>this._physicalRotateUp(t)}> - `})();return j`${c}${h}${d}${u}${_}`}_renderMarkupLayer(t){const e=this._openPairs().flatMap(t=>t.segs),i=this._thickWallCuts(),s=e.concat(i),o=s.length?_s(this._segments,s,.02*this._gridPitch):this._segments,n=this._path,r=this._gridPitch,a=this._viewOr(this._baseVb()),l="draw"===this._tool||"partition"===this._tool?this._drawWallCm:null,c=(()=>"draw"!==this._tool&&"partition"!==this._tool||!n.length||!(null!=l&&l>0)?null:this._contourClosed?n:this._cursorPt?[...n,this._cursorPt]:n.length>=2?n:null)(),h=c?Dr(c,dr(l,this._cellCm,this._gridPitch)/2,this._contourClosed):"";return j` - ${this._gridLevels()?j``:W} - ${o.map(t=>j``)} + `})();return W`${c}${h}${d}${u}${_}`}_renderMarkupLayer(t){const e=this._openPairs().flatMap(t=>t.segs),i=this._thickWallCuts(),s=e.concat(i),n=s.length?_s(this._segments,s,.02*this._gridPitch):this._segments,o=this._path,r=this._gridPitch,a=this._viewOr(this._baseVb()),l="draw"===this._tool||"partition"===this._tool?this._drawWallCm:null,c=(()=>"draw"!==this._tool&&"partition"!==this._tool||!o.length||!(null!=l&&l>0)?null:this._contourClosed?o:this._cursorPt?[...o,this._cursorPt]:o.length>=2?o:null)(),h=c?Dr(c,dr(l,this._cellCm,this._gridPitch)/2,this._contourClosed):"";return W` + ${this._gridLevels()?W``:G} + ${n.map(t=>W``)} ${this._renderPhysicalEditorLayer()} - ${"column"===this._tool&&this._cursorPt&&this._drawWallCm?j``M ${ha({shape:"square",center:this._cursorPt,cm:this._drawWallCm},this._cellCm,this._gridPitch).map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`)()}>`:W} - ${h?j` - `:W} - ${n.length>1?j``:W} - ${!n.length||!this._cursorPt||"draw"!==this._tool&&"partition"!==this._tool||this._contourClosed?W:j``M ${ha({shape:"square",center:this._cursorPt,cm:this._drawWallCm},this._cellCm,this._gridPitch).map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`)()}>`:G} + ${h?W` + `:G} + ${o.length>1?W``:G} + ${!o.length||!this._cursorPt||"draw"!==this._tool&&"partition"!==this._tool||this._contourClosed?G:W``} - ${n.map((t,e)=>j``)} - ${(()=>{const t=this._openingPreview;if(!t)return W;const e=t.angle*Math.PI/180,i=Math.cos(e)*t.rlen/2,s=Math.sin(e)*t.rlen/2;return j`W``)} + ${(()=>{const t=this._openingPreview;if(!t)return G;const e=t.angle*Math.PI/180,i=Math.cos(e)*t.rlen/2,s=Math.sin(e)*t.rlen/2;return W` `})()} - ${"split"===this._tool&&this._splitSel?.pts?.length?j`${this._splitSel.pts.length>1?j``:W} - ${this._splitSel.pts.map((t,e)=>j``)} - ${this._cursorPt?j``:W}`:W} + ${"split"===this._tool&&this._splitSel?.pts?.length?W`${this._splitSel.pts.length>1?W``:G} + ${this._splitSel.pts.map((t,e)=>W``)} + ${this._cursorPt?W``:G}`:G} `}_renderPhysicalDialog(){const t=this._physicalDialog,e="column"===t.kind;return B`{const i=e.target.value;this._physicalDialog={...t,shape:i}}}> - `:W} + `:G}
this._physicalDialog={...t,angle:e.target.value}} />`:W} - ${t.length?B`
${this._t("physical.length")}: ${t.length}
`:W} + @input=${e=>this._physicalDialog={...t,angle:e.target.value}} />`:G} + ${t.length?B`
${this._t("physical.length")}: ${t.length}
`:G}
-
`}_renderMarkupBar(){const t=this._geometryHistory.undoName,e=this._geometryHistory.redoName,i="boundary"===this._tool&&!!this._openWallAnchor,s=i?this._t("markup.boundary_cancel"):t?this._t("history.undo_named",{name:t}):this._t("history.undo_empty"),o=i?this._t("markup.boundary_cancel"):e?this._t("history.redo_named",{name:e}):this._t("history.redo_empty");return B`
+ `}_renderMarkupBar(){const t=this._geometryHistory.undoName,e=this._geometryHistory.redoName,i="boundary"===this._tool&&!!this._openWallAnchor,s=i?this._t("markup.boundary_cancel"):t?this._t("history.undo_named",{name:t}):this._t("history.undo_empty"),n=i?this._t("markup.boundary_cancel"):e?this._t("history.redo_named",{name:e}):this._t("history.redo_empty");return B`
@@ -5116,7 +5189,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
@@ -5147,33 +5220,33 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
-
`}_cardEntities(t){const e=this._planHass,i=[],s=new Set,o=t=>{if(!t||s.has(t)||!e.states[t])return;const o=e.entities[t];if("config"===o?.entity_category||"diagnostic"===o?.entity_category)return;s.add(t);const n=t.split(".")[0];["light","switch","fan","humidifier","siren","input_boolean"].includes(n)?i.push({eid:t,kind:"toggle"}):["cover","valve","lock","climate","media_player","vacuum","water_heater"].includes(n)?i.push({eid:t,kind:"open"}):["sensor","binary_sensor","number","select"].includes(n)&&i.push({eid:t,kind:"value"})};for(const i of xo(e,[t]))o(i.eid);t.primary&&o(t.primary);for(const e of t.entities)o(e);return i.slice(0,12)}_cardToggle(t){const e=t.split(".")[0];"lock"!==e&&"alarm_control_panel"!==e&&this._planEntityAvailable(t)&&this.hass.callService("homeassistant","toggle",{entity_id:t}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}_renderInfoCard(){const t=this._infoCard,e=t.primary?this.hass.states[t.primary]:void 0,i=e?Oi(this.hass,t.primary)?.text??e.state:null,s=(t.controls??t.marker?.controls??[]).filter(ps).filter(t=>this._planEntityAvailable(t));return B``}_cardEntities(t){const e=this._planHass,i=[],s=new Set,n=t=>{if(!t||s.has(t)||!e.states[t])return;const n=e.entities[t];if("config"===n?.entity_category||"diagnostic"===n?.entity_category)return;s.add(t);const o=t.split(".")[0];["light","switch","fan","humidifier","siren","input_boolean"].includes(o)?i.push({eid:t,kind:"toggle"}):["cover","valve","lock","climate","media_player","vacuum","water_heater"].includes(o)?i.push({eid:t,kind:"open"}):["sensor","binary_sensor","number","select"].includes(o)&&i.push({eid:t,kind:"value"})};for(const i of xn(e,[t]))n(i.eid);t.primary&&n(t.primary);for(const e of t.entities)n(e);return i.slice(0,12)}_cardToggle(t){const e=t.split(".")[0];"lock"!==e&&"alarm_control_panel"!==e&&this._planEntityAvailable(t)&&this.hass.callService("homeassistant","toggle",{entity_id:t}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}_renderInfoCard(){const t=this._infoCard,e=t.primary?this.hass.states[t.primary]:void 0,i=e?Oi(this.hass,t.primary)?.text??e.state:null,s=(t.controls??t.marker?.controls??[]).filter(ps).filter(t=>this._planEntityAvailable(t));return B`
${(()=>{const e=this._cardEntities(t);return e.length?B`
- ${e.map(({eid:t,kind:e})=>{const i=this.hass.states[t],s=this.hass.entities[t]?.name||i?.attributes?.friendly_name||t,o=i?Oi(this.hass,t)?.text??i.state:"",n="on"===i?.state||["open","unlocked","playing","cleaning"].includes(i?.state);return B`
- + ${e.map(({eid:t,kind:e})=>{const i=this.hass.states[t],s=this.hass.entities[t]?.name||i?.attributes?.friendly_name||t,n=i?Oi(this.hass,t)?.text??i.state:"",o="on"===i?.state||["open","unlocked","playing","cleaning"].includes(i?.state);return B`
+ ${s} - ${"toggle"===e?B``:"open"===e?B``:B`${o}`} + ${"toggle"===e?B``:"open"===e?B``:B`${n}`}
`})} -
`:W})()} - ${t.model?B`
${this._t("info.model")}${t.model}
`:W} - ${i&&!this._cardEntities(t).length?B`
${this._t("info.state")}${i}
`:W} +
`:G})()} + ${t.model?B`
${this._t("info.model")}${t.model}
`:G} + ${i&&!this._cardEntities(t).length?B`
${this._t("info.state")}${i}
`:G} ${$i(t.link)?B`
${this._t("info.link")} - ${t.link}
`:W} - ${t.description?B`
${t.description}
`:W} + ${t.link}
`:G} + ${t.description?B`
${t.description}
`:G} ${t.pdfs&&t.pdfs.length?B`
${this._t("info.manuals")} ${t.pdfs.map(t=>B` - ${t.name}`)}
`:W} + ${t.name}`)}
`:G} ${s.length?B`
${this._t("info.controls")} ${s.map(t=>{const e=this.hass.states[t],i="on"===e?.state;return B` ${e?.attributes?.friendly_name||t}`})} -
`:W} - ${t.model||i||t.link||t.description||t.pdfs&&t.pdfs.length||s.length?W:B`
${this._t("info.none")}
`} +
`:G} + ${t.model||i||t.link||t.description||t.pdfs&&t.pdfs.length||s.length?G:B`
${this._t("info.none")}
`}
${t.primary?B``:W} + `:G}
- `}_markerDraft(t){if("ha"===t.bindingMode&&(!t.binding||"virtual"===t.binding))return null;const e=Ds(t.room),i=yi(t.binding,t.devId,()=>"__hp_device_preview__"),s=this._markers.find(e=>e.id===i||e.id===t.devId),o=yo(t.binding,t.controls,this._bindingEntities(t.binding)),n={id:i,binding:t.binding,name:t.name.trim()||null,icon:t.icon||null,display:"badge"!==t.display?t.display:null,ripple_color:"icon_ripple"===t.display&&t.rippleColor?t.rippleColor:null,ripple_size:"icon_ripple"===t.display&&3!==t.rippleSize?t.rippleSize:null,size:1!==t.size?t.size:null,angle:t.angle||null,tap_action:t.tapAction||null,tap_target:"run"===t.tapAction&&t.tapTarget||null,tap_confirm:!!t.tapConfirm||null,controls:o.length?o:null,...this._markerLightFields(t),use_climate_temp:!!t.useClimateTemp||null,glow_radius_cm:(()=>{const e=Hc(t.glowRadius);return null==e||e<=0?null:Math.round(this._imperial?30.48*e:100*e)})(),model:t.model.trim()||null,link:t.link.trim()||null,description:t.description.trim()||null,pdfs:t.pdfs,hidden:t.hideFromPlan,vacuum:s?.vacuum||null};return("virtual"===t.binding||t.room)&&(n.space=e?.space||("virtual"===t.binding?this._space:null),n.area=e?.area||null,n.room_id=e?.roomId||null),n}_markerPreviewDevice(t){const e=this._markerDraft(t);if(!e)return null;const i=`${this._haRegistry.revision}\n${JSON.stringify(e)}`;if(this._markerPreviewMemo?.key===i)return this._markerPreviewMemo.device;const s=function(t){const{marker:e,...i}=t;return Fo({...i,markers:[e]}).find(t=>t.id===e.id)||null}({hass:this.hass,registry:this._haRegistry,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),marker:e,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||this._space,loc:t=>this._t(t),iconRules:this._iconRules});return this._markerPreviewMemo={key:i,device:s},s}_markerSpatialSource(t){const e=this._markerPreviewDevice(t);return e?So(xo(this._planHass,[{...e,hidden:!1}])):null}_markerAutoHasSpatialSource(t){const e={...t,lightRole:"auto",lightRoleTouched:!0},i=this._markerPreviewDevice(e);return!!i&&ko(this._planHass,{...i})}_setMarkerLightRole(t){const e=this._markerDialog;e&&(this._markerDialog={...e,lightRole:t,lightRoleTouched:!0})}_setMarkerGlowMode(t){const e=this._markerDialog;if(!e)return;if("auto"===t)return void(this._markerDialog={...e,glowMode:t,glowTouched:!0});const i=this._markerSpatialSource(e),s=cs(i?this._planHass.states[i.eid]:void 0,null,this._fillColors.glow_light.c),o=!e.glowColorDrafted,n="fixed"===t&&!e.glowBrightnessDrafted;this._markerDialog={...e,glowMode:t,glowColor:o?s.c:e.glowColor,glowBrightness:n?Math.max(1,Math.round(100*s.bri)):e.glowBrightness,glowColorDrafted:!0,glowBrightnessDrafted:e.glowBrightnessDrafted||"fixed"===t,glowTouched:!0}}_renderMarkerDialog(){const t=this._markerDialog,e="virtual"===t.bindingMode,i=this._bindingCandidates(),s=this._bindingEntities(t.binding),o=e?null:this._bindingStatus(t.binding),n=!e&&this._bindingHasHaPage(t.binding),r=this._markerPreviewDevice(t),a=r?Yi(this._serverCfg?.spaces.find(t=>t.id===r.space)):null,l=r?Zo(this._planHass,r,{liveStates:!1!==this._config?.live_states,showTemperature:!1!==this._config?.show_temperature,showSignal:a?.showLqi??!1!==this._config?.show_signal,designPreview:!0,activityRuntime:this._activityRt.get(r.id)}):null,c=this._markerAutoHasSpatialSource(t),h=this._markerSpatialSource(t),d="never"===t.lightRole||!h,p="never"===t.lightRole?this._t("marker.glow_disabled_never"):"auto"!==t.lightRole||c?this._t("marker.glow_disabled_no_entity"):this._t("marker.glow_disabled_auto"),u=(()=>{if(e)return null;const s=i.find(e=>e.value===t.binding);if(s)return s.label;const[o,n]=t.binding.split(":");return"device"===o?this._fullRegistryHass.devices[n]?.name_by_user||this._fullRegistryHass.devices[n]?.name||n:this._fullRegistryHass.entities[n]?.name||this.hass.states[n]?.attributes?.friendly_name||n})();return B``}_markerDraft(t){if("ha"===t.bindingMode&&(!t.binding||"virtual"===t.binding))return null;const e=Ds(t.room),i=yi(t.binding,t.devId,()=>"__hp_device_preview__"),s=this._markers.find(e=>e.id===i||e.id===t.devId),n=yn(t.binding,t.controls,this._bindingEntities(t.binding)),o={id:i,binding:t.binding,name:t.name.trim()||null,icon:t.icon||null,display:"badge"!==t.display?t.display:null,ripple_color:"icon_ripple"===t.display&&t.rippleColor?t.rippleColor:null,ripple_size:"icon_ripple"===t.display&&3!==t.rippleSize?t.rippleSize:null,size:1!==t.size?t.size:null,angle:t.angle||null,tap_action:t.tapAction||null,tap_target:"run"===t.tapAction&&t.tapTarget||null,tap_confirm:!!t.tapConfirm||null,controls:n.length?n:null,...this._markerLightFields(t),use_climate_temp:!!t.useClimateTemp||null,glow_radius_cm:(()=>{const e=Vc(t.glowRadius);return null==e||e<=0?null:Math.round(this._imperial?30.48*e:100*e)})(),model:t.model.trim()||null,link:t.link.trim()||null,description:t.description.trim()||null,pdfs:t.pdfs,hidden:t.hideFromPlan,vacuum:s?.vacuum||null};return("virtual"===t.binding||t.room)&&(o.space=e?.space||("virtual"===t.binding?this._space:null),o.area=e?.area||null,o.room_id=e?.roomId||null),o}_markerPreviewDevice(t){const e=this._markerDraft(t);if(!e)return null;const i=`${this._haRegistry.revision}\n${JSON.stringify(e)}`;if(this._markerPreviewMemo?.key===i)return this._markerPreviewMemo.device;const s=function(t){const{marker:e,...i}=t;return Nn({...i,markers:[e]}).find(t=>t.id===e.id)||null}({hass:this.hass,registry:this._haRegistry,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),marker:e,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||this._space,loc:t=>this._t(t),iconRules:this._iconRules});return this._markerPreviewMemo={key:i,device:s},s}_markerSpatialSource(t){const e=this._markerPreviewDevice(t);return e?Sn(xn(this._planHass,[{...e,hidden:!1}])):null}_markerAutoHasSpatialSource(t){const e={...t,lightRole:"auto",lightRoleTouched:!0},i=this._markerPreviewDevice(e);return!!i&&kn(this._planHass,{...i})}_setMarkerLightRole(t){const e=this._markerDialog;e&&(this._markerDialog={...e,lightRole:t,lightRoleTouched:!0})}_setMarkerGlowMode(t){const e=this._markerDialog;if(!e)return;if("auto"===t)return void(this._markerDialog={...e,glowMode:t,glowTouched:!0});const i=this._markerSpatialSource(e),s=cs(i?this._planHass.states[i.eid]:void 0,null,this._fillColors.glow_light.c),n=!e.glowColorDrafted,o="fixed"===t&&!e.glowBrightnessDrafted;this._markerDialog={...e,glowMode:t,glowColor:n?s.c:e.glowColor,glowBrightness:o?Math.max(1,Math.round(100*s.bri)):e.glowBrightness,glowColorDrafted:!0,glowBrightnessDrafted:e.glowBrightnessDrafted||"fixed"===t,glowTouched:!0}}_renderMarkerDialog(){const t=this._markerDialog,e="virtual"===t.bindingMode,i=this._bindingCandidates(),s=this._bindingEntities(t.binding),n=e?null:this._bindingStatus(t.binding),o=!e&&this._bindingHasHaPage(t.binding),r=this._markerPreviewDevice(t),a=r?Yi(this._serverCfg?.spaces.find(t=>t.id===r.space)):null,l=r?Zn(this._planHass,r,{liveStates:!1!==this._config?.live_states,showTemperature:!1!==this._config?.show_temperature,showSignal:a?.showLqi??!1!==this._config?.show_signal,designPreview:!0,activityRuntime:this._activityRt.get(r.id)}):null,c=this._markerAutoHasSpatialSource(t),h=this._markerSpatialSource(t),d="never"===t.lightRole||!h,p="never"===t.lightRole?this._t("marker.glow_disabled_never"):"auto"!==t.lightRole||c?this._t("marker.glow_disabled_no_entity"):this._t("marker.glow_disabled_auto"),u=(()=>{if(e)return null;const s=i.find(e=>e.value===t.binding);if(s)return s.label;const[n,o]=t.binding.split(":");return"device"===n?this._fullRegistryHass.devices[o]?.name_by_user||this._fullRegistryHass.devices[o]?.name||o:this._fullRegistryHass.entities[o]?.name||this.hass.states[o]?.attributes?.friendly_name||o})();return B`this._markerDialog=null}>
- ${"ha_disabled"===o?.kind?B`
+ ${"ha_disabled"===n?.kind?B`
- ${this._t(`marker.ha_disabled_${o.reason}`)} - ${n?B``:W} -
`:"unverified"===o?.kind&&t.binding?B`
+ `:G} +
`:"unverified"===n?.kind&&t.binding?B`
${this._t("marker.ha_registry_limited")} -
`:W} +
`:G}
@@ -5223,7 +5296,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
${"ha"===t.bindingMode?B` ${t.bindingOpen?B`
@@ -5232,12 +5305,12 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 @input=${e=>this._markerDialog={...t,bindingFilter:e.target.value}} />
${i.map(e=>B`
this._markerDialog={...t,binding:e.value,bindingOpen:!1,controls:vo(e.value,t.controls,this._bindingEntities(e.value)),autoIcon:this._autoIconForBinding(e.value)}}> + @click=${()=>this._markerDialog={...t,binding:e.value,bindingOpen:!1,controls:vn(e.value,t.controls,this._bindingEntities(e.value)),autoIcon:this._autoIconForBinding(e.value)}}> ${e.label}${e.sub}
`)} - ${i.length?W:B`
${this._t("marker.nothing_found")}
`} + ${i.length?G:B`
${this._t("marker.nothing_found")}
`}
-
`:W}`:W} +
`:G}`:G}
@@ -5256,22 +5329,22 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 ${"run"===t.tapAction?(()=>{const e=t.runFilter.trim().toLowerCase(),i=this._runCandidates().filter(t=>!e||t.label.toLowerCase().includes(e)||t.value.includes(e)),s=t.tapTarget?this._runCandidates().find(e=>e.value===t.tapTarget):null;return B` - ${t.tapTarget&&!s?B`
${this._t("marker.run_target_gone",{id:t.tapTarget})}
`:W} + ${t.tapTarget&&!s?B`
${this._t("marker.run_target_gone",{id:t.tapTarget})}
`:G} {t.target.select()}} @input=${e=>this._markerDialog={...t,runFilter:e.target.value,tapTarget:""}} /> - ${s?W:B`
+ ${s?G:B`
${i.slice(0,40).map(e=>B`
this._markerDialog={...t,tapTarget:e.value,runFilter:""}}> ${e.label}${e.sub}
`)} - ${i.length?W:B`
${this._t("marker.nothing_found")}
`} -
`}`})():W} + ${i.length?G:B`
${this._t("marker.nothing_found")}
`} +
`}`})():G} ${"run"===t.tapAction||"toggle"===t.tapAction||"cover"===t.tapAction||!t.tapAction&&"toggle"===t.defaultTap?B``:W} + `:G}
${this._t("marker.controls_hint")}
@@ -5280,23 +5353,23 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 ${this.hass.states[e]?.attributes?.friendly_name||e} this._markerDialog={...t,controls:t.controls.filter(t=>t!==e)}}> `)} - `:W} + `:G} this._markerDialog={...t,controlsFilter:e.target.value}} /> ${t.controlsFilter.trim()?B`
- ${Object.keys(this.hass.states).filter(e=>vo(t.binding,[e],s).length>0&&!t.controls.includes(e)&&this._planEntityAvailable(e)).filter(e=>{const i=t.controlsFilter.trim().toLowerCase(),s=String(this.hass.states[e]?.attributes?.friendly_name||"");return e.toLowerCase().includes(i)||s.toLowerCase().includes(i)}).slice(0,8).map(e=>B``)} -
`:W} + `:G} ${this._bindingHasClimate(t.binding)?B``:W} + `:G}
${this._t("marker.light_role_label")}

${this._t("marker.light_role_tip")}

@@ -5314,7 +5387,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
+ aria-describedby=${d?"marker-glow-disabled-hint":G}> ${this._t("marker.glow_color_label")}

${this._t("marker.glow_color_tip")}

@@ -5339,9 +5412,9 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 ${"fixed"===t.glowMode?B` ${this._t("marker.glow_brightness")} ${this._rangeInput(1,100,1,t.glowBrightness,e=>{this._markerDialog={...t,glowBrightness:e,glowBrightnessDrafted:!0,glowTouched:!0}},d,this._t("marker.glow_brightness"))} - ${Math.round(t.glowBrightness)}%`:W} -
`:W} - ${"fixed"===t.glowMode?B`

${this._t("marker.glow_brightness_hint")}

`:W} + ${Math.round(t.glowBrightness)}%`:G} + `:G} + ${"fixed"===t.glowMode?B`

${this._t("marker.glow_brightness_hint")}

`:G}
${p} -

`:W} +

`:G} ${customElements.get("ha-icon-picker")?B`this._markerDialog={...t,icon:t.autoIcon}}> ${this._t("marker.icon_pin_auto")} -

`:W} +

`:G} this._spaceDialog={...t,source:"draw"}} /> @@ -5496,9 +5569,9 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
- {const i=Hc(e.target.value);this._spaceDialog={...t,cellCm:null!=i&&i>0?Math.max(.1,Math.min(Fc,i)):t.cellCm}}} /> + @input=${e=>{const i=Vc(e.target.value);this._spaceDialog={...t,cellCm:null!=i&&i>0?Math.max(.1,Math.min(jc,i)):t.cellCm}}} /> ${this._t("space.scale_unit")}
@@ -5552,20 +5625,20 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 - ${"static"===(t.bgMode??Wn(this._settings,{}))?B` + ${"static"===(t.bgMode??Go(this._settings,{}))?B`
this._spaceDialog={...t,bgColor:e.target.value}} /> ${t.bgColor?B``:B`${this._t("space.bg_inherited")}`} -
`:W} +
`:G}
{const i=e.target.value.trim(),s=""===i?null:Math.round(Number(i));this._spaceDialog={...t,northDeg:null!==s&&Number.isFinite(s)?Math.min(359,Math.max(0,s)):null}}} /> - ${null===t.northDeg?this._t("space.north_inherited",{v:null===Gn(this._settings,{})?"—":String(Gn(this._settings,{}))+"°"}):"°"} + ${null===t.northDeg?this._t("space.north_inherited",{v:null===jo(this._settings,{})?"—":String(jo(this._settings,{}))+"°"}):"°"}
{const i=Hc(e.target.value);null!=i&&(this._spaceDialog={...t,tempMin:i})}} /> + @input=${e=>{const i=Vc(e.target.value);null!=i&&(this._spaceDialog={...t,tempMin:i})}} /> – {const i=Hc(e.target.value);null!=i&&(this._spaceDialog={...t,tempMax:i})}} /> + @input=${e=>{const i=Vc(e.target.value);null!=i&&(this._spaceDialog={...t,tempMax:i})}} /> °C - `:W} + `:G} ${"custom"===e&&"custom"===t.fillMode?B`
${this._t("space.custom_fill")} {this._spaceDialog={...t,customFill:{c:e.detail.color,a:e.detail.opacity}}}}> ${t.customFill?B``:W} -
`:W}`)} + ${this._t("btn.reset")}`:G} + `:G}`)} -
- ${"edit"===t.mode?B``:W} - - ${this._importTotal>0&&"create"===t.mode?B``:W} - - + - `}_renderMergeDialog(){const t=this._mergeDialog,e=this._spaceModel().rooms,i=(i,s)=>{const o=e.find(t=>t.id===i),n=o?.area?this.hass.areas[o.area]?.name:null;return B`
- `:W}`:W}`}_renderRoomDialog(){const t=!!this._roomEditId,e=!!this._areaSel||!!this._nameSel.trim(),i=Yi(this._curSpaceCfg),s=this._roomFill||i.fill,o=this._roomCustomFill||i.customFill,n=[...this._freeAreas];if(t&&this._areaSel&&!n.some(t=>t.area_id===this._areaSel)){const t=this.hass.areas[this._areaSel];t&&n.unshift(t)}return B``:G}`:G}`}_renderRoomDialog(){const t=!!this._roomEditId,e=!!this._areaSel||!!this._nameSel.trim(),i=Yi(this._curSpaceCfg),s=this._roomFill||i.fill,n=this._roomCustomFill||i.customFill,o=[...this._freeAreas];if(t&&this._areaSel&&!o.some(t=>t.area_id===this._areaSel)){const t=this.hass.areas[this._areaSel];t&&o.unshift(t)}return B`
@@ -5684,7 +5760,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 @@ -5699,11 +5775,11 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 {this._roomCustomFill={c:t.detail.color,a:t.detail.opacity}}}> - ${this._roomCustomFill?B``:W} -
`:W} + ${this._roomCustomFill?B``:G} + `:G} ${this._renderRoomSource("temp")} ${this._renderRoomSource("hum")} @@ -5725,11 +5801,11 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 ${t?B``:B`${this._pendingSplit?W:B``:B`${this._pendingSplit?G:B``} `} -
`}}ch.properties={_hdrH:{state:!0},_booting:{state:!0},_bootFading:{state:!0},_bootSoft:{state:!0},_tapConfirm:{state:!0},hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_hoverRoom:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_wallDialog:{state:!0},_drawWallField:{state:!0},_activeDraftId:{state:!0},_physicalSel:{state:!0},_physicalDialog:{state:!0},_physicalDrag:{state:!0},_physicalRotate:{state:!0},_duplicateColumnId:{state:!0},_rszSel:{state:!0},_rszLive:{state:!0},_opMeasure:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_openWallAnchor:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorEraseConfirm:{state:!0},_decorTextDialog:{state:!0},_decorShapeDialog:{state:!0},_backdropDialog:{state:!0},_furnPalette:{state:!0},_bdDrag:{state:!0},_dtBox:{state:!0},_dtDrag:{state:!0},_kioskDialog:{state:!0},_vacFit:{state:!0},_vacAllCamerasFor:{state:!0},_vacCalConfirm:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomCustomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_alignDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},ch.ZOOM_MAX=8,ch.ZOOM_MIN=1/3,ch._touchSeen=!1,ch._noHoverMq="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,ch.styles=[At,mc],customElements.get("houseplan-card")||customElements.define("houseplan-card",ch),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info(`%c HOUSEPLAN-CARD %c v${Ec} `,"background:#3ea6ff;color:#04121f;font-weight:700",""); +
`}}uh.properties={_hdrH:{state:!0},_booting:{state:!0},_bootFading:{state:!0},_bootSoft:{state:!0},_continuityEpoch:{state:!0},_tapConfirm:{state:!0},hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_hoverRoom:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_wallDialog:{state:!0},_drawWallField:{state:!0},_activeDraftId:{state:!0},_physicalSel:{state:!0},_physicalDialog:{state:!0},_physicalDrag:{state:!0},_physicalRotate:{state:!0},_duplicateColumnId:{state:!0},_rszSel:{state:!0},_rszLive:{state:!0},_opMeasure:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_openWallAnchor:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorEraseConfirm:{state:!0},_decorTextDialog:{state:!0},_decorShapeDialog:{state:!0},_backdropDialog:{state:!0},_furnPalette:{state:!0},_bdDrag:{state:!0},_dtBox:{state:!0},_dtDrag:{state:!0},_kioskDialog:{state:!0},_vacFit:{state:!0},_vacAllCamerasFor:{state:!0},_vacCalConfirm:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomCustomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_alignDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},uh.ZOOM_MAX=8,uh.ZOOM_MIN=1/3,uh._touchSeen=!1,uh._noHoverMq="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,uh.styles=[At,$c],customElements.get("houseplan-card")||customElements.define("houseplan-card",uh),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info(`%c HOUSEPLAN-CARD %c v${Bc} `,"background:#3ea6ff;color:#04121f;font-weight:700",""); diff --git a/dist/houseplan-card.js b/dist/houseplan-card.js index 6655bb0d..569edbba 100755 --- a/dist/houseplan-card.js +++ b/dist/houseplan-card.js @@ -1,5 +1,5 @@ -globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96fbc26ab50eb69b49466b3c7bab33";const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),s=new WeakMap;let o=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const i=this.t;if(e&&void 0===t){const e=void 0!==i&&1===i.length;e&&(t=s.get(i)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&s.set(i,t))}return t}toString(){return this.cssText}};const n=(t,...e)=>{const s=1===t.length?t[0]:e.reduce((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1],t[0]);return new o(s,t,i)},r=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new o("string"==typeof t?t:t+"",void 0,i))(e)})(t):t,{is:a,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:d,getPrototypeOf:p}=Object,u=globalThis,_=u.trustedTypes,m=_?_.emptyScript:"",g=u.reactiveElementPolyfillSupport,f=(t,e)=>t,v={toAttribute(t,e){switch(e){case Boolean:t=t?m:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},y=(t,e)=>!a(t,e),b={attribute:!0,type:String,converter:v,reflect:!1,useDefault:!1,hasChanged:y};Symbol.metadata??=Symbol("metadata"),u.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=b){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(t,i,e);void 0!==s&&l(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){const{get:s,set:o}=c(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:s,set(e){const n=s?.call(this);o?.call(this,e),this.requestUpdate(t,n,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??b}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const t=p(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const t=this.properties,e=[...h(t),...d(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(r(t))}else void 0!==t&&e.push(r(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.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(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(e)i.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of s){const s=document.createElement("style"),o=t.litNonce;void 0!==o&&s.setAttribute("nonce",o),s.textContent=e.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$ET(t,e){const i=this.constructor.elementProperties.get(t),s=this.constructor._$Eu(t,i);if(void 0!==s&&!0===i.reflect){const o=(void 0!==i.converter?.toAttribute?i.converter:v).toAttribute(e,i.type);this._$Em=t,null==o?this.removeAttribute(s):this.setAttribute(s,o),this._$Em=null}}_$AK(t,e){const i=this.constructor,s=i._$Eh.get(t);if(void 0!==s&&this._$Em!==s){const t=i.getPropertyOptions(s),o="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:v;this._$Em=s;const n=o.fromAttribute(e,t.type);this[s]=n??this._$Ej?.get(s)??n,this._$Em=null}}requestUpdate(t,e,i,s=!1,o){if(void 0!==t){const n=this.constructor;if(!1===s&&(o=this[t]),i??=n.getPropertyOptions(t),!((i.hasChanged??y)(o,e)||i.useDefault&&i.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,i))))return;this.C(t,e,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:i,reflect:s,wrapped:o},n){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),!0!==o||void 0!==n)||(this._$AL.has(t)||(this.hasUpdated||i||(e=void 0),this._$AL.set(t,e)),!0===s&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,i]of t){const{wrapped:t}=i,s=this[e];!0!==t||this._$AL.has(e)||void 0===s||this.C(e,void 0,i,s)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};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,$=t=>t,x=k.trustedTypes,S=x?x.createPolicy("lit-html",{createHTML:t=>t}):void 0,M="$lit$",C=`lit$${Math.random().toFixed(9).slice(2)}$`,D="?"+C,T=`<${D}>`,P=document,R=()=>P.createComment(""),A=t=>null===t||"object"!=typeof t&&"function"!=typeof t,z=Array.isArray,I="[ \t\n\f\r]",E=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,N=/-->/g,F=/>/g,O=RegExp(`>|${I}(?:([^\\s"'>=/]+)(${I}*=${I}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),L=/'/g,H=/"/g,q=/^(?:script|style|textarea|title)$/i,U=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),B=U(1),j=U(2),G=Symbol.for("lit-noChange"),W=Symbol.for("lit-nothing"),V=new WeakMap,K=P.createTreeWalker(P,129);function Y(t,e){if(!z(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const J=(t,e)=>{const i=t.length-1,s=[];let o,n=2===e?"":3===e?"":"",r=E;for(let e=0;e"===l[0]?(r=o??E,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?O:'"'===l[3]?H:L):r===H||r===L?r=O:r===N||r===F?r=E:(r=O,o=void 0);const d=r===O&&t[e+1].startsWith("/>")?" ":"";n+=r===E?i+T:c>=0?(s.push(a),i.slice(0,c)+M+i.slice(c)+C+d):i+C+(-2===c?e:d)}return[Y(t,n+(t[i]||"")+(2===e?"":3===e?"":"")),s]};class Z{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let o=0,n=0;const r=t.length-1,a=this.parts,[l,c]=J(t,e);if(this.el=Z.createElement(l,i),K.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(s=K.nextNode())&&a.length0){s.textContent=x?x.emptyScript:"";for(let i=0;iz(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==W&&A(this._$AH)?this._$AA.nextSibling.data=t:this.T(P.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:i}=t,s="number"==typeof i?this._$AC(t):(void 0===i.el&&(i.el=Z.createElement(Y(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(e);else{const t=new Q(s,this),i=t.u(this.options);t.p(e),this.T(i),this._$AH=t}}_$AC(t){let e=V.get(t.strings);return void 0===e&&V.set(t.strings,e=new Z(t)),e}k(t){z(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const o of t)s===e.length?e.push(i=new tt(this.O(R()),this.O(R()),this,this.options)):i=e[s],i._$AI(o),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=W}_$AI(t,e=this,i,s){const o=this.strings;let n=!1;if(void 0===o)t=X(this,t,e,0),n=!A(t)||t!==this._$AH&&t!==G,n&&(this._$AH=t);else{const s=t;let r,a;for(t=o[0],r=0;r{const s=i?.renderBefore??e;let o=s._$litPart$;if(void 0===o){const t=i?.renderBefore??null;s._$litPart$=o=new tt(e.insertBefore(R(),t),t,void 0,i??{})}return o._$AI(t),o})(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return G}};ct._$litElement$=!0,ct.finalized=!0,lt.litElementHydrateSupport?.({LitElement:ct});const ht=lt.litElementPolyfillSupport;ht?.({LitElement:ct}),(lt.litElementVersions??=[]).push("4.2.2");const dt=2;let pt=class{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}};const{I:ut}=rt,_t=t=>t,mt=()=>document.createComment(""),gt=(t,e,i)=>{const s=t._$AA.parentNode,o=void 0===e?t._$AB:e._$AA;if(void 0===i){const e=s.insertBefore(mt(),o),n=s.insertBefore(mt(),o);i=new ut(e,n,t,t.options)}else{const e=i._$AB.nextSibling,n=i._$AM,r=n!==t;if(r){let e;i._$AQ?.(t),i._$AM=t,void 0!==i._$AP&&(e=t._$AU)!==n._$AU&&i._$AP(e)}if(e!==o||r){let t=i._$AA;for(;t!==e;){const e=_t(t).nextSibling;_t(s).insertBefore(t,o),t=e}}}return i},ft=(t,e,i=t)=>(t._$AI(e,i),t),vt={},yt=(t,e=vt)=>t._$AH=e,bt=t=>{t._$AR(),t._$AA.remove()},wt=(t,e,i)=>{const s=new Map;for(let o=e;o<=i;o++)s.set(t[o],o);return s},kt=(t=>(...e)=>({_$litDirective$:t,values:e}))(class extends pt{constructor(t){if(super(t),t.type!==dt)throw Error("repeat() can only be used in text expressions")}dt(t,e,i){let s;void 0===i?i=e:void 0!==e&&(s=e);const o=[],n=[];let r=0;for(const e of t)o[r]=s?s(e,r):r,n[r]=i(e,r),r++;return{values:n,keys:o}}render(t,e,i){return this.dt(t,e,i).values}update(t,[e,i,s]){const o=(t=>t._$AH)(t),{values:n,keys:r}=this.dt(e,i,s);if(!Array.isArray(o))return this.ut=r,n;const a=this.ut??=[],l=[];let c,h,d=0,p=o.length-1,u=0,_=n.length-1;for(;d<=p&&u<=_;)if(null===o[d])d++;else if(null===o[p])p--;else if(a[d]===r[u])l[u]=ft(o[d],n[u]),d++,u++;else if(a[p]===r[_])l[_]=ft(o[p],n[_]),p--,_--;else if(a[d]===r[_])l[_]=ft(o[d],n[_]),gt(t,l[_+1],o[d]),d++,_--;else if(a[p]===r[u])l[u]=ft(o[p],n[u]),gt(t,o[d],o[p]),p--,u++;else if(void 0===c&&(c=wt(r,u,_),h=wt(a,d,p)),c.has(a[d]))if(c.has(a[p])){const e=h.get(r[u]),i=void 0!==e?o[e]:null;if(null===i){const e=gt(t,o[d]);ft(e,n[u]),l[u]=e}else l[u]=ft(i,n[u]),gt(t,o[d],i),o[e]=null;u++}else bt(o[p]),p--;else bt(o[d]),d++;for(;u<=_;){const e=gt(t,l[_+1]);ft(e,n[u]),l[u++]=e}for(;d<=p;){const t=o[d++];null!==t&&bt(t)}return this.ut=r,yt(t,l),G}}),$t=new WeakMap;let xt=0;class St extends ct{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._titleId="hp-dialog-title-"+ ++xt,this._focusInitial=()=>{const t=this.querySelector("[autofocus]")||this._focusableElements()[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=t=>{if("Escape"===t.key)return t.preventDefault(),t.stopImmediatePropagation(),void this._requestClose();if("Tab"!==t.key||this._useHaDialog)return;const e=this.renderRoot.querySelector(".close"),i=e?[e,...this._focusableElements()]:this._focusableElements();if(!i.length)return t.preventDefault(),void this.renderRoot.querySelector(".surface")?.focus({preventScroll:!0});const s=this._deepActiveElement(),o=i[0],n=i[i.length-1];!t.shiftKey||s!==o&&i.includes(s)?t.shiftKey||s!==n||(t.preventDefault(),o.focus()):(t.preventDefault(),n.focus())},this._onFallbackCancel=t=>{t.preventDefault(),this._requestClose()},this._onFallbackClick=t=>{this.dismissOnScrim&&t.target===t.currentTarget&&this._requestClose()}}connectedCallback(){super.connectedCallback(),this._opener=this._deepActiveElement();const t=this.getRootNode();this._focusRoot=t;const e=$t.get(t)||{dialogs:new Set,opener:this._opener};e.dialogs.add(this),$t.set(t,e),this._useHaDialog=!!customElements.get("ha-dialog"),this.addEventListener("keydown",this._onKeyDown,!0)}disconnectedCallback(){this.removeEventListener("keydown",this._onKeyDown,!0);const t=this._focusRoot,e=this._opener;this._opener=null,this._focusRoot=null;const i=t?$t.get(t):void 0;i?.dialogs.delete(this),super.disconnectedCallback(),t&&i&&requestAnimationFrame(()=>{const i=$t.get(t);if(!i)return;if(!i.dialogs.size)return i.opener?.isConnected&&i.opener.focus({preventScroll:!0}),void $t.delete(t);const s=e?.closest("hp-dialog");e?.isConnected&&s&&i.dialogs.has(s)&&e.focus({preventScroll:!0})})}firstUpdated(t){if(super.firstUpdated(t),!this._useHaDialog){const t=this.renderRoot.querySelector("dialog");t&&!t.open&&t.showModal()}queueMicrotask(()=>this._focusInitial())}_deepActiveElement(){let t=document.activeElement;for(;t?.shadowRoot?.activeElement;)t=t.shadowRoot.activeElement;return t&&t!==document.body?t:null}_focusableElements(){const t=["[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(",");return Array.from(this.querySelectorAll(t)).filter(t=>{const e=getComputedStyle(t);return!t.hidden&&"none"!==e.display&&"hidden"!==e.visibility})}render(){const t=B` - ${this.icon?B``:W} +globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="0f963f24ce2ba631daf1931ac9ffd44c7326b1bfee5fca1dac9b3a1678c892f7";const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),s=new WeakMap;let n=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const i=this.t;if(e&&void 0===t){const e=void 0!==i&&1===i.length;e&&(t=s.get(i)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&s.set(i,t))}return t}toString(){return this.cssText}};const o=(t,...e)=>{const s=1===t.length?t[0]:e.reduce((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1],t[0]);return new n(s,t,i)},r=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new n("string"==typeof t?t:t+"",void 0,i))(e)})(t):t,{is:a,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:d,getPrototypeOf:p}=Object,u=globalThis,_=u.trustedTypes,m=_?_.emptyScript:"",g=u.reactiveElementPolyfillSupport,f=(t,e)=>t,v={toAttribute(t,e){switch(e){case Boolean:t=t?m:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},y=(t,e)=>!a(t,e),b={attribute:!0,type:String,converter:v,reflect:!1,useDefault:!1,hasChanged:y};Symbol.metadata??=Symbol("metadata"),u.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=b){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(t,i,e);void 0!==s&&l(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){const{get:s,set:n}=c(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:s,set(e){const o=s?.call(this);n?.call(this,e),this.requestUpdate(t,o,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??b}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const t=p(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const t=this.properties,e=[...h(t),...d(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(r(t))}else void 0!==t&&e.push(r(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.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(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(e)i.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of s){const s=document.createElement("style"),n=t.litNonce;void 0!==n&&s.setAttribute("nonce",n),s.textContent=e.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$ET(t,e){const i=this.constructor.elementProperties.get(t),s=this.constructor._$Eu(t,i);if(void 0!==s&&!0===i.reflect){const n=(void 0!==i.converter?.toAttribute?i.converter:v).toAttribute(e,i.type);this._$Em=t,null==n?this.removeAttribute(s):this.setAttribute(s,n),this._$Em=null}}_$AK(t,e){const i=this.constructor,s=i._$Eh.get(t);if(void 0!==s&&this._$Em!==s){const t=i.getPropertyOptions(s),n="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:v;this._$Em=s;const o=n.fromAttribute(e,t.type);this[s]=o??this._$Ej?.get(s)??o,this._$Em=null}}requestUpdate(t,e,i,s=!1,n){if(void 0!==t){const o=this.constructor;if(!1===s&&(n=this[t]),i??=o.getPropertyOptions(t),!((i.hasChanged??y)(n,e)||i.useDefault&&i.reflect&&n===this._$Ej?.get(t)&&!this.hasAttribute(o._$Eu(t,i))))return;this.C(t,e,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:i,reflect:s,wrapped:n},o){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,o??e??this[t]),!0!==n||void 0!==o)||(this._$AL.has(t)||(this.hasUpdated||i||(e=void 0),this._$AL.set(t,e)),!0===s&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,i]of t){const{wrapped:t}=i,s=this[e];!0!==t||this._$AL.has(e)||void 0===s||this.C(e,void 0,i,s)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};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,$=t=>t,x=k.trustedTypes,S=x?x.createPolicy("lit-html",{createHTML:t=>t}):void 0,M="$lit$",C=`lit$${Math.random().toFixed(9).slice(2)}$`,D="?"+C,T=`<${D}>`,R=document,P=()=>R.createComment(""),A=t=>null===t||"object"!=typeof t&&"function"!=typeof t,z=Array.isArray,I="[ \t\n\f\r]",E=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,F=/-->/g,N=/>/g,O=RegExp(`>|${I}(?:([^\\s"'>=/]+)(${I}*=${I}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),L=/'/g,H=/"/g,q=/^(?:script|style|textarea|title)$/i,U=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),B=U(1),W=U(2),j=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),V=new WeakMap,K=R.createTreeWalker(R,129);function Y(t,e){if(!z(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const J=(t,e)=>{const i=t.length-1,s=[];let n,o=2===e?"":3===e?"":"",r=E;for(let e=0;e"===l[0]?(r=n??E,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?O:'"'===l[3]?H:L):r===H||r===L?r=O:r===F||r===N?r=E:(r=O,n=void 0);const d=r===O&&t[e+1].startsWith("/>")?" ":"";o+=r===E?i+T:c>=0?(s.push(a),i.slice(0,c)+M+i.slice(c)+C+d):i+C+(-2===c?e:d)}return[Y(t,o+(t[i]||"")+(2===e?"":3===e?"":"")),s]};class Z{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let n=0,o=0;const r=t.length-1,a=this.parts,[l,c]=J(t,e);if(this.el=Z.createElement(l,i),K.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(s=K.nextNode())&&a.length0){s.textContent=x?x.emptyScript:"";for(let i=0;iz(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==G&&A(this._$AH)?this._$AA.nextSibling.data=t:this.T(R.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:i}=t,s="number"==typeof i?this._$AC(t):(void 0===i.el&&(i.el=Z.createElement(Y(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(e);else{const t=new Q(s,this),i=t.u(this.options);t.p(e),this.T(i),this._$AH=t}}_$AC(t){let e=V.get(t.strings);return void 0===e&&V.set(t.strings,e=new Z(t)),e}k(t){z(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const n of t)s===e.length?e.push(i=new tt(this.O(P()),this.O(P()),this,this.options)):i=e[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(t,e=this,i,s){const n=this.strings;let o=!1;if(void 0===n)t=X(this,t,e,0),o=!A(t)||t!==this._$AH&&t!==j,o&&(this._$AH=t);else{const s=t;let r,a;for(t=n[0],r=0;r{const s=i?.renderBefore??e;let n=s._$litPart$;if(void 0===n){const t=i?.renderBefore??null;s._$litPart$=n=new tt(e.insertBefore(P(),t),t,void 0,i??{})}return n._$AI(t),n})(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return j}};ct._$litElement$=!0,ct.finalized=!0,lt.litElementHydrateSupport?.({LitElement:ct});const ht=lt.litElementPolyfillSupport;ht?.({LitElement:ct}),(lt.litElementVersions??=[]).push("4.2.2");const dt=2;let pt=class{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}};const{I:ut}=rt,_t=t=>t,mt=()=>document.createComment(""),gt=(t,e,i)=>{const s=t._$AA.parentNode,n=void 0===e?t._$AB:e._$AA;if(void 0===i){const e=s.insertBefore(mt(),n),o=s.insertBefore(mt(),n);i=new ut(e,o,t,t.options)}else{const e=i._$AB.nextSibling,o=i._$AM,r=o!==t;if(r){let e;i._$AQ?.(t),i._$AM=t,void 0!==i._$AP&&(e=t._$AU)!==o._$AU&&i._$AP(e)}if(e!==n||r){let t=i._$AA;for(;t!==e;){const e=_t(t).nextSibling;_t(s).insertBefore(t,n),t=e}}}return i},ft=(t,e,i=t)=>(t._$AI(e,i),t),vt={},yt=(t,e=vt)=>t._$AH=e,bt=t=>{t._$AR(),t._$AA.remove()},wt=(t,e,i)=>{const s=new Map;for(let n=e;n<=i;n++)s.set(t[n],n);return s},kt=(t=>(...e)=>({_$litDirective$:t,values:e}))(class extends pt{constructor(t){if(super(t),t.type!==dt)throw Error("repeat() can only be used in text expressions")}dt(t,e,i){let s;void 0===i?i=e:void 0!==e&&(s=e);const n=[],o=[];let r=0;for(const e of t)n[r]=s?s(e,r):r,o[r]=i(e,r),r++;return{values:o,keys:n}}render(t,e,i){return this.dt(t,e,i).values}update(t,[e,i,s]){const n=(t=>t._$AH)(t),{values:o,keys:r}=this.dt(e,i,s);if(!Array.isArray(n))return this.ut=r,o;const a=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(a[d]===r[u])l[u]=ft(n[d],o[u]),d++,u++;else if(a[p]===r[_])l[_]=ft(n[p],o[_]),p--,_--;else if(a[d]===r[_])l[_]=ft(n[d],o[_]),gt(t,l[_+1],n[d]),d++,_--;else if(a[p]===r[u])l[u]=ft(n[p],o[u]),gt(t,n[d],n[p]),p--,u++;else if(void 0===c&&(c=wt(r,u,_),h=wt(a,d,p)),c.has(a[d]))if(c.has(a[p])){const e=h.get(r[u]),i=void 0!==e?n[e]:null;if(null===i){const e=gt(t,n[d]);ft(e,o[u]),l[u]=e}else l[u]=ft(i,o[u]),gt(t,n[d],i),n[e]=null;u++}else bt(n[p]),p--;else bt(n[d]),d++;for(;u<=_;){const e=gt(t,l[_+1]);ft(e,o[u]),l[u++]=e}for(;d<=p;){const t=n[d++];null!==t&&bt(t)}return this.ut=r,yt(t,l),j}}),$t=new WeakMap;let xt=0;class St extends ct{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._titleId="hp-dialog-title-"+ ++xt,this._focusInitial=()=>{const t=this.querySelector("[autofocus]")||this._focusableElements()[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=t=>{if("Escape"===t.key)return t.preventDefault(),t.stopImmediatePropagation(),void this._requestClose();if("Tab"!==t.key||this._useHaDialog)return;const e=this.renderRoot.querySelector(".close"),i=e?[e,...this._focusableElements()]:this._focusableElements();if(!i.length)return t.preventDefault(),void this.renderRoot.querySelector(".surface")?.focus({preventScroll:!0});const s=this._deepActiveElement(),n=i[0],o=i[i.length-1];!t.shiftKey||s!==n&&i.includes(s)?t.shiftKey||s!==o||(t.preventDefault(),n.focus()):(t.preventDefault(),o.focus())},this._onFallbackCancel=t=>{t.preventDefault(),this._requestClose()},this._onFallbackClick=t=>{this.dismissOnScrim&&t.target===t.currentTarget&&this._requestClose()}}connectedCallback(){super.connectedCallback(),this._opener=this._deepActiveElement();const t=this.getRootNode();this._focusRoot=t;const e=$t.get(t)||{dialogs:new Set,opener:this._opener};e.dialogs.add(this),$t.set(t,e),this._useHaDialog=!!customElements.get("ha-dialog"),this.addEventListener("keydown",this._onKeyDown,!0)}disconnectedCallback(){this.removeEventListener("keydown",this._onKeyDown,!0);const t=this._focusRoot,e=this._opener;this._opener=null,this._focusRoot=null;const i=t?$t.get(t):void 0;i?.dialogs.delete(this),super.disconnectedCallback(),t&&i&&requestAnimationFrame(()=>{const i=$t.get(t);if(!i)return;if(!i.dialogs.size)return i.opener?.isConnected&&i.opener.focus({preventScroll:!0}),void $t.delete(t);const s=e?.closest("hp-dialog");e?.isConnected&&s&&i.dialogs.has(s)&&e.focus({preventScroll:!0})})}firstUpdated(t){if(super.firstUpdated(t),!this._useHaDialog){const t=this.renderRoot.querySelector("dialog");t&&!t.open&&t.showModal()}queueMicrotask(()=>this._focusInitial())}_deepActiveElement(){let t=document.activeElement;for(;t?.shadowRoot?.activeElement;)t=t.shadowRoot.activeElement;return t&&t!==document.body?t:null}_focusableElements(){const t=["[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(",");return Array.from(this.querySelectorAll(t)).filter(t=>{const e=getComputedStyle(t);return!t.hidden&&"none"!==e.display&&"hidden"!==e.visibility})}render(){const t=B` + ${this.icon?B``:G} ${this.title} `;return this._useHaDialog?B` - `}}St.properties={title:{type:String},icon:{type:String},wide:{type:Boolean,reflect:!0},dismissOnScrim:{type:Boolean,attribute:"dismiss-on-scrim"},hass:{attribute:!1}},St.styles=n` + `}}St.properties={title:{type:String},icon:{type:String},wide:{type:Boolean,reflect:!0},dismissOnScrim:{type:Boolean,attribute:"dismiss-on-scrim"},hass:{attribute:!1}},St.styles=o` :host { display: contents; color: var(--primary-text-color, #e6e7eb); @@ -191,8 +191,8 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 display: flex; flex-direction: column; } - `,customElements.get("hp-dialog")||customElements.define("hp-dialog",St);const Mt=/^#[0-9a-fA-F]{6}$/;function Ct(t,e){return"string"==typeof t&&Mt.test(t)?t:e}const Dt="(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])",Tt=new RegExp(`^rgb\\(${Dt}, ${Dt}, ${Dt}\\)$`);function Pt(t){if(!Array.isArray(t)||t.length<3)return null;const e=t.slice(0,3);if(!e.every(t=>"number"==typeof t&&Number.isFinite(t)))return null;const[i,s,o]=e.map(t=>Math.round(Math.min(255,Math.max(0,t))));return`rgb(${i}, ${s}, ${o})`}class Rt extends ct{constructor(){super(...arguments),this.label="",this.opacityLabel="Opacity",this.color="#607d8b",this.opacity=1,this.disabled=!1,this.showOpacity=!0,this._open=!1,this._pickerRaf=0,this._outsidePointerDown=t=>{this._open&&!t.composedPath().includes(this)&&this._closePicker()},this._keyDown=t=>{this._open&&"Escape"===t.key&&(t.preventDefault(),t.stopImmediatePropagation(),this._closePicker(!0))},this._queuePickerPosition=()=>{this._open&&(this._pickerRaf&&cancelAnimationFrame(this._pickerRaf),this._pickerRaf=requestAnimationFrame(()=>{this._pickerRaf=0,this._positionPicker()}))}}connectedCallback(){super.connectedCallback(),document.addEventListener("pointerdown",this._outsidePointerDown,!0),document.addEventListener("scroll",this._queuePickerPosition,!0),window.addEventListener("resize",this._queuePickerPosition),this.addEventListener("keydown",this._keyDown,!0)}disconnectedCallback(){document.removeEventListener("pointerdown",this._outsidePointerDown,!0),document.removeEventListener("scroll",this._queuePickerPosition,!0),window.removeEventListener("resize",this._queuePickerPosition),this.removeEventListener("keydown",this._keyDown,!0),this._pickerRaf&&cancelAnimationFrame(this._pickerRaf),this._pickerRaf=0,super.disconnectedCallback()}_toggle(){this.disabled||(this._open?this._closePicker():(this._open=!0,this.updateComplete.then(()=>this._positionPicker())))}_closePicker(t=!1){const e=this.renderRoot.querySelector(".picker");if(e?.hidePopover)try{e.matches(":popover-open")&&e.hidePopover()}catch{}this._open=!1,t&&this.updateComplete.then(()=>this.renderRoot.querySelector(".trigger")?.focus())}_positionPicker(){if(!this._open)return;const t=this.renderRoot.querySelector(".trigger"),e=this.renderRoot.querySelector(".picker");if(!t||!e)return;if(e.style.visibility="hidden",e.showPopover)try{e.matches(":popover-open")||e.showPopover()}catch{}const i=t.getBoundingClientRect(),s=e.getBoundingClientRect(),o=Math.max(8,window.innerWidth-s.width-8);let n=i.left;n+s.width>window.innerWidth-8&&(n=i.right-s.width),n=Math.min(o,Math.max(8,n));const r=i.bottom+7,a=i.top-7-s.height;let l=r;l=r+s.height>window.innerHeight-8&&a>=8?a:Math.min(Math.max(8,l),Math.max(8,window.innerHeight-s.height-8)),e.style.left=`${Math.round(n)}px`,e.style.top=`${Math.round(l)}px`,e.style.visibility=""}_emit(t,e){const i=Number.isFinite(e)?e:Number(this.opacity)||0,s=Math.min(1,Math.max(0,i));this.color=t,this.opacity=s,this.dispatchEvent(new CustomEvent("hp-color-opacity-change",{detail:{color:t,opacity:s},bubbles:!0,composed:!0}))}render(){const t=Math.round(100*Math.min(1,Math.max(0,Number(this.opacity)||0))),e=Ct(this.color,"#607d8b"),i=this.showOpacity?`${this.label||"Color"}: ${e}, ${t}%`:`${this.label||"Color"}: ${e}`;return B` - ${this.label?B`${this.label}`:W} + `,customElements.get("hp-dialog")||customElements.define("hp-dialog",St);const Mt=/^#[0-9a-fA-F]{6}$/;function Ct(t,e){return"string"==typeof t&&Mt.test(t)?t:e}const Dt="(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])",Tt=new RegExp(`^rgb\\(${Dt}, ${Dt}, ${Dt}\\)$`);function Rt(t){if(!Array.isArray(t)||t.length<3)return null;const e=t.slice(0,3);if(!e.every(t=>"number"==typeof t&&Number.isFinite(t)))return null;const[i,s,n]=e.map(t=>Math.round(Math.min(255,Math.max(0,t))));return`rgb(${i}, ${s}, ${n})`}class Pt extends ct{constructor(){super(...arguments),this.label="",this.opacityLabel="Opacity",this.color="#607d8b",this.opacity=1,this.disabled=!1,this.showOpacity=!0,this._open=!1,this._pickerRaf=0,this._outsidePointerDown=t=>{this._open&&!t.composedPath().includes(this)&&this._closePicker()},this._keyDown=t=>{this._open&&"Escape"===t.key&&(t.preventDefault(),t.stopImmediatePropagation(),this._closePicker(!0))},this._queuePickerPosition=()=>{this._open&&(this._pickerRaf&&cancelAnimationFrame(this._pickerRaf),this._pickerRaf=requestAnimationFrame(()=>{this._pickerRaf=0,this._positionPicker()}))}}connectedCallback(){super.connectedCallback(),document.addEventListener("pointerdown",this._outsidePointerDown,!0),document.addEventListener("scroll",this._queuePickerPosition,!0),window.addEventListener("resize",this._queuePickerPosition),this.addEventListener("keydown",this._keyDown,!0)}disconnectedCallback(){document.removeEventListener("pointerdown",this._outsidePointerDown,!0),document.removeEventListener("scroll",this._queuePickerPosition,!0),window.removeEventListener("resize",this._queuePickerPosition),this.removeEventListener("keydown",this._keyDown,!0),this._pickerRaf&&cancelAnimationFrame(this._pickerRaf),this._pickerRaf=0,super.disconnectedCallback()}_toggle(){this.disabled||(this._open?this._closePicker():(this._open=!0,this.updateComplete.then(()=>this._positionPicker())))}_closePicker(t=!1){const e=this.renderRoot.querySelector(".picker");if(e?.hidePopover)try{e.matches(":popover-open")&&e.hidePopover()}catch{}this._open=!1,t&&this.updateComplete.then(()=>this.renderRoot.querySelector(".trigger")?.focus())}_positionPicker(){if(!this._open)return;const t=this.renderRoot.querySelector(".trigger"),e=this.renderRoot.querySelector(".picker");if(!t||!e)return;if(e.style.visibility="hidden",e.showPopover)try{e.matches(":popover-open")||e.showPopover()}catch{}const i=t.getBoundingClientRect(),s=e.getBoundingClientRect(),n=Math.max(8,window.innerWidth-s.width-8);let o=i.left;o+s.width>window.innerWidth-8&&(o=i.right-s.width),o=Math.min(n,Math.max(8,o));const r=i.bottom+7,a=i.top-7-s.height;let l=r;l=r+s.height>window.innerHeight-8&&a>=8?a:Math.min(Math.max(8,l),Math.max(8,window.innerHeight-s.height-8)),e.style.left=`${Math.round(o)}px`,e.style.top=`${Math.round(l)}px`,e.style.visibility=""}_emit(t,e){const i=Number.isFinite(e)?e:Number(this.opacity)||0,s=Math.min(1,Math.max(0,i));this.color=t,this.opacity=s,this.dispatchEvent(new CustomEvent("hp-color-opacity-change",{detail:{color:t,opacity:s},bubbles:!0,composed:!0}))}render(){const t=Math.round(100*Math.min(1,Math.max(0,Number(this.opacity)||0))),e=Ct(this.color,"#607d8b"),i=this.showOpacity?`${this.label||"Color"}: ${e}, ${t}%`:`${this.label||"Color"}: ${e}`;return B` + ${this.label?B`${this.label}`:G} `:W} - `}}pn.properties={hass:{attribute:!1},presentation:{attribute:!1},registry:{attribute:!1},deviceName:{attribute:!1}},pn.styles=[At,n` + `:G} + `}}po.properties={hass:{attribute:!1},presentation:{attribute:!1},registry:{attribute:!1},deviceName:{attribute:!1}},po.styles=[At,o` :host { display: block; min-width: 0; } .devicepreview { margin: 4px 0 14px; @@ -3389,80 +3416,102 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 .previewstage { min-height: 142px; } .previewfacts > div { grid-template-columns: minmax(0, 1fr); gap: 2px; } } - `],customElements.get("hp-device-preview")||customElements.define("hp-device-preview",pn);const un=(t,e)=>[t[0]-e[0],t[1]-e[1]],_n=(t,e)=>[t[0]+e[0],t[1]+e[1]],mn=(t,e)=>t[0]*e[0]+t[1]*e[1],gn=t=>Math.hypot(t[0],t[1]);function fn(t){let e=0;for(let i=0;it[1]!=l>t[1]&&t[0]<(a-n)*(t[1]-r)/(l-r)+n&&(i=!i)}return i}([a[0]+r[0]*l,a[1]+r[1]*l],t)&&(r=[-r[0],-r[1]]),r}function bn(t,e,i,s){const o=s||yn(t,e),n=(e+1)%t.length;return t.map((t,s)=>s===e||s===n?[t[0]+o[0]*i,t[1]+o[1]*i]:[...t])}function wn(t,e,i,s){const o=[],n=un(i,e),r=gn(n);if(rs||h>s)continue;const d=mn(un(n,e),a),p=mn(un(l,e),a),u=Math.max(0,Math.min(d,p)),_=Math.min(r,Math.max(d,p));_-u>s&&o.push([[e[0]+a[0]*u,e[1]+a[1]*u],[e[0]+a[0]*_,e[1]+a[1]*_]])}return o}function kn(t,e,i,s,o){const n=wn(t,e,i,o);if(!n.length)return null;const r=t=>n.some(([e,i])=>vn(t,e,i)<=o),a=t.length,l=[];for(let n=0;no||f>o)continue;const v=mn(un(e,c),u),y=mn(un(i,c),u),b=Math.max(0,Math.min(v,y)),w=Math.min(p,Math.max(v,y));if(!(w-b<=o)){if(b>o&&bo&&wgn(un(i,t[(s+1)%t.length]))>e);for(let t=0;t<2;t++)i=i.filter((t,s)=>{const o=i[(s-1+i.length)%i.length],n=i[(s+1)%i.length],r=(t[0]-o[0])*(n[1]-o[1])-(t[1]-o[1])*(n[0]-o[0]),a=gn(un(n,o))||1;return Math.abs(r)/a>e});return i.length>=3?i:t}function xn(t){const e=t.length;if(e<3)return!1;for(let i=0;i(t[0]-o[0])*a[1]-(t[1]-o[1])*a[0],c=t=>(t[0]-o[0])*a[0]+(t[1]-o[1])*a[1],h=i,d=r-i;for(const e of t){const t=Math.abs(l(e));if(t<=i)continue;const o=c(e);o<=h||o>=d||tMath.abs(r+(t-p)/g*(a-r));s=Math.min(s,f(_),f(m))}}return s}function Mn(t,e,i){if(ui(t,e,i))return!0;if(pi(t,e,i)||pi(e,t,i))return!1;let s=0;try{const i=je([[...t.map(t=>[t[0],t[1]]),[t[0][0],t[0][1]]]],[[...e.map(t=>[t[0],t[1]]),[e[0][0],e[0][1]]]]);for(const t of i)t?.[0]&&(s+=mi(t[0]))}catch{return!1}return s>Math.max(1e-7,i*i)}function Cn(t,e,i,s,o){const n=[i.n[0]*s,i.n[1]*s],r={polys:{},openings:{},movedSpans:{}};if(Math.abs(s)<1e-9)return r;for(const e of t){if(e.id===i.roomId){r.polys[e.id]=bn(e.poly,i.edge,s,i.n),r.movedSpans[e.id]=[[_n(i.a,n),_n(i.b,n)]];continue}const t=wn(e.poly,i.a,i.b,o);if(!t.length)continue;const a=kn(e.poly,i.a,i.b,n,o);a&&(r.polys[e.id]=a,r.movedSpans[e.id]=t.map(([t,e])=>[_n(t,n),_n(e,n)]))}for(const t of e)vn([t.x,t.y],i.a,i.b)<=o&&(r.openings[t.id]=[t.x+n[0],t.y+n[1]]);return r}function Dn(t,e,i){for(const s of e)for(let e=0;ei)continue;const c=(t.x-o[0])*l[0]+(t.y-o[1])*l[1];if(c-t.length/2>=-i&&c+t.length/2<=a+i)return!0}return!1}function Tn(t,e,i){return t.filter(t=>e.some(e=>{for(let s=0;sa.polys[t.id]||t.poly;for(const e of l){const s=t.find(t=>t.id===e),o=a.polys[e];if(!xn(o))return!1;const l=fn(s.poly),h=fn(o);if(Math.abs(h)t.find(t=>t.id===e).poly),d=t.map(c);for(const t of Tn(e,h,2*r)){const e=a.openings[t.id];if(!Dn(e?{...t,x:e[0],y:e[1]}:t,d,2*r))return!1}return!0}function Rn(t,e,i,s,o,n){const r=t=>[s[0]+(t[0]-s[0])*o,s[1]+(t[1]-s[1])*o],a={poly:t.poly.map(r),openings:{}};for(const s of e){let e=!1;for(let i=0;i{for(let e=0;et.id===i);if(!l)return!1;const c=t.filter(t=>t.id!==i).map(t=>t.poly),h=Rn(l,e,c,s,o,2*a),d=h.poly,p=function(t){const e=function(t){const e=[...t].sort((t,e)=>t[0]-e[0]||t[1]-e[1]);if(e.length<3)return e;const i=(t,e,i)=>(e[0]-t[0])*(i[1]-t[1])-(e[1]-t[1])*(i[0]-t[0]),s=[];for(const t of e){for(;s.length>=2&&i(s[s.length-2],s[s.length-1],t)<=0;)s.pop();s.push(t)}const o=[];for(let t=e.length-1;t>=0;t--){const s=e[t];for(;o.length>=2&&i(o[o.length-2],o[o.length-1],s)<=0;)o.pop();o.push(s)}return s.pop(),o.pop(),s.concat(o)}(t);if(e.length<3)return 0;let i=1/0;for(let t=0;tt.id===i?d:t.poly);for(const t of Tn(e,[l.poly],2*a)){const e=h.openings[t.id];if(!Dn(e?{...t,x:e[0],y:e[1]}:t,u,2*a))return!1}return!0}function zn(t,e,i){const s=i/e;return mi(t)*s*s/1e4}function In(t,e){return e?`${Math.round(10.7639*t)} ft²`:`${(Math.round(10*t)/10).toFixed(1)} m²`}const En=[[-90,"#070c14"],[-12,"#070c14"],[-4,"#131a28"],[0,"#4a3527"],[10,"#e8ddcf"],[30,"#ffffff"],[90,"#ffffff"]],Nn=t=>Math.min(1,Math.max(0,t));function Fn(t){const e=Math.min(90,Math.max(-90,Number(t)||0));let i=En[En.length-1][1];for(let t=1;t{const n=[t.x+o[0]*i*s,t.y+o[1]*i*s];return e.find(t=>t.poly.length>=3&&si(n,t.poly))||null},r=n(1),a=n(-1);return r&&a?null:r||a?r?{normal:[-o[0],-o[1]],roomId:r.id}:{normal:o,roomId:a.id}:null}function Ln(t,e,i){return i>0&&t[0]*e[0]+t[1]*e[1]>.05}function Hn(t,e,i,s){return[[t[0],t[1]],[e[0],e[1]],[e[0]+i[0]*s,e[1]+i[1]*s],[t[0]+i[0]*s,t[1]+i[1]*s]]}function qn(t,e){try{const i=je([[...t.map(t=>[t[0],t[1]]),[t[0][0],t[0][1]]]],[[...e.map(t=>[t[0],t[1]]),[e[0][0],e[0][1]]]]),s=[];for(const t of i){const e=t?.[0];!Array.isArray(e)||e.length<4||s.push(e.slice(0,e.length-1).map(t=>[t[0],t[1]]))}return s}catch{return[]}}function Un(t,e,i,s,o,n,r){if(!(s>0))return[];const a=function(t,e){const i=function(t,e){return function(t){const e=t%360;return e<0?e+360:e}(t-e)}(t,e)*Math.PI/180;return[Math.sin(i),-Math.cos(i)]}(i,o),l=[-a[0],-a[1]],c=function(t){const e=Math.min(90,Math.max(0,t));return.7*(.8+1.7*Math.pow(1-e/90,1.6))}(s),h=[];for(const i of e){if(!(i.length>0))continue;const e=On(i,t);if(!e||!Ln(e.normal,a,s))continue;const o=t.find(t=>t.id===e.roomId);if(!o)continue;const d=n&&n[e.roomId]||o.poly,p=i.angle*Math.PI/180,u=i.length/2,_=[-e.normal[0],-e.normal[1]],m=Math.max(0,r?.[i.id]||0),g=i.x+_[0]*m/2,f=i.y+_[1]*m/2,v=Math.cos(p)*u,y=Math.sin(p)*u,b=[g-v,f-y],w=[g+v,f+y],k=c*i.length,$=qn(Hn(b,w,l,k),d);if(!$.length)continue;const x=l[0]*_[0]+l[1]*_[1];h.push({openingId:i.id,roomId:e.roomId,polys:$,a:b,b:w,dir:l,len:k,normal:_,depth:k*x})}return h}function Bn(t){return Math.round(10*(Number(t)||0))/10}const jn=t=>"number"==typeof t&&Number.isInteger(t)&&t>=0&&t<=359?t:null;function Gn(t,e){const i=jn(e?.north_deg);return null!==i?i:jn(t?.north_deg)}function Wn(t,e){const i=t=>"static"===t||"daynight"===t?t:null;return i(e?.bg_mode)??i(t?.bg_mode)??"static"}function Vn(t,e){const i=e?.sun_rays;return"boolean"==typeof i?i:!0===t?.sun_rays}function Kn(t){const e=t?.states?.["sun.sun"]?.attributes,i=Number(e?.azimuth),s=Number(e?.elevation);return Number.isFinite(i)&&Number.isFinite(s)?{azimuth:i,elevation:s}:null}const Yn={color:"#607d8b",opacity:1,widthCm:3.6,fill:!1,fillColor:"#607d8b",fillOpacity:.25},Jn=(t,e=1)=>{const i=Number(t);return Number.isFinite(i)?Math.min(1,Math.max(0,i)):e},Zn=t=>{let e=Number(t);return Number.isFinite(e)?(e=(e%360+360)%360,e>180?e-360:e):0};function Xn(t,e,i){return Zn(e)?[t.x,t.y]:i([t.x,t.y])}const Qn=(t,e,i)=>{const s=Number(t),o=Number.isFinite(e)&&e>0?e:5;return Number.isFinite(s)&&s>0?s/o*i:0},tr=(t,e,i)=>{const s=Number(t),o=Number.isFinite(e)&&e>0?e:5;return Number.isFinite(s)&&i>0?s/i*o:0},er=(t,e,i,s=Yn.widthCm)=>{const o=Number(t?.width_cm);if(Number.isFinite(o)&&o>0)return o;const n=Number(t?.width);return Number.isFinite(n)&&n>0?tr(n,e,i):s};function ir(t,e){return{color:t.color,opacity:Jn(t.opacity),width_cm:Math.max(.1,Math.min(100,Number(t.widthCm)||.1)),...e?{fill:t.fill,fill_color:t.fillColor,fill_opacity:Jn(t.fillOpacity,.25)}:{}}}function sr(t){const e=t.x+t.w/2,i=t.y+t.h/2,s=Zn(t.angle)*Math.PI/180,o=Math.cos(s),n=Math.sin(s),r=(t,s)=>{const r=t-e,a=s-i;return[e+r*o-a*n,i+r*n+a*o]};return[r(t.x,t.y),r(t.x+t.w,t.y),r(t.x+t.w,t.y+t.h),r(t.x,t.y+t.h)]}function or(t){const e=sr(t),i=(t,e)=>[(t[0]+e[0])/2,(t[1]+e[1])/2],s=[(e[0][0]+e[2][0])/2,(e[0][1]+e[2][1])/2];return{points:[...e,i(e[0],e[1]),i(e[1],e[2]),i(e[2],e[3]),i(e[3],e[0]),s],segments:e.map((t,i)=>({a:t,b:e[(i+1)%4]}))}}function nr(t,e,i,s,o,n,r,a){const l=Zn(t.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l),d=-Math.sin(l),p=Math.cos(l),u=t.x+t.w/2,_=t.y+t.h/2,m=e>0?-t.w/2:t.w/2,g=i>0?-t.h/2:t.h/2,f=u+m*c+g*d,v=_+m*h+g*p,y=s-f,b=o-v;let w=(y*c+b*h)*(e>0?1:-1),k=(y*d+b*p)*(i>0?1:-1),$=null;if(n){const e=w/Math.max(t.w,a),i=k/Math.max(t.h,a);$=Math.max(a/Math.max(t.w,a),a/Math.max(t.h,a),e,i),w=t.w*$,k=t.h*$}if(r>0)if(n){const e=Math.max(1,Math.round(t.w/r)),i=Math.max(1,Math.round(t.h/r));if(Math.abs(t.w-e*r)<1e-6&&Math.abs(t.h-i*r)<1e-6){const s=(t,e)=>{let i=Math.abs(t),s=Math.abs(e);for(;s;)[i,s]=[s,i%s];return Math.max(1,i)},o=1/s(e,i),n=Math.max(a/t.w,a/t.h),r=Math.max(n,Math.round(($??1)/o)*o);w=t.w*r,k=t.h*r}else{t.w>=t.h?(w=Math.round(w/r)*r,k=w*(t.h/Math.max(t.w,a))):(k=Math.round(k/r)*r,w=k*(t.w/Math.max(t.h,a)))}}else w=Math.round(w/r)*r,k=Math.round(k/r)*r;w=Math.max(a,w),k=Math.max(a,k);return{x:f+(e>0?w/2:-w/2)*c+(i>0?k/2:-k/2)*d-w/2,y:v+(e>0?w/2:-w/2)*h+(i>0?k/2:-k/2)*p-k/2,w:w,h:k,angle:Zn(t.angle)||void 0}}const rr=(t,e,i)=>{const s=i[0]-e[0],o=i[1]-e[1],n=s*s+o*o;if(n<1e-12)return[...e];const r=Math.max(0,Math.min(1,((t[0]-e[0])*s+(t[1]-e[1])*o)/n));return[e[0]+s*r,e[1]+o*r]};const ar=15;function lr(t,e){return Number.isFinite(t)&&t>0&&Number.isFinite(e)&&e>0&&t*e<3}function cr(t){return Number.isFinite(t)?Math.max(1,Math.min(100,t)):1}function hr(t,e){return!Number.isFinite(t)||t<=0?"":String(e?Math.round(t/2.54*100)/100:Math.round(100*t)/100)}function dr(t,e,i){const s=Number(e)>0?Number(e):5;return cr(t)/s*i}function pr(t,e){return e>0&&Number.isFinite(t)?Math.round(t/e)*e:t}function ur(t,e){let i=e[0]-t[0],s=e[1]-t[1];const o=Math.hypot(i,s);return o<1e-12?[1,0]:(i/=o,s/=o,(i<-1e-12||Math.abs(i)<=1e-12&&s<0)&&(i=-i,s=-s),[i,s])}function _r(t,e,i){const s=pr((t[0]+e[0])/2,i),o=pr((t[1]+e[1])/2,i),[n,r]=ur(t,e);let a=Math.atan2(r,n);a<0&&(a+=Math.PI);const l=Math.round(1800*a)/1800,c=i>0&&i<.01?6:i<1?4:2;return`${s.toFixed(c)},${o.toFixed(c)}@${l.toFixed(4)}`}function mr(t,e,i,s){return 1===s?_r(t,e,i):_r([t[0]/s,t[1]/s],[e[0]/s,e[1]/s],i)}function gr(t,e){if(!Array.isArray(t.a)||!Array.isArray(t.b)||t.a.length<2||t.b.length<2)return null;const i=[Number(t.a[0]),Number(t.a[1]),Number(t.b[0]),Number(t.b[1])];if(!i.every(Number.isFinite))return null;const s=e>0?e:1;return[[i[0]*s,i[1]*s],[i[2]*s,i[3]*s]]}function fr(t,e,i,s,o){const n=o>0?o:1;return{key:mr(t,e,s,n),cm:cr(i),a:[t[0]/n,t[1]/n],b:[e[0]/n,e[1]/n]}}function vr(t,e){const i=e>0?e:1,s=[];for(const e of t){const t=e.key.lastIndexOf("@");if(t<0)continue;const[o,n]=e.key.slice(0,t).split(",").map(Number),r=Number(e.key.slice(t+1));[o,n,r].every(Number.isFinite)&&s.push({w:e,x:o*i,y:n*i,ang:r})}return s}function yr(t,e){const[i,s]=ur(t,e);let o=Math.atan2(s,i);return o<0&&(o+=Math.PI),o}function br(t,e){let i=Math.abs(t-e);return i>Math.PI/2&&(i=Math.PI-i),i<.02}function wr(t,e,i,s,o=1){if(!t?.length)return null;const n=mr(e,i,s,o),r=t.find(t=>t.key===n);if(r)return r;const a=o>0?o:1,l=(e[0]+i[0])/2,c=(e[1]+i[1])/2,h=yr(e,i),d=Math.max(.5*s,1e-9)*a;for(const e of vr(t,a))if(br(e.ang,h)&&Math.hypot(e.x-l,e.y-c)<=d)return e.w;return null}function kr(t,e,i,s,o=1){const n=wr(t,e,i,s,o);return n&&n.cm>0?cr(n.cm):0}function $r(t,e,i,s=1,o=[]){if(!t?.length)return[];const n=new Set,r=Xe(e);for(const t of r)n.add(mr([t[0],t[1]],[t[2],t[3]],i,s));const a=e||[],l=Math.max(i*s*.02,1e-9);for(let t=0;t(n.has(t.key)||(t=>{const e=gr(t,s);if(!e)return!1;const[i,n]=e,a=n[0]-i[0],c=n[1]-i[1],h=Math.hypot(a,c);if(h<=l)return!1;if(!r.some(t=>{const e=[t[0],t[1]],s=[t[2],t[3]];return br(yr(i,n),yr(e,s))&&Br(i[0],i[1],e[0],e[1],s[0],s[1])<=l&&Br(n[0],n[1],e[0],e[1],s[0],s[1])<=l}))return!1;const d=(o||[]).some(t=>{const e=[t[0],t[1]],s=[t[2],t[3]];if(!br(yr(i,n),yr(e,s)))return!1;const o=t=>Math.abs((t[0]-i[0])*c-(t[1]-i[1])*a)/h;if(o(e)>l||o(s)>l)return!1;const r=h*h,d=((e[0]-i[0])*a+(e[1]-i[1])*c)/r,p=((s[0]-i[0])*a+(s[1]-i[1])*c)/r;return Math.min(1,Math.max(d,p))-Math.max(0,Math.min(d,p))>l/h});return!d})(t))&&t.cm>=1&&t.cm<=100)}function xr(t,e,i,s=8){const[o,n]=ur(t,e);let r=Math.atan2(n,o);r<0&&(r+=Math.PI);let a=i*Math.PI/180%Math.PI;a<0&&(a+=Math.PI);let l=Math.abs(r-a);return l>Math.PI/2&&(l=Math.PI-l),l<=s*Math.PI/180}function Sr(t,e,i,s,o=1){if(!t?.length)return[];if(e.length!==i.length)return t.slice();const n=new Map;for(let t=0;t0?o:1,a=Math.max(.5*s,1e-9)*r,l=new Set,c=[];for(const o of t){let t="",h=null;const d=gr(o,r);if(d)for(let o=0;oa||Br(d[1][0],d[1][1],n[0],n[1],l[0],l[1])>a)continue;const u=l[0]-n[0],_=l[1]-n[1],m=u*u+_*_;if(m<1e-18)continue;const g=t=>{const e=Math.max(0,Math.min(1,((t[0]-n[0])*u+(t[1]-n[1])*_)/m));return[c[0]+(p[0]-c[0])*e,c[1]+(p[1]-c[1])*e]};h=[g(d[0]),g(d[1])],t=mr(h[0],h[1],s,r);break}if(d||(t=n.get(o.key)||""),!t){const n=vr([o],r)[0];if(n)for(let o=0;o1.000001)continue;if(Br(n.x,n.y,l[0],l[1],c[0],c[1])>a)continue;const g=h[0]+(d[0]-h[0])*Math.max(0,Math.min(1,m)),f=h[1]+(d[1]-h[1])*Math.max(0,Math.min(1,m)),[v,y]=ur(h,d),b=Math.max(s*r,1e-6);t=mr([g-v*b,f-y*b],[g+v*b,f+y*b],s,r);break}}t||(t=o.key),l.has(t)||(l.add(t),c.push(h?fr(h[0],h[1],o.cm,s,r):{...o,key:t,cm:cr(o.cm)}))}return c}function Mr(t,e,i,s,o,n=1){const r=mr(e,i,o,n),a=(t||[]).filter(t=>t.key!==r);return null==s||s<1?a:[...a,fr(e,i,s,o,n)]}function Cr(t,e,i,s,o,n=[],r=1){let a=t?t.slice():[];for(const t of function(t,e,i,s,o=1,n=[]){const r=Fr(t,e,i,s,o,n);if(!r)return[];const a=[];for(let t=0;t0)||!t||t.length<2)return"";if(i&&t.length>=3){let i=t;const s=t[t.length-1];if(t.length>=4&&Math.hypot(t[0][0]-s[0],t[0][1]-s[1])<1e-9&&(i=t.slice(0,-1)),i.length>=3){const t=i.map(()=>e),s=Kr(i,t),o=Ir(i,t);if(s&&o)return`${Er(s)} ${Er(Nr(o))}`}}let s="";for(let i=0;i.05)&&Br(r,a,t[0],t[1],t[2],t[3])<=n)return!0}return!1}function Pr(t,e){return Math.max(t*(e>0?e:1)*.04,1e-9)}function Rr(t,e){const i=t[e],s=t[(e+1)%t.length],o=s[0]-i[0],n=s[1]-i[1],r=Math.hypot(o,n)||1;let a=-n/r,l=o/r;const c=[(i[0]+s[0])/2,(i[1]+s[1])/2];return function(t,e){let i=!1;for(let s=0,o=e.length-1;st[1]!=l>t[1]&&t[0]<(a-n)*(t[1]-r)/(l-r+0)+n&&(i=!i)}return i}([c[0]+.001*a,c[1]+.001*l],t)||(a=-a,l=-l),[a,l]}function Ar(t,e){const i=t[0]*e[1]-t[1]*e[0],s=t[0]*e[0]+t[1]*e[1];return Math.abs(i)<1e-9&&s>0}function zr(t,e,i,s){const o=e[0]*s[1]-e[1]*s[0];if(Math.abs(o)<1e-12)return null;const n=[i[0]-t[0],i[1]-t[1]],r=(n[0]*s[1]-n[1]*s[0])/o;return[t[0]+r*e[0],t[1]+r*e[1]]}function Ir(t,e){const i=t?.length||0;if(i<3||e.length!==i)return null;if(e.every(t=>!(t>0)))return t.map(t=>[t[0],t[1]]);const s=[];for(let o=0;o0||d>0)){s.push([t[o][0],t[o][1]]);continue}if(Ar(b,w)){const e=t[o],i=[e[0]+p*h,e[1]+u*h],n=[e[0]+_*d,e[1]+m*d];s.push(i),Math.hypot(n[0]-i[0],n[1]-i[1])>1e-9&&s.push(n);continue}const x=zr(k,b,$,w),S=Math.max(h,d,1e-9);if(x){if(Math.hypot(x[0]-t[o][0],x[1]-t[o][1])<=4*S){s.push(x);continue}}h>0&&s.push([t[o][0]+p*h,t[o][1]+u*h]),d>0&&s.push([t[o][0]+_*d,t[o][1]+m*d]),h>0||d>0||s.push([t[o][0],t[o][1]])}return s.length>=3?s:null}function Er(t,e=!0){if(!t.length)return"";let i=`M ${t[0][0]} ${t[0][1]}`;for(let e=1;et?.id===e),a=Je(r);if(!a||a.length<3)return null;const l=Pr(s,o),c=[];for(const i of t||[]){if(!i||i.id===e)continue;const t=Je(i);if(t)for(const e of us(a,t,l))c.push([e[0],e[1]],[e[2],e[3]])}for(const t of i||[])c.push([t[0],t[1]],[t[2],t[3]]);for(const t of n||[]){const e=gr(t,o);e&&c.push(e[0],e[1])}const h=[],d=[];for(let t=0;tl)continue;const r=((t[0]-e[0])*(i[0]-e[0])+(t[1]-e[1])*(i[1]-e[1]))/(s*s);r<=o||r>=1-o||(n.some(t=>Math.abs(t-r)*s<=2*l)||n.push(r))}n.sort((t,e)=>t-e);for(const s of n)h.push([e[0]+(i[0]-e[0])*s,e[1]+(i[1]-e[1])*s]),d.push(t)}return{poly:h,parent:d,orig:a}}function Or(t,e,i,s){const o=e.poly.length,n=new Array(o).fill(0);if(!t?.length)return n;const r=new Set,a=[];for(let l=0;l0?(n[l]=cr(c.cm),r.add(c.key)):a.push(l)}if(!a.length)return n;const l=s>0?s:1,c=Math.max(.5*i,1e-9)*l,h=vr(t,l).filter(t=>t.w.cm>0),d=new Map;for(const t of a){const i=e.parent[t],s=d.get(i);s?s.push(t):d.set(i,[t])}for(const[t,i]of d){const s=e.orig[t],o=e.orig[(t+1)%e.orig.length],a=yr(s,o),d=(s[0]+o[0])/2,p=(s[1]+o[1])/2;let u=null;const _=Math.hypot(o[0]-s[0],o[1]-s[1]);for(const t of h){if(r.has(t.w.key))continue;if(!br(t.ang,a))continue;const e=gr(t.w,l);let i=!1,n=0;if(e){if(!br(yr(e[0],e[1]),a))continue;if(Br(s[0],s[1],e[0][0],e[0][1],e[1][0],e[1][1])>c||Br(o[0],o[1],e[0][0],e[0][1],e[1][0],e[1][1])>c)continue;i=!0,n=Math.max(0,Math.hypot(e[1][0]-e[0][0],e[1][1]-e[0][1])-_)}else{if(Br(t.x,t.y,s[0],s[1],o[0],o[1])>c)continue;n=Math.hypot(t.x-d,t.y-p)}(!u||i&&!u.exact||i===u.exact&&nt?.id===e),o=Je(s);if(!o)return[];const n=[];for(const s of t||[]){if(!s||s.id===e)continue;const t=Je(s);if(t)for(const e of us(o,t,i))n.push(e)}return n}(t,e,Pr(o,a)),h=function(t,e,i,s,o){const n=Pr(s,o),r=[];for(let a=0;aBr(h,d,t[0],t[1],t[2],t[3])<=n);r.push(p?"shared":"outer")}return r}(l.poly,c,s,o,a),d=Or(i,l,o,a),p=d.map((t,e)=>h[e]&&t>0?dr(t,n,r)/2:0);return{...l,kinds:h,cms:d,offsets:p}}function Hr(t,e,i,s,o,n,r=1){const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,o,n,r);if(c)for(let t=0;t0&&!l.has(c.key)&&(l.add(c.key),a.push(c));const c=[];for(const a of t||[]){if(!a?.id)continue;const l=Lr(t,a.id,e,i,s,o,n,r);if(l)for(let t=0;t0)||null===l.kinds[i]){t++;continue}let n=t;for(;n+10&&c.push({a:[h[0],h[1]],b:[d[0],d[1]],key:mr(h,d,s,r),cm:o,len:p}),t=n+1}}}c.sort((t,e)=>e.len-t.len||t.key.localeCompare(e.key));const h=[],d=new Set,p=new Set,u=4*Pr(s,r);for(const t of c){const e=a.filter(e=>!p.has(e.key)&&e.cm===t.cm&&br(yr(e.a,e.b),yr(t.a,t.b))&&Br(e.a[0],e.a[1],t.a[0],t.a[1],t.b[0],t.b[1])<=u&&Br(e.b[0],e.b[1],t.a[0],t.a[1],t.b[0],t.b[1])<=u);if(e.length){for(const t of e)p.add(t.key);d.has(t.key)||(d.add(t.key),h.push(fr(t.a,t.b,t.cm,s,r)))}}for(const t of a)p.has(t.key)||d.has(t.key)||(d.add(t.key),h.push(fr(t.a,t.b,t.cm,s,r)));return h}function Ur(t,e,i,s,o,n,r,a=1){const l=Pr(o,a),c=(s[0]+s[2])/2,h=(s[1]+s[3])/2,d=yr([s[0],s[1]],[s[2],s[3]]);let p=null;for(const s of Hr(t,e,i,o,n,r,a)){if(!br(yr(s.a,s.b),d))continue;const t=Br(c,h,s.a[0],s.a[1],s.b[0],s.b[1]);t>4*l||(!p||tt?.id===e),c=Je(l);if(!c||c.length<3)return null;if(!i?.length)return c.map(t=>[t[0],t[1]]);const h=Lr(t,e,i,s,o,n,r,a);return h&&h.offsets.some(t=>t>0)&&Ir(h.poly,h.offsets)||c.map(t=>[t[0],t[1]])}function Gr(t){const e=t.map(t=>[t[0],t[1]]);return e.push([t[0][0],t[0][1]]),[e]}function Wr(t,e,i,s=[],o,n,r,a=1,l=[]){if(!e?.length&&!l.length)return null;const c=[];let h=0;for(const s of t||[]){if(!s?.id)continue;const l=Lr(t,s.id,e,i,o,n,r,a);if(!l||l.poly.length<3||!l.offsets.some(t=>t>0))continue;for(const t of l.offsets)t>0&&(h=Math.max(h,2*t));const d=Kr(l.poly,l.offsets),p=Ir(l.poly,l.offsets);d&&c.push({outset:d,inset:p})}for(const t of l){const e=t.map(t=>t[0]),i=t.map(t=>t[1]);if(e.length){const s=Math.min(Math.max(...e)-Math.min(...e),Math.max(...i)-Math.min(...i)),o=Math.min(...t.map((e,i)=>{const s=t[(i+1)%t.length];return Math.hypot(s[0]-e[0],s[1]-e[1])}));h=Math.max(h,t.length>16?s:o)}}const d=function(t,e,i,s,o,n,r){if(!e?.length||!i?.length)return[];const a=4*Pr(s,r),l=new Map;for(const a of Hr(t,e,i,s,o,n,r))!a.open&&a.half>0&&!l.has(a.key)&&l.set(a.key,a);const c=[...l.values()];if(c.length<2)return[];const h=[];for(const t of i)for(const e of[[t[0],t[1]],[t[2],t[3]]])h.some(t=>Math.hypot(t[0]-e[0],t[1]-e[1])<=a)||h.push(e);const d=[],p=(t,e)=>{let i=0,s=0;if(Math.hypot(t.a[0]-e[0],t.a[1]-e[1])<=a)i=t.b[0]-t.a[0],s=t.b[1]-t.a[1];else{if(!(Math.hypot(t.b[0]-e[0],t.b[1]-e[1])<=a))return null;i=t.a[0]-t.b[0],s=t.a[1]-t.b[1]}const o=Math.hypot(i,s);return o>a?[i/o,s/o]:null};for(const t of h){const e=c.map(e=>({iv:e,u:p(e,t)})).filter(t=>!!t.u);for(let i=0;i4*_||d.push(r>0?[t.slice(),h,u,p]:[t.slice(),p,u,h])}}return d}(t,e,i,o,n,r,a),p=s.length?Jr(t,e,i,o,n,r,a):null;try{const t=t=>{const e=Gr(t.outset);return t.inset?Ge(e,Gr(t.inset)):e};let e=c.length?t(c[0]):null;for(let i=1;i0))continue;const i=Qr(p,t,!0);if(!i.negative&&!i.positive)continue;const s=t.angle*Math.PI/180,n=Math.cos(s),r=Math.sin(s),l=-r,c=n,d=t.length/2,u=1.25*Math.max(h,o*a),_=[[t.x-n*d-l*u,t.y-r*d-c*u],[t.x+n*d-l*u,t.y+r*d-c*u],[t.x+n*d+l*u,t.y+r*d+c*u],[t.x-n*d+l*u,t.y-r*d+c*u]];e&&(e=Ge(e,Gr(_)))}for(const t of l)t.length<3||(e=e?Be(e,Gr(t)):[Gr(t)]);return e?{geom:e,depthUnits:h}:null}catch{return null}}function Vr(t,e,i,s=[],o,n,r,a=1,l=[]){if(!e?.length&&!l.length)return null;const c=Wr(t,e,i,s,o,n,r,a,l),h=c?function(t){if(!t)return"";let e="";for(const i of t)if(Array.isArray(i))for(const t of i){if(!Array.isArray(t)||t.length<4)continue;const i=t.slice(0,t.length-1);i.length<3||(e+=(e?" ":"")+Er(i.map(t=>[t[0],t[1]])))}return e}(c.geom):"";if(c&&h)return{d:h,depthUnits:c.depthUnits,fillRule:"evenodd"};const d=function(t,e,i,s,o,n,r=1){if(!e?.length)return[];const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,o,n,r);if(!c||c.poly.length<3||!c.offsets.some(t=>t>0))continue;const h=Kr(c.poly,c.offsets),d=Ir(c.poly,c.offsets);if(!h||!d)continue;const p=`${Er(h)} ${Er(Nr(d))}`;let u="",_="outer",m=0,g=0;for(let t=0;t0){u=mr(c.poly[t],c.poly[(t+1)%c.poly.length],s,r),_=c.kinds[t]||"outer",m=c.cms[t],g=dr(m,o,n);break}a.push({d:p,key:u,kind:_,cm:m,depthUnits:g})}return a}(t,e,i,o,n,r,a),p=l.map(t=>Er(t)).join(" ");if(!d.length&&!p)return null;let u=c?.depthUnits||0;for(const t of d)u=Math.max(u,t.depthUnits);return{d:[d.map(t=>t.d).join(" "),p].filter(Boolean).join(" "),depthUnits:u,fillRule:"nonzero"}}function Kr(t,e){const i=t?.length||0;if(i<3||e.length!==i)return null;if(e.every(t=>!(t>0)))return t.map(t=>[t[0],t[1]]);Nr(t),e.slice().reverse();const s=[];for(let o=0;o0||a>0)){s.push([t[o][0],t[o][1]]);continue}if(Ar(b,w)){const e=t[o],i=[e[0]-l*r,e[1]-c*r],n=[e[0]-h*a,e[1]-d*a];s.push(i),Math.hypot(n[0]-i[0],n[1]-i[1])>1e-9&&s.push(n);continue}const x=zr(k,b,$,w),S=Math.max(r,a,1e-9);if(x){if(Math.hypot(x[0]-t[o][0],x[1]-t[o][1])<=4*S){s.push(x);continue}}r>0&&s.push([t[o][0]-l*r,t[o][1]-c*r]),a>0&&s.push([t[o][0]-h*a,t[o][1]-d*a])}return s.length>=3?s:null}function Yr(t,e,i,s,o,n,r=1){const a=[];for(const l of t||[]){const c=Je(l);if(c&&c.length>=3){const h=Lr(t,l.id,e,i,s,o,n,r),d=h&&h.offsets.some(t=>t>0)?Kr(h.poly,h.offsets):null,p=d||c;a.push({poly:p.map(t=>t.join(",")).join(" ")})}else l&&null!=l.x&&null!=l.y&&null!=l.w&&null!=l.h&&a.push({rect:{x:l.x,y:l.y,w:l.w,h:l.h,rx:.03*Math.min(l.w,l.h)}})}return a}function Jr(t,e,i,s,o,n,r=1){const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,o,n,r);if(!c)continue;const h=Math.abs(mi(c.poly));for(let t=0;t[Math.max(e,t.x0),Math.min(i,t.x1)]).filter(t=>t[1]-t[0]>s).sort((t,e)=>t[0]-e[0]||t[1]-e[1]);if(!o.length)return{coverage:0,full:!1};let n=o[0][0],r=o[0][1],a=0,l=n<=e+s;for(let t=1;t=i-s,{coverage:a,full:l}}function Xr(t,e){return Number(e.full)-Number(t.full)||t.faceDistance-e.faceDistance||t.area-e.area||t.roomId.localeCompare(e.roomId)}function Qr(t,e,i=!1){const s=Number(e?.x),o=Number(e?.y),n=Number(e?.angle),r=Number(e?.length);if(!([s,o,n,r].every(Number.isFinite)&&r>0))return{negative:null,positive:null};const a=n*Math.PI/180,l=Math.cos(a),c=Math.sin(a),h=-c,d=l,p=r/2,u=Math.max(1e-9,t.adjacencyEps),_=new Map;let m=0;for(const e of t.edges){if(i&&!(e.half>0))continue;if(!xr(e.a,e.b,n))continue;const[t,r]=ur(e.a,e.b);if(Math.abs((s-e.a[0])*r-(o-e.a[1])*t)>u)continue;const a=(e.a[0]-s)*l+(e.a[1]-o)*c,g=(e.b[0]-s)*l+(e.b[1]-o)*c,f=Math.max(-p,Math.min(a,g)),v=Math.min(p,Math.max(a,g));if(v-f<=u)continue;const y=e.inward[0]*h+e.inward[1]*d>=0?1:-1,b=((e.a[0]+e.b[0])/2-s)*h+((e.a[1]+e.b[1])/2-o)*d,w=Math.abs(b+y*e.half),k=`${y}|${e.roomId}`,$={x0:f,x1:v,half:e.half,cm:e.cm,key:e.key,axis:[t,r]},x=_.get(k);x?(x.pieces.push($),x.faceDistance=Math.min(x.faceDistance,w)):_.set(k,{roomId:e.roomId,side:y,order:m++,pieces:[$],faceDistance:w,area:e.area,coverage:0,full:!1})}for(const t of _.values()){const e=Zr(t.pieces,-p,p,u);t.coverage=e.coverage,t.full=e.full}const g=t=>{const e=[..._.values()].filter(e=>e.side===t&&e.coverage>u);return e.sort(Xr),e[0]||null};return{negative:g(-1),positive:g(1)}}function ta(t,e){const i=Qr(t,e),s=[i.negative,i.positive].filter(t=>!!t).sort((t,e)=>t.order-e.order);if(!s.length)return{ox:0,oy:0,cm:0,side:-1};const o=s[0],n=e.flip_v?-o.side:o.side,r=(-1===n?i.negative:i.positive)||o,a=[...r.pieces].sort((t,e)=>(t.x0<=0&&t.x1>=0?0:Math.min(Math.abs(t.x0),Math.abs(t.x1)))-(e.x0<=0&&e.x1>=0?0:Math.min(Math.abs(e.x0),Math.abs(e.x1)))||e.x1-e.x0-(t.x1-t.x0)||t.key.localeCompare(e.key))[0];if(!(a.half>0&&a.cm>0))return{ox:0,oy:0,cm:0,side:n};const l=e.angle*Math.PI/180,c=-Math.sin(l),h=Math.cos(l);return{ox:c*n*a.half,oy:h*n*a.half,cm:a.cm,side:n}}function ea(t,e){const i=1e-9,s=[];for(const t of[...e].filter(t=>t.x1>t.x0&&t.half>0).sort((t,e)=>t.x0-e.x0||t.x1-e.x1||t.half-e.half)){const e=s[s.length-1];e&&t.x0<=e.x1+i&&Math.abs(t.half-e.half)<=i?e.x1=Math.max(e.x1,t.x1):s.push({...t})}const o=[];for(let e=0;e=o-l?i.push([s,o]):(t>s+l&&i.push([s,Math.min(o,t)]),et[0]-e[0]||t[1]-e[1]),b=[];for(const t of y){const e=b[b.length-1];e&&t[0]<=e[1]+l?e[1]=Math.max(e[1],t[1]):b.push([t[0],t[1]])}s.set(g,b)}return a}function sa(t,e){const i=new Map;return e.map(e=>function(t,e,i){const s=Qr(t,e,!0),o=s.negative,n=s.positive;if(!o&&!n)return null;let r;if(o&&n)r=[{candidate:o,side:-1},{candidate:n,side:1}];else{const t=o||n;r=[{candidate:t,side:-1},{candidate:t,side:1}]}const a=r.map(({candidate:t,side:s})=>({candidate:t,side:s,pieces:ia(e,s,t.pieces,i)})),l=a.map(({candidate:t,side:e,pieces:i})=>({side:e,roomId:t.roomId,d:ea(e,i)})),c=a.flatMap(({pieces:t})=>t);if(!c.length)return null;const h=Math.max(...c.map(t=>t.half));return{faces:l,minY:-h,maxY:h,wallKey:[...new Set(c.map(t=>t.key))].sort().join("|")}}(t,e,i))}const oa=150;function na(t){return Number.isFinite(t)?Math.max(1,Math.min(oa,t)):1}function ra(t){return((Number.isFinite(Number(t))?Number(t):0)%90+90)%90}const aa=t=>{const e=t.map(t=>[t[0],t[1]]);return!e.length||e[0][0]===e[e.length-1][0]&&e[0][1]===e[e.length-1][1]||e.push([...e[0]]),[e]};function la(t){const e=[];for(const i of t||[])for(const t of i||[]){const i=(t||[]).filter(t=>Array.isArray(t)&&t.length>=2);i.length<4||e.push(`M ${i.slice(0,-1).map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`)}return e.join(" ")}function ca(t,e,i,s,o){const n=e[0]-t[0],r=e[1]-t[1],a=Math.hypot(n,r);if(!(a>1e-9))return null;const l=dr(i,s,o)/2,c=-r/a*l,h=n/a*l;return[[t[0]+c,t[1]+h],[e[0]+c,e[1]+h],[e[0]-c,e[1]-h],[t[0]-c,t[1]-h]]}function ha(t,e,i){const s=Number(e)>0?Number(e):5,o=na(t.cm)/s*i,n=t.center[0],r=t.center[1];if("circle"===t.shape){const t=o/2;return Array.from({length:96},(e,i)=>{const s=i/96*Math.PI*2;return[n+Math.cos(s)*t,r+Math.sin(s)*t]})}const a=o/2,l=ra(t.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l);return[[-a,-a],[a,-a],[a,a],[-a,a]].map(([t,e])=>[n+t*c-e*h,r+t*h+e*c])}function da(t,e,i){const s=[];for(let o=0;o+1t.length>=3).map(t=>aa(t));return e.length?Be(e[0],...e.slice(1)):null}catch{return null}}const ua=t=>`M ${t.map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`;function _a(t){const e=[];for(const i of t||[]){const t=[];for(const e of i||[]){const i=(e||[]).filter(t=>Array.isArray(t)&&t.length>=2);i.length<4||t.push(ua(i.slice(0,-1)))}t.length&&e.push(t.join(" "))}return e}function ma(t,e){const i=pa(t.filter(t=>t.length>=3)),s=pa(e.filter(t=>t.length>=3));if(!i)return[];if(!s)return _a(i);try{return _a(je(i,s))}catch{return _a(i)}}function ga(t,e){if(!e.length)return[aa(t)];try{const i=pa(e);if(i)return Ge(aa(t),i)}catch{}let i=[aa(t)];for(const t of e)if(!(t.length<3))try{i=Ge(i,aa(t))}catch{}return i}function fa(t){let e=0;for(const i of t||[])if(i?.length){e+=mi(i[0]||[]);for(let t=1;t=4&&e.push(t.slice(0,-1).map(t=>[t[0],t[1]]))}return e}function ya(t,e){let i=!1;for(let s=0,o=e.length-1;st[1]!=l>t[1]&&t[0]<(a-n)*(t[1]-r)/(l-r||1e-12)+n&&(i=!i)}return i}function ba(t,e,i){if(Math.hypot(t.center[0]-e.center[0],t.center[1]-e.center[1])>i)return!1;if(Math.abs(na(t.cm)-na(e.cm))>1e-6)return!1;if(t.shape!==e.shape)return!0;if("circle"===t.shape||"circle"===e.shape)return!0;const s=Math.abs(ra(t.angle)-ra(e.angle));return Math.min(s,90-s)<=1e-6}const wa=1e3;function ka(t,e){const i=Number(t),s=Number.isFinite(i)&&i>0?i:1,o=s>=1?e:e*s,n=s>=1?e/s:e;return{x:(e-o)/2,y:(e-n)/2,w:o,h:n}}const $a=.01,xa=100;function Sa(t,e=1e3){const i=ka(t?.plan_aspect,e),s=Number(t?.plan_scale),o=Number.isFinite(s)&&s>0?Math.min(xa,Math.max($a,s)):1,n=Number(t?.plan_scale_x),r=Number(t?.plan_scale_y),a=Number.isFinite(n)&&n>0?Math.min(xa,Math.max($a,n)):o,l=Number.isFinite(r)&&r>0?Math.min(xa,Math.max($a,r)):o,c=Number(t?.plan_x),h=Number(t?.plan_y),d=Zn(t?.plan_angle);return{x:i.x+(Number.isFinite(c)?Na(c):0)*e,y:i.y+(Number.isFinite(h)?Na(h):0)*e,w:i.w*a,h:i.h*l,...d?{angle:d}:{}}}function Ma(t){if(null==t.x||null==t.y)return{x:t.x,y:t.y,w:t.w,h:t.h};const e=Number(t.w)||0,i=Number(t.h)||0;return{x:e<0?t.x+e:t.x,y:i<0?t.y+i:t.y,w:Math.abs(e),h:Math.abs(i)}}function Ca(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=wa,i=function(t){return Array.isArray(t)&&4===t.length&&t.every(t=>Number.isFinite(t))&&t[2]>1e-6&&t[3]>1e-6?t:[0,0,1,1]}(t.view_box);return{id:t.id,title:t.title,vb:[i[0]*wa,i[1]*e,i[2]*wa,i[3]*e],bg:t.plan_url?{href:ys(t.plan_url),...Sa(t,wa)}:null,rooms:(t.rooms||[]).map(t=>{const i={...t,...Ma(t)};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*wa:void 0,y:null!=i.y?i.y*e:void 0,w:null!=i.w?i.w*wa:void 0,h:null!=i.h?i.h*e:void 0,poly:i.poly?i.poly.map(t=>[t[0]*wa,t[1]*e]):void 0}}),room_drafts:(t.room_drafts||[]).map(t=>({id:t.id,points:(t.points||[]).map(t=>[t[0]*wa,t[1]*e]),segments:(t.segments||[]).map(t=>({cm:Number(t.cm)}))})),partitions:(t.partitions||[]).map(t=>({id:t.id,a:[t.a[0]*wa,t.a[1]*e],b:[t.b[0]*wa,t.b[1]*e],cm:Number(t.cm)})),wall_columns:(t.wall_columns||[]).map(t=>({id:t.id,shape:"circle"===t.shape?"circle":"square",center:[t.center[0]*wa,t.center[1]*e],cm:Number(t.cm),..."circle"===t.shape?{}:{angle:ra(t.angle)}}))}}):[]}const Da=5e3,Ta=Da*wa,Pa=240,Ra=wa/Pa,Aa=1/Pa;function za(t){if(!Number.isFinite(t))return t;const e=Math.round(t*Pa/wa)*wa/Pa;return Math.abs(e-t)<=1e-9*Ra?t:e}function Ia(t){return{x:za(t.x),y:za(t.y)}}function Ea(t){return Number.isFinite(t)?Math.min(Ta,Math.max(-Ta,t)):0}function Na(t){return Number.isFinite(t)?Math.min(Da,Math.max(-Da,t)):0}const Fa=1/3,Oa=200;function La(t){let e=1/0,i=1/0,s=-1/0,o=-1/0;for(const n of t){const t=Number(n[0]),r=Number(n[1]);Number.isFinite(t)&&Number.isFinite(r)&&(ts&&(s=t),r>o&&(o=r))}return e>s?null:{minX:e,minY:i,maxX:s,maxY:o}}function Ha(t){return t.poly&&t.poly.length?La(t.poly):null==t.x||null==t.y?null:La([[t.x,t.y],[t.x+(t.w||0),t.y+(t.h||0)]])}function qa(t,e){const i=[];for(const e of t.rooms||[]){const t=Ha(e);t&&i.push(t)}if(t.bg){const e=La(sr(t.bg));e&&i.push(e)}for(const t of e||[])if(Array.isArray(t)){const e=La([t]);e&&i.push(e)}else i.push(t);return i}const Ua=t=>{if(!t.length)return 0;const e=[...t].sort((t,e)=>t-e),i=e.length>>1;return e.length%2?e[i]:(e[i-1]+e[i])/2},Ba=t=>{let e=1/0,i=1/0,s=-1/0,o=-1/0;for(const n of t)n.minXs&&(s=n.maxX),n.maxY>o&&(o=n.maxY);return e>s||i>o?null:{x:e,y:i,w:s-e,h:o-i}};function ja(t,e){let{x:i,y:s,w:o,h:n}=t;o<30&&(i=i+o/2-100,o=Oa),n<30&&(s=s+n/2-100,n=Oa);const r=Math.max(o,n)*e;return{x:i-r,y:s-r,w:o+2*r,h:n+2*r}}function Ga(t,e={}){const i=e.pad??.05,s=e.k??10,o=e.minSpread??50,n=t.filter(t=>Number.isFinite(t.minX)&&Number.isFinite(t.minY)&&Number.isFinite(t.maxX)&&Number.isFinite(t.maxY)&&Math.abs(t.minX)<=Ta&&Math.abs(t.maxX)<=Ta&&Math.abs(t.minY)<=Ta&&Math.abs(t.maxY)<=Ta);if(!n.length)return{core:null,all:null,outliers:0};const r=Ba(n);if(n.length<4){const t=ja(r,i);return{core:t,all:t,outliers:0}}const a=n.map(t=>(t.minX+t.maxX)/2),l=n.map(t=>(t.minY+t.maxY)/2),c=Ua(a),h=Ua(l),d=n.map((t,e)=>Math.max(Math.abs(a[e]-c),Math.abs(l[e]-h))),p=Math.max(((t,e)=>t.length?t[Math.min(t.length-1,Math.max(0,Math.round(e*(t.length-1))))]:0)([...d].sort((t,e)=>t-e),.75),o),u=d.map(t=>t>s*p),_=u.filter(Boolean).length,m=_&&_<=n.length*Fa?n.filter((t,e)=>!u[e]):n;return{core:ja(Ba(m)||r,i),all:ja(r,i),outliers:m===n?0:_}}function Wa(t,e,i=.05){const s=Ga(qa(t,e),{pad:i});if(s.core)return s.core;const o=t.vb&&4===t.vb.length&&t.vb[2]>0&&t.vb[3]>0?t.vb:[0,0,wa,wa];return{x:o[0],y:o[1],w:o[2],h:o[3]}}function Va(t){const e=Wa(t);return{x:e.x+e.w/2,y:e.y+e.h/2}}function Ka(t){const e=[];for(const i of t.rooms||[]){const t=Ha(i);t&&e.push(t)}const i=Ga(e,{pad:0}).core;return i?Math.max(wa,Math.min(Ta,Math.max(i.w,i.h))):wa}function Ya(t,e,i,s=1){const o=Number(i),n=Number.isFinite(s)&&s>0?s:1;return!Number.isFinite(o)||o<=0?t*n:t*Ka(e)*n/o}const Ja=[1,2,5,10,20,50,100,200,500,1e3];function Za(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),o=Math.min(...i);return{x:s,y:o,w:Math.max(...e)-s,h:Math.max(...i)-o}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Xa(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}const Qa=["furniture","appliance","sanitary","other"],tl=[{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]]}],el=new Map(tl.map(t=>[t.id,t]));function il(t){return t&&el.get(t)||null}function sl(t){return tl.filter(e=>e.group===t)}function ol(t,e,i=Ra,s=1e3){const o=Number(e)>0?Number(e):5;return(Number(t)||0)/o*i/s}const nl=5e-4,rl=Da;function al(t){return Number.isFinite(t)?Math.max(nl,Math.min(rl,t)):nl}const ll=t=>{const e=Math.round(1e3*t)/1e3;return Object.is(e,-0)?"0":String(e)};function cl(t,e,i){const s=il(t);if(!(s&&e>0&&i>0))return"";const o=t=>ll(t*e),n=t=>ll(t*i),r=[];for(const t of s.g)if("r"===t[0]){const[,e,i,s,a]=t;r.push(`M${o(e)} ${n(i)}H${o(e+s)}V${n(i+a)}H${o(e)}Z`)}else if("l"===t[0]){const[,e,i,s,a]=t;r.push(`M${o(e)} ${n(i)}L${o(s)} ${n(a)}`)}else if("e"===t[0]){const[,s,a,l,c]=t;r.push(`M${o(s-l)} ${n(a)}A${ll(l*e)} ${ll(c*i)} 0 0 1 ${o(s+l)} ${n(a)}A${ll(l*e)} ${ll(c*i)} 0 0 1 ${o(s-l)} ${n(a)}Z`)}else{const e=t.slice(1);if(e.length<4)continue;let i=`M${o(e[0])} ${n(e[1])}`;for(let t=2;t+1{let e=(t%360+360)%360;return e>180&&(e-=360),e};function dl(t,e,i,s,o,n=0){let r=null,a=o;for(const o of s){const[s,l,c,h]=o,d=c-s,p=h-l,u=d*d+p*p;if(!u)continue;const _=Math.sqrt(u);let m=((t-s)*d+(e-l)*p)/u;m=Math.max(0,Math.min(1,m));let g=s+m*d,f=l+m*p;const v=Math.hypot(t-g,e-f);if(!(v0){const t=Math.round(m*_/n)*n;g=s+t/_*d,f=l+t/_*p}r={cx:g+y*(i/2),cy:f+b*(i/2),angle:hl(180*Math.atan2(-y,b)/Math.PI),dist:v}}return r}function pl(t,e,i){const s=i;return{a:[t[0]/s,t[1]/s],b:[e[0]/s,e[1]/s]}}function ul(t,e){const i=e;return[t.a[0]*i,t.a[1]*i,t.b[0]*i,t.b[1]*i]}function _l(t){return Array.isArray(t)&&t.length>=2&&Number.isFinite(Number(t[0]))&&Number.isFinite(Number(t[1]))}function ml(t){if(!Array.isArray(t))return[];const e=[];for(const i of t){if(!i||"object"!=typeof i)continue;const t=i;if(!_l(t.a)||!_l(t.b))continue;const s=[Number(t.a[0]),Number(t.a[1])],o=[Number(t.b[0]),Number(t.b[1])];Math.hypot(o[0]-s[0],o[1]-s[1])<.001||e.push({a:s,b:o})}return e}function gl(t,e){const i=e[0],s=e[1],o=e[2]-i,n=e[3]-s,r=o*o+n*n;if(r<1e-18){return{q:[i,s],t:0,d:Math.hypot(t[0]-i,t[1]-s)}}let a=((t[0]-i)*o+(t[1]-s)*n)/r;a=Math.max(0,Math.min(1,a));const l=[i+a*o,s+a*n];return{q:l,t:a,d:Math.hypot(t[0]-l[0],t[1]-l[1])}}function fl(t,e){return gl(t,e).q}function vl(t,e,i,s,o){const n=gl(t,e).q;let r=n,a=1/0;for(const t of i){const e=Math.hypot(n[0]-t[0],n[1]-t[1]);e<=o&&e0?s:1,m=Math.round(u/_)*_,g=Math.max(0,Math.min(p,m))/p;return[l+h*g,c+d*g]}function yl(t,e){const i=[],s=(t||[]).filter(t=>t?.id);for(let t=0;tt?.id),r=ml(e);if(r.length)return Dl(r,t,i,s).map(t=>ul(t,i));if(!o)return[];const a=[],l=(t,e)=>(t.open_to||[]).includes(e.id)||(e.open_to||[]).includes(t.id);for(let t=0;tpl([t[0],t[1]],[t[2],t[3]],e))}function kl(t,e,i,s){for(const e of t||[])e.open_to&&delete e.open_to;if(!i.length)return;const o=(e||[]).filter(t=>t?.id);new Map(o.map(t=>[t.id,t]));const n=new Map((t||[]).filter(t=>t?.id).map(t=>[t.id,t])),r=(t,e)=>{const i=n.get(t),s=n.get(e);i&&s&&((i.open_to||[]).includes(e)||(i.open_to=[...i.open_to||[],e]),(s.open_to||[]).includes(t)||(s.open_to=[...s.open_to||[],t]))};for(let t=0;tSs(i,t)<4*s)){r(o[t].id,o[e].id);break}}}for(const e of t||[])e.open_to&&!e.open_to.length&&delete e.open_to}function $l(t,e,i,s=0){const o=e[2]-e[0],n=e[3]-e[1],r=Math.hypot(o,n);if(!(r>1e-9))return null;const a=o/r,l=n/r,c=(t[0]-e[0])*a+(t[1]-e[1])*l;if(c<-s||c>r+s)return null;return Math.abs((t[0]-e[0])*l-(t[1]-e[1])*a)>i?null:Ss(t,e)}function xl(t,e,i){const s=t[2]-t[0],o=t[3]-t[1],n=e[2]-e[0],r=e[3]-e[1],a=Math.hypot(s,o),l=Math.hypot(n,r);return a>1e-9&&l>1e-9&&(!(Math.abs(s/a*(r/l)-o/a*(n/l))>1e-6)&&(Ss([e[0],e[1]],t)<=4*i||Ss([t[0],t[1]],e)<=4*i))}function Sl(t,e,i){if(!t.length)return null;t.sort((t,e)=>t.distance-e.distance);const s=t[0];for(let o=1;oe)break;if(n.semantic!==s.semantic||!xl(s.seg,n.seg,i))return"ambiguous"}return s}function Ml(t,e,i,s,o=1,n=15){const r=function(t,e,i,s,o=1,n=15){const[r,a]=ur([e[0],e[1]],[e[2],e[3]]);let l=0,c=1/0;const h=[(e[0]+e[2])/2,(e[1]+e[3])/2];for(const e of i){const[i,n]=ur([e[0],e[1]],[e[2],e[3]]);if(Math.abs(r*n-a*i)>.05)continue;const d=kr(t,[e[0],e[1]],[e[2],e[3]],s,o);if(!(d>0))continue;const p=[(e[0]+e[2])/2,(e[1]+e[3])/2],u=Math.hypot(p[0]-h[0],p[1]-h[1]);u0?l:n}(t,e,i,s,o,n);return Mr(t,[e[0],e[1]],[e[2],e[3]],r,s,o)}function Cl(t,e,i){const s=[[t[0],t[1]],[t[2],t[3]]],o=[(t[0]+t[2])/2,(t[1]+t[3])/2];for(const n of e)if(!(Ss(o,n)>Math.hypot(t[2]-t[0],t[3]-t[1])&&Ss([n[0],n[1]],t)>i))for(const e of[[n[0],n[1]],[n[2],n[3]]])Ss(e,t)<=2*i&&s.push(e);return s}function Dl(t,e,i,s){const o=ml(t);if(!o.length)return[];const n=yl(e,s);if(!n.length)return[];const r=[],a=Math.max(4*s,1e-6);for(const t of o){const e=ul(t,i),o=e[0],l=e[1],c=e[2]-o,h=e[3]-l,d=Math.hypot(c,h);if(d4*s||n>4*s)continue;const r=(t[0]-o)*p+(t[1]-l)*u,c=(t[2]-o)*p+(t[3]-l)*u,h=Math.max(0,Math.min(r,c)),m=Math.min(d,Math.max(r,c));if(m-ht.lo-e.lo||t.hi-e.hi);const i=[];for(const t of e){const e=i[i.length-1];e&&t.lo<=e.hi+a?e.hi=Math.max(e.hi,t.hi):i.push({...t})}for(const e of i){const i=[o+p*e.lo,l+u*e.lo],s=[o+p*e.hi,l+u*e.hi];Math.hypot(s[0]-i[0],s[1]-i[1])i.pair===t&&Math.abs(i.ux*r-i.uy*n)<=1e-6&&Math.abs((e[0]-i.origin[0])*i.uy-(e[1]-i.origin[1])*i.ux)<=c&&Math.abs((e[2]-i.origin[0])*i.uy-(e[3]-i.origin[1])*i.ux)<=c);h||(h={pair:t,origin:[e[0],e[1]],ux:n,uy:r,ranges:[]},l.push(h));const d=(e[0]-h.origin[0])*h.ux+(e[1]-h.origin[1])*h.uy,p=(e[2]-h.origin[0])*h.ux+(e[3]-h.origin[1])*h.uy;h.ranges.push({lo:Math.min(d,p),hi:Math.max(d,p)})}const h=[];for(const t of l){t.ranges.sort((t,e)=>t.lo-e.lo||t.hi-e.hi);const e=[];for(const i of t.ranges){const t=e[e.length-1];t&&i.lo<=t.hi+a?t.hi=Math.max(t.hi,i.hi):e.push({...i})}for(const s of e){const e=[t.origin[0]+t.ux*s.lo,t.origin[1]+t.uy*s.lo],o=[t.origin[0]+t.ux*s.hi,t.origin[1]+t.uy*s.hi];Math.hypot(o[0]-e[0],o[1]-e[1])>=a&&h.push(pl(e,o,i))}}return h}function Tl(t,e,i,s){const o=ml(t);if(!o.length)return[];if(e.length!==i.length)return o;const n=[];for(const t of o){const o=ul(t,s),r=[o[0],o[1]],a=[o[2],o[3]];let l=r,c=a;for(let t=0;t{const e=gl(t,[s[0],s[1],o[0],o[1]]);if(e.d>.001)return t;const i=h[0]-n[0],r=h[1]-n[1];return[n[0]+i*e.t,n[1]+r*e.t]};if(gl([(r[0]+a[0])/2,(r[1]+a[1])/2],[s[0],s[1],o[0],o[1]]).d<.01){l=d(r),c=d(a);break}}n.push(pl(l,c,s))}return n}class Pl{constructor(t,e=()=>Date.now()){this.onUpdate=t,this.now=e,this.signed={},this.queued=new Set,this.inFlight=new Map,this.retry=new Map,this.disposed=!1}start(t,e){this.disposed=!1,this.stopTimer(),this.resignTimer=setInterval(()=>this.resign(t(),e()),288e5)}dispose(){this.disposed=!0,this.stopTimer(),clearTimeout(this.batchTimer),this.queued.clear(),this.inFlight.clear()}stopTimer(){void 0!==this.resignTimer&&clearInterval(this.resignTimer),this.resignTimer=void 0}display(t,e){const i=ys(e);if(!i.startsWith("/api/houseplan/content/"))return i;const s=this.signed[i],o=s?this.now()-s.at:1/0;return o{const e=[...this.queued];this.queued.clear(),this.sign(t,e)},30))}sign(t,e){if(e.length&&t?.callWS)for(const i of function(t,e){const i=Math.max(1,Math.floor(e)),s=[];for(let e=0;e{if(this.disposed)return;const e=this.now(),s={...this.signed};let o=0;for(const n of i){const i=t?.urls?.[n];"string"==typeof i&&i?(s[n]={url:i,at:e},this.retry.delete(n),o++):this.backOff(n)}o&&(this.signed=s,this.onUpdate())}).catch(()=>{for(const t of i)this.backOff(t)}).finally(()=>{for(const t of i)this.inFlight.get(t)===e&&this.inFlight.delete(t)})}}backOff(t){const e=this.retry.get(t)?.delay||0,i=Math.min(6e4,e?2*e:2e3);this.retry.set(t,{notBefore:this.now()+i,delay:i})}resign(t,e){const i=this.now(),s={};for(const[t,o]of Object.entries(this.signed))e.has(t)&&i-o.at{if(null==t||""===t)return null;const e=Number(t);return Number.isFinite(e)?e:null},Il=t=>{const e=t,i=zl(Array.isArray(e)?e[0]:e?.x),s=zl(Array.isArray(e)?e[1]:e?.y);return null==i||null==s?null:[i,s]};function El(t){if(!Array.isArray(t))return null;const e=[];for(const i of t){const t=Il(i);if(!t)return null;e.push(t)}if(e.length>1){const t=e[0],i=e[e.length-1];t[0]===i[0]&&t[1]===i[1]&&e.pop()}if(e.length<3)return null;let i=0,s=0,o=0;for(let t=0;tt+e[0],0)/e.length,e.reduce((t,e)=>t+e[1],0)/e.length]:[s/(3*i),o/(3*i)]}function Nl(t){if(!Array.isArray(t))return null;const e=[];for(const i of t){const t=Il(i);if(!t)return null;e.push(t)}if(e.length>1){const t=e[0],i=e[e.length-1];t[0]===i[0]&&t[1]===i[1]&&e.pop()}const i=El(e);if(!i)return null;const s=e.map(t=>t[0]),o=e.map(t=>t[1]);return{centroid:i,bbox:[Math.min(...s),Math.min(...o),Math.max(...s),Math.max(...o)]}}function Fl(t){let e=t;if(e&&"object"==typeof e&&!Array.isArray(e)){const t=e;e=t.path??t.points??e}if(!Array.isArray(e)||!e.length)return[];let i=(e.some(t=>(t=>Array.isArray(t)?t.length>=2&&!Array.isArray(t[0])&&("object"!=typeof t[0]||null==t[0]):!!t&&"object"==typeof t&&("x"in t||"y"in t))(t))?[e]:e.filter(Array.isArray)).map(t=>t.map(Il).filter(t=>null!=t)).filter(t=>t.length>=2);i.length>64&&(i=i.slice(-64));if(i.reduce((t,e)=>t+e.length,0)<=4e3)return i.map(t=>t.slice());const s=4e3-2*i.length,o=i.map(t=>t.length-2),n=o.reduce((t,e)=>t+e,0),r=o.map(t=>s*t/n),a=r.map(Math.floor);let l=s-a.reduce((t,e)=>t+e,0);const c=r.map((t,e)=>({index:e,fraction:t-Math.floor(t)})).sort((t,e)=>e.fraction-t.fraction||t.index-e.index);for(let t=0;tfunction(t,e){if(e>=t.length)return t.slice();if(e<=2)return[t[0],t[t.length-1]];const i=[];for(let s=0;st.slice()),i=e[e.length-1];return i?(i.length>2?i.pop():e.pop(),e):[]}function Hl(t){if(!t)return null;const e=t.vacuum_position||t.robot_position||null,i=e&&null!=zl(e.x)&&null!=zl(e.y)?{x:zl(e.x),y:zl(e.y),a:zl(e.a??e.angle??e.theta)}:null,s=Fl(t.path?.path??t.path?.points??t.path),o=[],n=t.rooms,r=Array.isArray(n)?n.map((t,e)=>[String(t?.id??e),t]):n&&"object"==typeof n?Object.entries(n):[];for(const[t,e]of r){if(!e||"object"!=typeof e)continue;const i=String(e.name??e.label??"").trim(),s=[[zl(e.cx),zl(e.cy)],[zl(e.center?.x),zl(e.center?.y)],[zl(e.x),zl(e.y)]].find(([t,e])=>null!=t&&null!=e);let n=s?.[0]??null,r=s?.[1]??null;const a=Nl(e.outline),l=zl(e.x0),c=zl(e.y0),h=zl(e.x1),d=zl(e.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)||a?.bbox||null;if(null!=n&&null!=r||!a||([n,r]=a.centroid),null!=n&&null!=r||!p||(n=(p[0]+p[2])/2,r=(p[1]+p[3])/2),i&&null!=n&&null!=r){const e={id:t,name:i,cx:n,cy:r};p&&([e.x0,e.y0,e.x1,e.y1]=p),o.push(e)}}const a=function(t){return String(t.map_name??t.current_map??t.map_index??t.selected_map??"default")}(t);return i||o.length||s.length?{pos:i,path:s,rooms:o,mapId:a}:null}function ql(t,e,i){if(!t||!e)return null;const s=e.attributes||{},o=t.split(".")[0]||"",n=null!=i?.platform?String(i.platform):null,r=!!Il(s.vacuum_position??s.robot_position),a=!(!s.rooms||"object"!=typeof s.rooms),l=Fl(s.path?.path??s.path?.points??s.path).length>0,c=[s.map_name,s.current_map,s.map_index,s.selected_map].some(t=>null!=t),h="xiaomi_cloud_map_extractor"===n,d=a||l||c;let p=null;if(r?p="compatible":h?p="known_xcme_incomplete":d?p="partial":"camera"===o&&(p="camera"),!p)return null;const u=r?"camera"===o?300:200:h?100:d?50:0;return{entityId:t,name:String(s.friendly_name||t),platform:n,category:p,hasPosition:r,hasRooms:a,hasPath:l,hasMapId:c,score:u}}const Ul=t=>t.toLowerCase().replace(/[\s_\-.,]+/g,"");function Bl(t,e){const i=new Map(e.map(t=>[Ul(t.name),t])),s=[],o=[];for(const e of t){const t=i.get(Ul(e.name));t&&(s.push([[e.cx,e.cy],[t.cx,t.cy]]),o.push(e.name))}if(s.length<3)return null;const n=function(t){if(t.length<3)return null;let e=0,i=0,s=0,o=0,n=0,r=0,a=0,l=0,c=0,h=0,d=0,p=0;for(const[[u,_],[m,g]]of t){if(![u,_,m,g].every(Number.isFinite))return null;e+=u*u,i+=u*_,s+=u,o+=_*_,n+=_,r+=1,a+=u*m,l+=_*m,c+=m,h+=u*g,d+=_*g,p+=g}const u=[e,i,s,i,o,n,s,n,r],_=t=>{const[e,i,s,o,n,r,a,l,c]=u,h=e*(n*c-r*l)-i*(o*c-r*a)+s*(o*l-n*a);if(!Number.isFinite(h)||Math.abs(h)<1e-9)return null;const d=[(n*c-r*l)/h,(s*l-i*c)/h,(i*r-s*n)/h,(r*a-o*c)/h,(e*c-s*a)/h,(s*o-e*r)/h,(o*l-n*a)/h,(i*a-e*l)/h,(e*n-i*o)/h];return[d[0]*t[0]+d[1]*t[1]+d[2]*t[2],d[3]*t[0]+d[4]*t[1]+d[5]*t[2],d[6]*t[0]+d[7]*t[1]+d[8]*t[2]]},m=_([a,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 n?{matrix:n,matched:o,residual:Al(n,s)}:null}function jl(t,e,i){const s=t[t.length-1];if(s&&s[0]===e[0]&&s[1]===e[1])return t;if(t.push(e),t.length<=600)return t;let o=function(t,e){if(t.length<3)return t.slice();const i=new Uint8Array(t.length);i[0]=i[t.length-1]=1;const s=[[0,t.length-1]];for(;s.length;){const[o,n]=s.pop(),[r,a]=t[o],[l,c]=t[n],h=l-r,d=c-a,p=Math.hypot(h,d)||1e-9;let u=0,_=-1;for(let e=o+1;eu&&(u=i,_=e)}_>0&&u>e&&(i[_]=1,s.push([o,_],[_,n]))}const o=[];for(let e=0;e600&&(o=o.filter((t,e)=>e%2==0||e===o.length-1)),o}function Gl(t){return"cleaning"===t||"returning"===t||"on"===t}const Wl={0:[1,0],90:[0,1],180:[-1,0],270:[0,-1]};function Vl(t){const[e,i]=Wl[t.rot]||[1,0],s=t.mir?-1:1;return[t.s*e*s,-t.s*i,t.ox,t.s*i*s,t.s*e,t.oy]}function Kl(t){const e=t[0]*t[4]-t[1]*t[3];if(!Number.isFinite(e)||Math.abs(e)<1e-12)return null;const i=e<0,s=Math.sqrt(Math.abs(e));let o=180*Math.atan2(-t[1],t[4])/Math.PI;return o=(90*Math.round(o/90)%360+360)%360,{ox:t[2],oy:t[5],s:s,rot:o,mir:i}}function Yl(t,e,i,s){const[o,n]=Rl(Vl(e),i,s),r=Vl({...t,ox:0,oy:0}),[a,l]=Rl(r,i,s);return{...t,ox:o-a,oy:n-l}}function Jl(t){const e=t?.trail_mode;return"never"===e||"cleaning"===e||"always"===e?e:!1===t?.trail?"never":"cleaning"}const Zl=1e-5;function Xl(t){const e=t.map(()=>[]);for(let i=0;i=1-1e-9||p<=1e-9||p>=1-1e-9||(e[i].push(d),e[r].push(p))}}const i=[];for(let s=0;st-e),1];for(let t=1;t{const i=e[2]-e[0],s=e[3]-e[1],o=i*i+s*s;if(!(o>0))return Math.hypot(t[0]-e[0],t[1]-e[1]);const n=Math.max(0,Math.min(1,((t[0]-e[0])*i+(t[1]-e[1])*s)/o));return Math.hypot(t[0]-(e[0]+n*i),t[1]-(e[1]+n*s))},ec=(t,e,i,s)=>{const o=s[2]-s[0],n=s[3]-s[1],r=e*n-i*o;if(Math.abs(r)<1e-12)return 1/0;const a=s[0]-t[0],l=s[1]-t[1],c=(a*n-l*o)/r;if(!(c>1e-9))return 1/0;const h=(a*i-l*e)/r;return h<-1e-9||h>1+1e-9?1/0:c};function ic(t,e,i,s=96){if(!(e>0&&Number.isFinite(t[0])&&Number.isFinite(t[1])))return[];const o=[];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=tc(t,s);i>e||i<1e-7||o.push(s)}const n=[],r=Math.max(12,Math.round(s));for(let t=0;tt-e);const a=[];let l=Number.NEGATIVE_INFINITY;for(const i of n){if(i-l<1e-9)continue;l=i;const s=Math.cos(i),n=Math.sin(i);let r=e;for(const e of o){const i=ec(t,s,n,e);i=3?a:[]}class sc extends ct{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return hn(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[],e=this._lang;return[{name:"title",selector:{text:{}}},t.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:t}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:[{value:"",label:dn(e,"editor.lang_auto")},{value:"en",label:dn(e,"editor.lang_en")},{value:"ru",label:dn(e,"editor.lang_ru")}]}}},{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 W;this._loadSpaces();const t=this._lang,e={title:dn(t,"editor.title"),default_floor:dn(t,"editor.default_floor"),language:dn(t,"editor.language"),icon_size:dn(t,"editor.icon_size"),show_temperature:dn(t,"editor.show_temperature"),live_states:dn(t,"editor.live_states"),show_signal:dn(t,"editor.show_signal"),kiosk:dn(t,"editor.kiosk"),cycle:dn(t,"editor.cycle")};return B`[t[0]-e[0],t[1]-e[1]],_o=(t,e)=>[t[0]+e[0],t[1]+e[1]],mo=(t,e)=>t[0]*e[0]+t[1]*e[1],go=t=>Math.hypot(t[0],t[1]);function fo(t){let e=0;for(let i=0;it[1]!=l>t[1]&&t[0]<(a-o)*(t[1]-r)/(l-r)+o&&(i=!i)}return i}([a[0]+r[0]*l,a[1]+r[1]*l],t)&&(r=[-r[0],-r[1]]),r}function bo(t,e,i,s){const n=s||yo(t,e),o=(e+1)%t.length;return t.map((t,s)=>s===e||s===o?[t[0]+n[0]*i,t[1]+n[1]*i]:[...t])}function wo(t,e,i,s){const n=[],o=uo(i,e),r=go(o);if(rs||h>s)continue;const d=mo(uo(o,e),a),p=mo(uo(l,e),a),u=Math.max(0,Math.min(d,p)),_=Math.min(r,Math.max(d,p));_-u>s&&n.push([[e[0]+a[0]*u,e[1]+a[1]*u],[e[0]+a[0]*_,e[1]+a[1]*_]])}return n}function ko(t,e,i,s,n){const o=wo(t,e,i,n);if(!o.length)return null;const r=t=>o.some(([e,i])=>vo(t,e,i)<=n),a=t.length,l=[];for(let o=0;on||f>n)continue;const v=mo(uo(e,c),u),y=mo(uo(i,c),u),b=Math.max(0,Math.min(v,y)),w=Math.min(p,Math.max(v,y));if(!(w-b<=n)){if(b>n&&bn&&wgo(uo(i,t[(s+1)%t.length]))>e);for(let t=0;t<2;t++)i=i.filter((t,s)=>{const n=i[(s-1+i.length)%i.length],o=i[(s+1)%i.length],r=(t[0]-n[0])*(o[1]-n[1])-(t[1]-n[1])*(o[0]-n[0]),a=go(uo(o,n))||1;return Math.abs(r)/a>e});return i.length>=3?i:t}function xo(t){const e=t.length;if(e<3)return!1;for(let i=0;i(t[0]-n[0])*a[1]-(t[1]-n[1])*a[0],c=t=>(t[0]-n[0])*a[0]+(t[1]-n[1])*a[1],h=i,d=r-i;for(const e of t){const t=Math.abs(l(e));if(t<=i)continue;const n=c(e);n<=h||n>=d||tMath.abs(r+(t-p)/g*(a-r));s=Math.min(s,f(_),f(m))}}return s}function Mo(t,e,i){if(ui(t,e,i))return!0;if(pi(t,e,i)||pi(e,t,i))return!1;let s=0;try{const i=We([[...t.map(t=>[t[0],t[1]]),[t[0][0],t[0][1]]]],[[...e.map(t=>[t[0],t[1]]),[e[0][0],e[0][1]]]]);for(const t of i)t?.[0]&&(s+=mi(t[0]))}catch{return!1}return s>Math.max(1e-7,i*i)}function Co(t,e,i,s,n){const o=[i.n[0]*s,i.n[1]*s],r={polys:{},openings:{},movedSpans:{}};if(Math.abs(s)<1e-9)return r;for(const e of t){if(e.id===i.roomId){r.polys[e.id]=bo(e.poly,i.edge,s,i.n),r.movedSpans[e.id]=[[_o(i.a,o),_o(i.b,o)]];continue}const t=wo(e.poly,i.a,i.b,n);if(!t.length)continue;const a=ko(e.poly,i.a,i.b,o,n);a&&(r.polys[e.id]=a,r.movedSpans[e.id]=t.map(([t,e])=>[_o(t,o),_o(e,o)]))}for(const t of e)vo([t.x,t.y],i.a,i.b)<=n&&(r.openings[t.id]=[t.x+o[0],t.y+o[1]]);return r}function Do(t,e,i){for(const s of e)for(let e=0;ei)continue;const c=(t.x-n[0])*l[0]+(t.y-n[1])*l[1];if(c-t.length/2>=-i&&c+t.length/2<=a+i)return!0}return!1}function To(t,e,i){return t.filter(t=>e.some(e=>{for(let s=0;sa.polys[t.id]||t.poly;for(const e of l){const s=t.find(t=>t.id===e),n=a.polys[e];if(!xo(n))return!1;const l=fo(s.poly),h=fo(n);if(Math.abs(h)t.find(t=>t.id===e).poly),d=t.map(c);for(const t of To(e,h,2*r)){const e=a.openings[t.id];if(!Do(e?{...t,x:e[0],y:e[1]}:t,d,2*r))return!1}return!0}function Po(t,e,i,s,n,o){const r=t=>[s[0]+(t[0]-s[0])*n,s[1]+(t[1]-s[1])*n],a={poly:t.poly.map(r),openings:{}};for(const s of e){let e=!1;for(let i=0;i{for(let e=0;et.id===i);if(!l)return!1;const c=t.filter(t=>t.id!==i).map(t=>t.poly),h=Po(l,e,c,s,n,2*a),d=h.poly,p=function(t){const e=function(t){const e=[...t].sort((t,e)=>t[0]-e[0]||t[1]-e[1]);if(e.length<3)return e;const i=(t,e,i)=>(e[0]-t[0])*(i[1]-t[1])-(e[1]-t[1])*(i[0]-t[0]),s=[];for(const t of e){for(;s.length>=2&&i(s[s.length-2],s[s.length-1],t)<=0;)s.pop();s.push(t)}const n=[];for(let t=e.length-1;t>=0;t--){const s=e[t];for(;n.length>=2&&i(n[n.length-2],n[n.length-1],s)<=0;)n.pop();n.push(s)}return s.pop(),n.pop(),s.concat(n)}(t);if(e.length<3)return 0;let i=1/0;for(let t=0;tt.id===i?d:t.poly);for(const t of To(e,[l.poly],2*a)){const e=h.openings[t.id];if(!Do(e?{...t,x:e[0],y:e[1]}:t,u,2*a))return!1}return!0}function zo(t,e,i){const s=i/e;return mi(t)*s*s/1e4}function Io(t,e){return e?`${Math.round(10.7639*t)} ft²`:`${(Math.round(10*t)/10).toFixed(1)} m²`}const Eo=[[-90,"#070c14"],[-12,"#070c14"],[-4,"#131a28"],[0,"#4a3527"],[10,"#e8ddcf"],[30,"#ffffff"],[90,"#ffffff"]],Fo=t=>Math.min(1,Math.max(0,t));function No(t){const e=Math.min(90,Math.max(-90,Number(t)||0));let i=Eo[Eo.length-1][1];for(let t=1;t{const o=[t.x+n[0]*i*s,t.y+n[1]*i*s];return e.find(t=>t.poly.length>=3&&si(o,t.poly))||null},r=o(1),a=o(-1);return r&&a?null:r||a?r?{normal:[-n[0],-n[1]],roomId:r.id}:{normal:n,roomId:a.id}:null}function Lo(t,e,i){return i>0&&t[0]*e[0]+t[1]*e[1]>.05}function Ho(t,e,i,s){return[[t[0],t[1]],[e[0],e[1]],[e[0]+i[0]*s,e[1]+i[1]*s],[t[0]+i[0]*s,t[1]+i[1]*s]]}function qo(t,e){try{const i=We([[...t.map(t=>[t[0],t[1]]),[t[0][0],t[0][1]]]],[[...e.map(t=>[t[0],t[1]]),[e[0][0],e[0][1]]]]),s=[];for(const t of i){const e=t?.[0];!Array.isArray(e)||e.length<4||s.push(e.slice(0,e.length-1).map(t=>[t[0],t[1]]))}return s}catch{return[]}}function Uo(t,e,i,s,n,o,r){if(!(s>0))return[];const a=function(t,e){const i=function(t,e){return function(t){const e=t%360;return e<0?e+360:e}(t-e)}(t,e)*Math.PI/180;return[Math.sin(i),-Math.cos(i)]}(i,n),l=[-a[0],-a[1]],c=function(t){const e=Math.min(90,Math.max(0,t));return.7*(.8+1.7*Math.pow(1-e/90,1.6))}(s),h=[];for(const i of e){if(!(i.length>0))continue;const e=Oo(i,t);if(!e||!Lo(e.normal,a,s))continue;const n=t.find(t=>t.id===e.roomId);if(!n)continue;const d=o&&o[e.roomId]||n.poly,p=i.angle*Math.PI/180,u=i.length/2,_=[-e.normal[0],-e.normal[1]],m=Math.max(0,r?.[i.id]||0),g=i.x+_[0]*m/2,f=i.y+_[1]*m/2,v=Math.cos(p)*u,y=Math.sin(p)*u,b=[g-v,f-y],w=[g+v,f+y],k=c*i.length,$=qo(Ho(b,w,l,k),d);if(!$.length)continue;const x=l[0]*_[0]+l[1]*_[1];h.push({openingId:i.id,roomId:e.roomId,polys:$,a:b,b:w,dir:l,len:k,normal:_,depth:k*x})}return h}function Bo(t){return Math.round(10*(Number(t)||0))/10}const Wo=t=>"number"==typeof t&&Number.isInteger(t)&&t>=0&&t<=359?t:null;function jo(t,e){const i=Wo(e?.north_deg);return null!==i?i:Wo(t?.north_deg)}function Go(t,e){const i=t=>"static"===t||"daynight"===t?t:null;return i(e?.bg_mode)??i(t?.bg_mode)??"static"}function Vo(t,e){const i=e?.sun_rays;return"boolean"==typeof i?i:!0===t?.sun_rays}function Ko(t){const e=t?.states?.["sun.sun"]?.attributes,i=Number(e?.azimuth),s=Number(e?.elevation);return Number.isFinite(i)&&Number.isFinite(s)?{azimuth:i,elevation:s}:null}const Yo={color:"#607d8b",opacity:1,widthCm:3.6,fill:!1,fillColor:"#607d8b",fillOpacity:.25},Jo=(t,e=1)=>{const i=Number(t);return Number.isFinite(i)?Math.min(1,Math.max(0,i)):e},Zo=t=>{let e=Number(t);return Number.isFinite(e)?(e=(e%360+360)%360,e>180?e-360:e):0};function Xo(t,e,i){return Zo(e)?[t.x,t.y]:i([t.x,t.y])}const Qo=(t,e,i)=>{const s=Number(t),n=Number.isFinite(e)&&e>0?e:5;return Number.isFinite(s)&&s>0?s/n*i:0},tr=(t,e,i)=>{const s=Number(t),n=Number.isFinite(e)&&e>0?e:5;return Number.isFinite(s)&&i>0?s/i*n:0},er=(t,e,i,s=Yo.widthCm)=>{const n=Number(t?.width_cm);if(Number.isFinite(n)&&n>0)return n;const o=Number(t?.width);return Number.isFinite(o)&&o>0?tr(o,e,i):s};function ir(t,e){return{color:t.color,opacity:Jo(t.opacity),width_cm:Math.max(.1,Math.min(100,Number(t.widthCm)||.1)),...e?{fill:t.fill,fill_color:t.fillColor,fill_opacity:Jo(t.fillOpacity,.25)}:{}}}function sr(t){const e=t.x+t.w/2,i=t.y+t.h/2,s=Zo(t.angle)*Math.PI/180,n=Math.cos(s),o=Math.sin(s),r=(t,s)=>{const r=t-e,a=s-i;return[e+r*n-a*o,i+r*o+a*n]};return[r(t.x,t.y),r(t.x+t.w,t.y),r(t.x+t.w,t.y+t.h),r(t.x,t.y+t.h)]}function nr(t){const e=sr(t),i=(t,e)=>[(t[0]+e[0])/2,(t[1]+e[1])/2],s=[(e[0][0]+e[2][0])/2,(e[0][1]+e[2][1])/2];return{points:[...e,i(e[0],e[1]),i(e[1],e[2]),i(e[2],e[3]),i(e[3],e[0]),s],segments:e.map((t,i)=>({a:t,b:e[(i+1)%4]}))}}function or(t,e,i,s,n,o,r,a){const l=Zo(t.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l),d=-Math.sin(l),p=Math.cos(l),u=t.x+t.w/2,_=t.y+t.h/2,m=e>0?-t.w/2:t.w/2,g=i>0?-t.h/2:t.h/2,f=u+m*c+g*d,v=_+m*h+g*p,y=s-f,b=n-v;let w=(y*c+b*h)*(e>0?1:-1),k=(y*d+b*p)*(i>0?1:-1),$=null;if(o){const e=w/Math.max(t.w,a),i=k/Math.max(t.h,a);$=Math.max(a/Math.max(t.w,a),a/Math.max(t.h,a),e,i),w=t.w*$,k=t.h*$}if(r>0)if(o){const e=Math.max(1,Math.round(t.w/r)),i=Math.max(1,Math.round(t.h/r));if(Math.abs(t.w-e*r)<1e-6&&Math.abs(t.h-i*r)<1e-6){const s=(t,e)=>{let i=Math.abs(t),s=Math.abs(e);for(;s;)[i,s]=[s,i%s];return Math.max(1,i)},n=1/s(e,i),o=Math.max(a/t.w,a/t.h),r=Math.max(o,Math.round(($??1)/n)*n);w=t.w*r,k=t.h*r}else{t.w>=t.h?(w=Math.round(w/r)*r,k=w*(t.h/Math.max(t.w,a))):(k=Math.round(k/r)*r,w=k*(t.w/Math.max(t.h,a)))}}else w=Math.round(w/r)*r,k=Math.round(k/r)*r;w=Math.max(a,w),k=Math.max(a,k);return{x:f+(e>0?w/2:-w/2)*c+(i>0?k/2:-k/2)*d-w/2,y:v+(e>0?w/2:-w/2)*h+(i>0?k/2:-k/2)*p-k/2,w:w,h:k,angle:Zo(t.angle)||void 0}}const rr=(t,e,i)=>{const s=i[0]-e[0],n=i[1]-e[1],o=s*s+n*n;if(o<1e-12)return[...e];const r=Math.max(0,Math.min(1,((t[0]-e[0])*s+(t[1]-e[1])*n)/o));return[e[0]+s*r,e[1]+n*r]};const ar=15;function lr(t,e){return Number.isFinite(t)&&t>0&&Number.isFinite(e)&&e>0&&t*e<3}function cr(t){return Number.isFinite(t)?Math.max(1,Math.min(100,t)):1}function hr(t,e){return!Number.isFinite(t)||t<=0?"":String(e?Math.round(t/2.54*100)/100:Math.round(100*t)/100)}function dr(t,e,i){const s=Number(e)>0?Number(e):5;return cr(t)/s*i}function pr(t,e){return e>0&&Number.isFinite(t)?Math.round(t/e)*e:t}function ur(t,e){let i=e[0]-t[0],s=e[1]-t[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 _r(t,e,i){const s=pr((t[0]+e[0])/2,i),n=pr((t[1]+e[1])/2,i),[o,r]=ur(t,e);let a=Math.atan2(r,o);a<0&&(a+=Math.PI);const l=Math.round(1800*a)/1800,c=i>0&&i<.01?6:i<1?4:2;return`${s.toFixed(c)},${n.toFixed(c)}@${l.toFixed(4)}`}function mr(t,e,i,s){return 1===s?_r(t,e,i):_r([t[0]/s,t[1]/s],[e[0]/s,e[1]/s],i)}function gr(t,e){if(!Array.isArray(t.a)||!Array.isArray(t.b)||t.a.length<2||t.b.length<2)return null;const i=[Number(t.a[0]),Number(t.a[1]),Number(t.b[0]),Number(t.b[1])];if(!i.every(Number.isFinite))return null;const s=e>0?e:1;return[[i[0]*s,i[1]*s],[i[2]*s,i[3]*s]]}function fr(t,e,i,s,n){const o=n>0?n:1;return{key:mr(t,e,s,o),cm:cr(i),a:[t[0]/o,t[1]/o],b:[e[0]/o,e[1]/o]}}function vr(t,e){const i=e>0?e:1,s=[];for(const e of t){const t=e.key.lastIndexOf("@");if(t<0)continue;const[n,o]=e.key.slice(0,t).split(",").map(Number),r=Number(e.key.slice(t+1));[n,o,r].every(Number.isFinite)&&s.push({w:e,x:n*i,y:o*i,ang:r})}return s}function yr(t,e){const[i,s]=ur(t,e);let n=Math.atan2(s,i);return n<0&&(n+=Math.PI),n}function br(t,e){let i=Math.abs(t-e);return i>Math.PI/2&&(i=Math.PI-i),i<.02}function wr(t,e,i,s,n=1){if(!t?.length)return null;const o=mr(e,i,s,n),r=t.find(t=>t.key===o);if(r)return r;const a=n>0?n:1,l=(e[0]+i[0])/2,c=(e[1]+i[1])/2,h=yr(e,i),d=Math.max(.5*s,1e-9)*a;for(const e of vr(t,a))if(br(e.ang,h)&&Math.hypot(e.x-l,e.y-c)<=d)return e.w;return null}function kr(t,e,i,s,n=1){const o=wr(t,e,i,s,n);return o&&o.cm>0?cr(o.cm):0}function $r(t,e,i,s=1,n=[]){if(!t?.length)return[];const o=new Set,r=Xe(e);for(const t of r)o.add(mr([t[0],t[1]],[t[2],t[3]],i,s));const a=e||[],l=Math.max(i*s*.02,1e-9);for(let t=0;t(o.has(t.key)||(t=>{const e=gr(t,s);if(!e)return!1;const[i,o]=e,a=o[0]-i[0],c=o[1]-i[1],h=Math.hypot(a,c);if(h<=l)return!1;if(!r.some(t=>{const e=[t[0],t[1]],s=[t[2],t[3]];return br(yr(i,o),yr(e,s))&&Br(i[0],i[1],e[0],e[1],s[0],s[1])<=l&&Br(o[0],o[1],e[0],e[1],s[0],s[1])<=l}))return!1;const d=(n||[]).some(t=>{const e=[t[0],t[1]],s=[t[2],t[3]];if(!br(yr(i,o),yr(e,s)))return!1;const n=t=>Math.abs((t[0]-i[0])*c-(t[1]-i[1])*a)/h;if(n(e)>l||n(s)>l)return!1;const r=h*h,d=((e[0]-i[0])*a+(e[1]-i[1])*c)/r,p=((s[0]-i[0])*a+(s[1]-i[1])*c)/r;return Math.min(1,Math.max(d,p))-Math.max(0,Math.min(d,p))>l/h});return!d})(t))&&t.cm>=1&&t.cm<=100)}function xr(t,e,i,s=8){const[n,o]=ur(t,e);let r=Math.atan2(o,n);r<0&&(r+=Math.PI);let a=i*Math.PI/180%Math.PI;a<0&&(a+=Math.PI);let l=Math.abs(r-a);return l>Math.PI/2&&(l=Math.PI-l),l<=s*Math.PI/180}function Sr(t,e,i,s,n=1){if(!t?.length)return[];if(e.length!==i.length)return t.slice();const o=new Map;for(let t=0;t0?n:1,a=Math.max(.5*s,1e-9)*r,l=new Set,c=[];for(const n of t){let t="",h=null;const d=gr(n,r);if(d)for(let n=0;na||Br(d[1][0],d[1][1],o[0],o[1],l[0],l[1])>a)continue;const u=l[0]-o[0],_=l[1]-o[1],m=u*u+_*_;if(m<1e-18)continue;const g=t=>{const e=Math.max(0,Math.min(1,((t[0]-o[0])*u+(t[1]-o[1])*_)/m));return[c[0]+(p[0]-c[0])*e,c[1]+(p[1]-c[1])*e]};h=[g(d[0]),g(d[1])],t=mr(h[0],h[1],s,r);break}if(d||(t=o.get(n.key)||""),!t){const o=vr([n],r)[0];if(o)for(let n=0;n1.000001)continue;if(Br(o.x,o.y,l[0],l[1],c[0],c[1])>a)continue;const g=h[0]+(d[0]-h[0])*Math.max(0,Math.min(1,m)),f=h[1]+(d[1]-h[1])*Math.max(0,Math.min(1,m)),[v,y]=ur(h,d),b=Math.max(s*r,1e-6);t=mr([g-v*b,f-y*b],[g+v*b,f+y*b],s,r);break}}t||(t=n.key),l.has(t)||(l.add(t),c.push(h?fr(h[0],h[1],n.cm,s,r):{...n,key:t,cm:cr(n.cm)}))}return c}function Mr(t,e,i,s,n,o=1){const r=mr(e,i,n,o),a=(t||[]).filter(t=>t.key!==r);return null==s||s<1?a:[...a,fr(e,i,s,n,o)]}function Cr(t,e,i,s,n,o=[],r=1){let a=t?t.slice():[];for(const t of function(t,e,i,s,n=1,o=[]){const r=Nr(t,e,i,s,n,o);if(!r)return[];const a=[];for(let t=0;t0)||!t||t.length<2)return"";if(i&&t.length>=3){let i=t;const s=t[t.length-1];if(t.length>=4&&Math.hypot(t[0][0]-s[0],t[0][1]-s[1])<1e-9&&(i=t.slice(0,-1)),i.length>=3){const t=i.map(()=>e),s=Kr(i,t),n=Ir(i,t);if(s&&n)return`${Er(s)} ${Er(Fr(n))}`}}let s="";for(let i=0;i.05)&&Br(r,a,t[0],t[1],t[2],t[3])<=o)return!0}return!1}function Rr(t,e){return Math.max(t*(e>0?e:1)*.04,1e-9)}function Pr(t,e){const i=t[e],s=t[(e+1)%t.length],n=s[0]-i[0],o=s[1]-i[1],r=Math.hypot(n,o)||1;let a=-o/r,l=n/r;const c=[(i[0]+s[0])/2,(i[1]+s[1])/2];return function(t,e){let i=!1;for(let s=0,n=e.length-1;st[1]!=l>t[1]&&t[0]<(a-o)*(t[1]-r)/(l-r+0)+o&&(i=!i)}return i}([c[0]+.001*a,c[1]+.001*l],t)||(a=-a,l=-l),[a,l]}function Ar(t,e){const i=t[0]*e[1]-t[1]*e[0],s=t[0]*e[0]+t[1]*e[1];return Math.abs(i)<1e-9&&s>0}function zr(t,e,i,s){const n=e[0]*s[1]-e[1]*s[0];if(Math.abs(n)<1e-12)return null;const o=[i[0]-t[0],i[1]-t[1]],r=(o[0]*s[1]-o[1]*s[0])/n;return[t[0]+r*e[0],t[1]+r*e[1]]}function Ir(t,e){const i=t?.length||0;if(i<3||e.length!==i)return null;if(e.every(t=>!(t>0)))return t.map(t=>[t[0],t[1]]);const s=[];for(let n=0;n0||d>0)){s.push([t[n][0],t[n][1]]);continue}if(Ar(b,w)){const e=t[n],i=[e[0]+p*h,e[1]+u*h],o=[e[0]+_*d,e[1]+m*d];s.push(i),Math.hypot(o[0]-i[0],o[1]-i[1])>1e-9&&s.push(o);continue}const x=zr(k,b,$,w),S=Math.max(h,d,1e-9);if(x){if(Math.hypot(x[0]-t[n][0],x[1]-t[n][1])<=4*S){s.push(x);continue}}h>0&&s.push([t[n][0]+p*h,t[n][1]+u*h]),d>0&&s.push([t[n][0]+_*d,t[n][1]+m*d]),h>0||d>0||s.push([t[n][0],t[n][1]])}return s.length>=3?s:null}function Er(t,e=!0){if(!t.length)return"";let i=`M ${t[0][0]} ${t[0][1]}`;for(let e=1;et?.id===e),a=Je(r);if(!a||a.length<3)return null;const l=Rr(s,n),c=[];for(const i of t||[]){if(!i||i.id===e)continue;const t=Je(i);if(t)for(const e of us(a,t,l))c.push([e[0],e[1]],[e[2],e[3]])}for(const t of i||[])c.push([t[0],t[1]],[t[2],t[3]]);for(const t of o||[]){const e=gr(t,n);e&&c.push(e[0],e[1])}const h=[],d=[];for(let t=0;tl)continue;const r=((t[0]-e[0])*(i[0]-e[0])+(t[1]-e[1])*(i[1]-e[1]))/(s*s);r<=n||r>=1-n||(o.some(t=>Math.abs(t-r)*s<=2*l)||o.push(r))}o.sort((t,e)=>t-e);for(const s of o)h.push([e[0]+(i[0]-e[0])*s,e[1]+(i[1]-e[1])*s]),d.push(t)}return{poly:h,parent:d,orig:a}}function Or(t,e,i,s){const n=e.poly.length,o=new Array(n).fill(0);if(!t?.length)return o;const r=new Set,a=[];for(let l=0;l0?(o[l]=cr(c.cm),r.add(c.key)):a.push(l)}if(!a.length)return o;const l=s>0?s:1,c=Math.max(.5*i,1e-9)*l,h=vr(t,l).filter(t=>t.w.cm>0),d=new Map;for(const t of a){const i=e.parent[t],s=d.get(i);s?s.push(t):d.set(i,[t])}for(const[t,i]of d){const s=e.orig[t],n=e.orig[(t+1)%e.orig.length],a=yr(s,n),d=(s[0]+n[0])/2,p=(s[1]+n[1])/2;let u=null;const _=Math.hypot(n[0]-s[0],n[1]-s[1]);for(const t of h){if(r.has(t.w.key))continue;if(!br(t.ang,a))continue;const e=gr(t.w,l);let i=!1,o=0;if(e){if(!br(yr(e[0],e[1]),a))continue;if(Br(s[0],s[1],e[0][0],e[0][1],e[1][0],e[1][1])>c||Br(n[0],n[1],e[0][0],e[0][1],e[1][0],e[1][1])>c)continue;i=!0,o=Math.max(0,Math.hypot(e[1][0]-e[0][0],e[1][1]-e[0][1])-_)}else{if(Br(t.x,t.y,s[0],s[1],n[0],n[1])>c)continue;o=Math.hypot(t.x-d,t.y-p)}(!u||i&&!u.exact||i===u.exact&&ot?.id===e),n=Je(s);if(!n)return[];const o=[];for(const s of t||[]){if(!s||s.id===e)continue;const t=Je(s);if(t)for(const e of us(n,t,i))o.push(e)}return o}(t,e,Rr(n,a)),h=function(t,e,i,s,n){const o=Rr(s,n),r=[];for(let a=0;aBr(h,d,t[0],t[1],t[2],t[3])<=o);r.push(p?"shared":"outer")}return r}(l.poly,c,s,n,a),d=Or(i,l,n,a),p=d.map((t,e)=>h[e]&&t>0?dr(t,o,r)/2:0);return{...l,kinds:h,cms:d,offsets:p}}function Hr(t,e,i,s,n,o,r=1){const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,n,o,r);if(c)for(let t=0;t0&&!l.has(c.key)&&(l.add(c.key),a.push(c));const c=[];for(const a of t||[]){if(!a?.id)continue;const l=Lr(t,a.id,e,i,s,n,o,r);if(l)for(let t=0;t0)||null===l.kinds[i]){t++;continue}let o=t;for(;o+10&&c.push({a:[h[0],h[1]],b:[d[0],d[1]],key:mr(h,d,s,r),cm:n,len:p}),t=o+1}}}c.sort((t,e)=>e.len-t.len||t.key.localeCompare(e.key));const h=[],d=new Set,p=new Set,u=4*Rr(s,r);for(const t of c){const e=a.filter(e=>!p.has(e.key)&&e.cm===t.cm&&br(yr(e.a,e.b),yr(t.a,t.b))&&Br(e.a[0],e.a[1],t.a[0],t.a[1],t.b[0],t.b[1])<=u&&Br(e.b[0],e.b[1],t.a[0],t.a[1],t.b[0],t.b[1])<=u);if(e.length){for(const t of e)p.add(t.key);d.has(t.key)||(d.add(t.key),h.push(fr(t.a,t.b,t.cm,s,r)))}}for(const t of a)p.has(t.key)||d.has(t.key)||(d.add(t.key),h.push(fr(t.a,t.b,t.cm,s,r)));return h}function Ur(t,e,i,s,n,o,r,a=1){const l=Rr(n,a),c=(s[0]+s[2])/2,h=(s[1]+s[3])/2,d=yr([s[0],s[1]],[s[2],s[3]]);let p=null;for(const s of Hr(t,e,i,n,o,r,a)){if(!br(yr(s.a,s.b),d))continue;const t=Br(c,h,s.a[0],s.a[1],s.b[0],s.b[1]);t>4*l||(!p||tt?.id===e),c=Je(l);if(!c||c.length<3)return null;if(!i?.length)return c.map(t=>[t[0],t[1]]);const h=Lr(t,e,i,s,n,o,r,a);return h&&h.offsets.some(t=>t>0)&&Ir(h.poly,h.offsets)||c.map(t=>[t[0],t[1]])}function jr(t){const e=t.map(t=>[t[0],t[1]]);return e.push([t[0][0],t[0][1]]),[e]}function Gr(t,e,i,s=[],n,o,r,a=1,l=[]){if(!e?.length&&!l.length)return null;const c=[];let h=0;for(const s of t||[]){if(!s?.id)continue;const l=Lr(t,s.id,e,i,n,o,r,a);if(!l||l.poly.length<3||!l.offsets.some(t=>t>0))continue;for(const t of l.offsets)t>0&&(h=Math.max(h,2*t));const d=Kr(l.poly,l.offsets),p=Ir(l.poly,l.offsets);d&&c.push({outset:d,inset:p})}for(const t of l){const e=t.map(t=>t[0]),i=t.map(t=>t[1]);if(e.length){const s=Math.min(Math.max(...e)-Math.min(...e),Math.max(...i)-Math.min(...i)),n=Math.min(...t.map((e,i)=>{const s=t[(i+1)%t.length];return Math.hypot(s[0]-e[0],s[1]-e[1])}));h=Math.max(h,t.length>16?s:n)}}const d=function(t,e,i,s,n,o,r){if(!e?.length||!i?.length)return[];const a=4*Rr(s,r),l=new Map;for(const a of Hr(t,e,i,s,n,o,r))!a.open&&a.half>0&&!l.has(a.key)&&l.set(a.key,a);const c=[...l.values()];if(c.length<2)return[];const h=[];for(const t of i)for(const e of[[t[0],t[1]],[t[2],t[3]]])h.some(t=>Math.hypot(t[0]-e[0],t[1]-e[1])<=a)||h.push(e);const d=[],p=(t,e)=>{let i=0,s=0;if(Math.hypot(t.a[0]-e[0],t.a[1]-e[1])<=a)i=t.b[0]-t.a[0],s=t.b[1]-t.a[1];else{if(!(Math.hypot(t.b[0]-e[0],t.b[1]-e[1])<=a))return null;i=t.a[0]-t.b[0],s=t.a[1]-t.b[1]}const n=Math.hypot(i,s);return n>a?[i/n,s/n]:null};for(const t of h){const e=c.map(e=>({iv:e,u:p(e,t)})).filter(t=>!!t.u);for(let i=0;i4*_||d.push(r>0?[t.slice(),h,u,p]:[t.slice(),p,u,h])}}return d}(t,e,i,n,o,r,a),p=s.length?Jr(t,e,i,n,o,r,a):null;try{const t=t=>{const e=jr(t.outset);return t.inset?je(e,jr(t.inset)):e};let e=c.length?t(c[0]):null;for(let i=1;i0))continue;const i=Qr(p,t,!0);if(!i.negative&&!i.positive)continue;const s=t.angle*Math.PI/180,o=Math.cos(s),r=Math.sin(s),l=-r,c=o,d=t.length/2,u=1.25*Math.max(h,n*a),_=[[t.x-o*d-l*u,t.y-r*d-c*u],[t.x+o*d-l*u,t.y+r*d-c*u],[t.x+o*d+l*u,t.y+r*d+c*u],[t.x-o*d+l*u,t.y-r*d+c*u]];e&&(e=je(e,jr(_)))}for(const t of l)t.length<3||(e=e?Be(e,jr(t)):[jr(t)]);return{geom:e||[],depthUnits:h}}catch{return null}}function Vr(t,e,i,s=[],n,o,r,a=1,l=[]){if(!e?.length&&!l.length)return null;const c=Gr(t,e,i,s,n,o,r,a,l),h=c?function(t){if(!t)return"";let e="";for(const i of t)if(Array.isArray(i))for(const t of i){if(!Array.isArray(t)||t.length<4)continue;const i=t.slice(0,t.length-1);i.length<3||(e+=(e?" ":"")+Er(i.map(t=>[t[0],t[1]])))}return e}(c.geom):"";if(c&&h)return{d:h,depthUnits:c.depthUnits,fillRule:"evenodd"};if(c)return null;const d=function(t,e,i,s,n,o,r=1){if(!e?.length)return[];const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,n,o,r);if(!c||c.poly.length<3||!c.offsets.some(t=>t>0))continue;const h=Kr(c.poly,c.offsets),d=Ir(c.poly,c.offsets);if(!h||!d)continue;const p=`${Er(h)} ${Er(Fr(d))}`;let u="",_="outer",m=0,g=0;for(let t=0;t0){u=mr(c.poly[t],c.poly[(t+1)%c.poly.length],s,r),_=c.kinds[t]||"outer",m=c.cms[t],g=dr(m,n,o);break}a.push({d:p,key:u,kind:_,cm:m,depthUnits:g})}return a}(t,e,i,n,o,r,a),p=l.map(t=>Er(t)).join(" ");if(!d.length&&!p)return null;let u=0;for(const t of d)u=Math.max(u,t.depthUnits);return{d:[d.map(t=>t.d).join(" "),p].filter(Boolean).join(" "),depthUnits:u,fillRule:"nonzero"}}function Kr(t,e){const i=t?.length||0;if(i<3||e.length!==i)return null;if(e.every(t=>!(t>0)))return t.map(t=>[t[0],t[1]]);Fr(t),e.slice().reverse();const s=[];for(let n=0;n0||a>0)){s.push([t[n][0],t[n][1]]);continue}if(Ar(b,w)){const e=t[n],i=[e[0]-l*r,e[1]-c*r],o=[e[0]-h*a,e[1]-d*a];s.push(i),Math.hypot(o[0]-i[0],o[1]-i[1])>1e-9&&s.push(o);continue}const x=zr(k,b,$,w),S=Math.max(r,a,1e-9);if(x){if(Math.hypot(x[0]-t[n][0],x[1]-t[n][1])<=4*S){s.push(x);continue}}r>0&&s.push([t[n][0]-l*r,t[n][1]-c*r]),a>0&&s.push([t[n][0]-h*a,t[n][1]-d*a])}return s.length>=3?s:null}function Yr(t,e,i,s,n,o,r=1){const a=[];for(const l of t||[]){const c=Je(l);if(c&&c.length>=3){const h=Lr(t,l.id,e,i,s,n,o,r),d=h&&h.offsets.some(t=>t>0)?Kr(h.poly,h.offsets):null,p=d||c;a.push({poly:p.map(t=>t.join(",")).join(" ")})}else l&&null!=l.x&&null!=l.y&&null!=l.w&&null!=l.h&&a.push({rect:{x:l.x,y:l.y,w:l.w,h:l.h,rx:.03*Math.min(l.w,l.h)}})}return a}function Jr(t,e,i,s,n,o,r=1){const a=[];for(const l of t||[]){if(!l?.id)continue;const c=Lr(t,l.id,e,i,s,n,o,r);if(!c)continue;const h=Math.abs(mi(c.poly));for(let t=0;t[Math.max(e,t.x0),Math.min(i,t.x1)]).filter(t=>t[1]-t[0]>s).sort((t,e)=>t[0]-e[0]||t[1]-e[1]);if(!n.length)return{coverage:0,full:!1};let o=n[0][0],r=n[0][1],a=0,l=o<=e+s;for(let t=1;t=i-s,{coverage:a,full:l}}function Xr(t,e){return Number(e.full)-Number(t.full)||t.faceDistance-e.faceDistance||t.area-e.area||t.roomId.localeCompare(e.roomId)}function Qr(t,e,i=!1){const s=Number(e?.x),n=Number(e?.y),o=Number(e?.angle),r=Number(e?.length);if(!([s,n,o,r].every(Number.isFinite)&&r>0))return{negative:null,positive:null};const a=o*Math.PI/180,l=Math.cos(a),c=Math.sin(a),h=-c,d=l,p=r/2,u=Math.max(1e-9,t.adjacencyEps),_=new Map;let m=0;for(const e of t.edges){if(i&&!(e.half>0))continue;if(!xr(e.a,e.b,o))continue;const[t,r]=ur(e.a,e.b);if(Math.abs((s-e.a[0])*r-(n-e.a[1])*t)>u)continue;const a=(e.a[0]-s)*l+(e.a[1]-n)*c,g=(e.b[0]-s)*l+(e.b[1]-n)*c,f=Math.max(-p,Math.min(a,g)),v=Math.min(p,Math.max(a,g));if(v-f<=u)continue;const y=e.inward[0]*h+e.inward[1]*d>=0?1:-1,b=((e.a[0]+e.b[0])/2-s)*h+((e.a[1]+e.b[1])/2-n)*d,w=Math.abs(b+y*e.half),k=`${y}|${e.roomId}`,$={x0:f,x1:v,half:e.half,cm:e.cm,key:e.key,axis:[t,r]},x=_.get(k);x?(x.pieces.push($),x.faceDistance=Math.min(x.faceDistance,w)):_.set(k,{roomId:e.roomId,side:y,order:m++,pieces:[$],faceDistance:w,area:e.area,coverage:0,full:!1})}for(const t of _.values()){const e=Zr(t.pieces,-p,p,u);t.coverage=e.coverage,t.full=e.full}const g=t=>{const e=[..._.values()].filter(e=>e.side===t&&e.coverage>u);return e.sort(Xr),e[0]||null};return{negative:g(-1),positive:g(1)}}function ta(t,e){const i=Qr(t,e),s=[i.negative,i.positive].filter(t=>!!t).sort((t,e)=>t.order-e.order);if(!s.length)return{ox:0,oy:0,cm:0,side:-1};const n=s[0],o=e.flip_v?-n.side:n.side,r=(-1===o?i.negative:i.positive)||n,a=[...r.pieces].sort((t,e)=>(t.x0<=0&&t.x1>=0?0:Math.min(Math.abs(t.x0),Math.abs(t.x1)))-(e.x0<=0&&e.x1>=0?0:Math.min(Math.abs(e.x0),Math.abs(e.x1)))||e.x1-e.x0-(t.x1-t.x0)||t.key.localeCompare(e.key))[0];if(!(a.half>0&&a.cm>0))return{ox:0,oy:0,cm:0,side:o};const l=e.angle*Math.PI/180,c=-Math.sin(l),h=Math.cos(l);return{ox:c*o*a.half,oy:h*o*a.half,cm:a.cm,side:o}}function ea(t,e){const i=1e-9,s=[];for(const t of[...e].filter(t=>t.x1>t.x0&&t.half>0).sort((t,e)=>t.x0-e.x0||t.x1-e.x1||t.half-e.half)){const e=s[s.length-1];e&&t.x0<=e.x1+i&&Math.abs(t.half-e.half)<=i?e.x1=Math.max(e.x1,t.x1):s.push({...t})}const n=[];for(let e=0;e=n-l?i.push([s,n]):(t>s+l&&i.push([s,Math.min(n,t)]),et[0]-e[0]||t[1]-e[1]),b=[];for(const t of y){const e=b[b.length-1];e&&t[0]<=e[1]+l?e[1]=Math.max(e[1],t[1]):b.push([t[0],t[1]])}s.set(g,b)}return a}function sa(t,e){const i=new Map;return e.map(e=>function(t,e,i){const s=Qr(t,e,!0),n=s.negative,o=s.positive;if(!n&&!o)return null;let r;if(n&&o)r=[{candidate:n,side:-1},{candidate:o,side:1}];else{const t=n||o;r=[{candidate:t,side:-1},{candidate:t,side:1}]}const a=r.map(({candidate:t,side:s})=>({candidate:t,side:s,pieces:ia(e,s,t.pieces,i)})),l=a.map(({candidate:t,side:e,pieces:i})=>({side:e,roomId:t.roomId,d:ea(e,i)})),c=a.flatMap(({pieces:t})=>t);if(!c.length)return null;const h=Math.max(...c.map(t=>t.half));return{faces:l,minY:-h,maxY:h,wallKey:[...new Set(c.map(t=>t.key))].sort().join("|")}}(t,e,i))}const na=150;function oa(t){return Number.isFinite(t)?Math.max(1,Math.min(na,t)):1}function ra(t){return((Number.isFinite(Number(t))?Number(t):0)%90+90)%90}const aa=t=>{const e=t.map(t=>[t[0],t[1]]);return!e.length||e[0][0]===e[e.length-1][0]&&e[0][1]===e[e.length-1][1]||e.push([...e[0]]),[e]};function la(t){const e=[];for(const i of t||[])for(const t of i||[]){const i=(t||[]).filter(t=>Array.isArray(t)&&t.length>=2);i.length<4||e.push(`M ${i.slice(0,-1).map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`)}return e.join(" ")}function ca(t,e,i,s,n){const o=e[0]-t[0],r=e[1]-t[1],a=Math.hypot(o,r);if(!(a>1e-9))return null;const l=dr(i,s,n)/2,c=-r/a*l,h=o/a*l;return[[t[0]+c,t[1]+h],[e[0]+c,e[1]+h],[e[0]-c,e[1]-h],[t[0]-c,t[1]-h]]}function ha(t,e,i){const s=Number(e)>0?Number(e):5,n=oa(t.cm)/s*i,o=t.center[0],r=t.center[1];if("circle"===t.shape){const t=n/2;return Array.from({length:96},(e,i)=>{const s=i/96*Math.PI*2;return[o+Math.cos(s)*t,r+Math.sin(s)*t]})}const a=n/2,l=ra(t.angle)*Math.PI/180,c=Math.cos(l),h=Math.sin(l);return[[-a,-a],[a,-a],[a,a],[-a,a]].map(([t,e])=>[o+t*c-e*h,r+t*h+e*c])}function da(t,e,i){const s=[];for(let n=0;n+1t.length>=3).map(t=>aa(t));return e.length?Be(e[0],...e.slice(1)):null}catch{return null}}const ua=t=>`M ${t.map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`;function _a(t,e){const i=pa(t.filter(t=>t.length>=3)),s=pa(e.filter(t=>t.length>=3));if(!i)return[];if(!s)return[];try{return function(t){const e=[];for(const i of t||[]){if(ga([i])<=1e-6)continue;const t=[];for(const e of i||[]){const i=(e||[]).filter(t=>Array.isArray(t)&&t.length>=2);i.length<4||t.push(ua(i.slice(0,-1)))}t.length&&e.push(t.join(" "))}return e}(We(i,s))}catch{return[]}}function ma(t,e){if(!e.length)return[aa(t)];try{const i=pa(e);if(i)return je(aa(t),i)}catch{}let i=[aa(t)];for(const t of e)if(!(t.length<3))try{i=je(i,aa(t))}catch{}return i}function ga(t){let e=0;for(const i of t||[])if(i?.length){e+=mi(i[0]||[]);for(let t=1;t=4&&e.push(t.slice(0,-1).map(t=>[t[0],t[1]]))}return e}function va(t,e){let i=!1;for(let s=0,n=e.length-1;st[1]!=l>t[1]&&t[0]<(a-o)*(t[1]-r)/(l-r||1e-12)+o&&(i=!i)}return i}function ya(t,e,i){if(Math.hypot(t.center[0]-e.center[0],t.center[1]-e.center[1])>i)return!1;if(Math.abs(oa(t.cm)-oa(e.cm))>1e-6)return!1;if(t.shape!==e.shape)return!0;if("circle"===t.shape||"circle"===e.shape)return!0;const s=Math.abs(ra(t.angle)-ra(e.angle));return Math.min(s,90-s)<=1e-6}const ba=1e3;function wa(t,e){const i=Number(t),s=Number.isFinite(i)&&i>0?i:1,n=s>=1?e:e*s,o=s>=1?e/s:e;return{x:(e-n)/2,y:(e-o)/2,w:n,h:o}}const ka=.01,$a=100;function xa(t,e=1e3){const i=wa(t?.plan_aspect,e),s=Number(t?.plan_scale),n=Number.isFinite(s)&&s>0?Math.min($a,Math.max(ka,s)):1,o=Number(t?.plan_scale_x),r=Number(t?.plan_scale_y),a=Number.isFinite(o)&&o>0?Math.min($a,Math.max(ka,o)):n,l=Number.isFinite(r)&&r>0?Math.min($a,Math.max(ka,r)):n,c=Number(t?.plan_x),h=Number(t?.plan_y),d=Zo(t?.plan_angle);return{x:i.x+(Number.isFinite(c)?Ea(c):0)*e,y:i.y+(Number.isFinite(h)?Ea(h):0)*e,w:i.w*a,h:i.h*l,...d?{angle:d}:{}}}function Sa(t){if(null==t.x||null==t.y)return{x:t.x,y:t.y,w:t.w,h:t.h};const e=Number(t.w)||0,i=Number(t.h)||0;return{x:e<0?t.x+e:t.x,y:i<0?t.y+i:t.y,w:Math.abs(e),h:Math.abs(i)}}function Ma(t){return t&&Array.isArray(t.spaces)?t.spaces.map(t=>{const e=ba,i=function(t){return Array.isArray(t)&&4===t.length&&t.every(t=>Number.isFinite(t))&&t[2]>1e-6&&t[3]>1e-6?t:[0,0,1,1]}(t.view_box);return{id:t.id,title:t.title,vb:[i[0]*ba,i[1]*e,i[2]*ba,i[3]*e],bg:t.plan_url?{href:ys(t.plan_url),...xa(t,ba)}:null,rooms:(t.rooms||[]).map(t=>{const i={...t,...Sa(t)};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*ba:void 0,y:null!=i.y?i.y*e:void 0,w:null!=i.w?i.w*ba:void 0,h:null!=i.h?i.h*e:void 0,poly:i.poly?i.poly.map(t=>[t[0]*ba,t[1]*e]):void 0}}),room_drafts:(t.room_drafts||[]).map(t=>({id:t.id,points:(t.points||[]).map(t=>[t[0]*ba,t[1]*e]),segments:(t.segments||[]).map(t=>({cm:Number(t.cm)}))})),partitions:(t.partitions||[]).map(t=>({id:t.id,a:[t.a[0]*ba,t.a[1]*e],b:[t.b[0]*ba,t.b[1]*e],cm:Number(t.cm)})),wall_columns:(t.wall_columns||[]).map(t=>({id:t.id,shape:"circle"===t.shape?"circle":"square",center:[t.center[0]*ba,t.center[1]*e],cm:Number(t.cm),..."circle"===t.shape?{}:{angle:ra(t.angle)}}))}}):[]}const Ca=5e3,Da=Ca*ba,Ta=240,Ra=ba/Ta,Pa=1/Ta;function Aa(t){if(!Number.isFinite(t))return t;const e=Math.round(t*Ta/ba)*ba/Ta;return Math.abs(e-t)<=1e-9*Ra?t:e}function za(t){return{x:Aa(t.x),y:Aa(t.y)}}function Ia(t){return Number.isFinite(t)?Math.min(Da,Math.max(-Da,t)):0}function Ea(t){return Number.isFinite(t)?Math.min(Ca,Math.max(-Ca,t)):0}const Fa=1/3,Na=200;function Oa(t){let e=1/0,i=1/0,s=-1/0,n=-1/0;for(const o of t){const t=Number(o[0]),r=Number(o[1]);Number.isFinite(t)&&Number.isFinite(r)&&(ts&&(s=t),r>n&&(n=r))}return e>s?null:{minX:e,minY:i,maxX:s,maxY:n}}function La(t){return t.poly&&t.poly.length?Oa(t.poly):null==t.x||null==t.y?null:Oa([[t.x,t.y],[t.x+(t.w||0),t.y+(t.h||0)]])}function Ha(t,e){const i=[];for(const e of t.rooms||[]){const t=La(e);t&&i.push(t)}if(t.bg){const e=Oa(sr(t.bg));e&&i.push(e)}for(const t of e||[])if(Array.isArray(t)){const e=Oa([t]);e&&i.push(e)}else i.push(t);return i}const qa=t=>{if(!t.length)return 0;const e=[...t].sort((t,e)=>t-e),i=e.length>>1;return e.length%2?e[i]:(e[i-1]+e[i])/2},Ua=t=>{let e=1/0,i=1/0,s=-1/0,n=-1/0;for(const o of t)o.minXs&&(s=o.maxX),o.maxY>n&&(n=o.maxY);return e>s||i>n?null:{x:e,y:i,w:s-e,h:n-i}};function Ba(t,e){let{x:i,y:s,w:n,h:o}=t;n<30&&(i=i+n/2-100,n=Na),o<30&&(s=s+o/2-100,o=Na);const r=Math.max(n,o)*e;return{x:i-r,y:s-r,w:n+2*r,h:o+2*r}}function Wa(t,e={}){const i=e.pad??.05,s=e.k??10,n=e.minSpread??50,o=t.filter(t=>Number.isFinite(t.minX)&&Number.isFinite(t.minY)&&Number.isFinite(t.maxX)&&Number.isFinite(t.maxY)&&Math.abs(t.minX)<=Da&&Math.abs(t.maxX)<=Da&&Math.abs(t.minY)<=Da&&Math.abs(t.maxY)<=Da);if(!o.length)return{core:null,all:null,outliers:0};const r=Ua(o);if(o.length<4){const t=Ba(r,i);return{core:t,all:t,outliers:0}}const a=o.map(t=>(t.minX+t.maxX)/2),l=o.map(t=>(t.minY+t.maxY)/2),c=qa(a),h=qa(l),d=o.map((t,e)=>Math.max(Math.abs(a[e]-c),Math.abs(l[e]-h))),p=Math.max(((t,e)=>t.length?t[Math.min(t.length-1,Math.max(0,Math.round(e*(t.length-1))))]:0)([...d].sort((t,e)=>t-e),.75),n),u=d.map(t=>t>s*p),_=u.filter(Boolean).length,m=_&&_<=o.length*Fa?o.filter((t,e)=>!u[e]):o;return{core:Ba(Ua(m)||r,i),all:Ba(r,i),outliers:m===o?0:_}}function ja(t,e,i=.05){const s=Wa(Ha(t,e),{pad:i});if(s.core)return s.core;const n=t.vb&&4===t.vb.length&&t.vb[2]>0&&t.vb[3]>0?t.vb:[0,0,ba,ba];return{x:n[0],y:n[1],w:n[2],h:n[3]}}function Ga(t){const e=ja(t);return{x:e.x+e.w/2,y:e.y+e.h/2}}function Va(t){const e=[];for(const i of t.rooms||[]){const t=La(i);t&&e.push(t)}const i=Wa(e,{pad:0}).core;return i?Math.max(ba,Math.min(Da,Math.max(i.w,i.h))):ba}function Ka(t,e,i,s=1){const n=Number(i),o=Number.isFinite(s)&&s>0?s:1;return!Number.isFinite(n)||n<=0?t*o:t*Va(e)*o/n}const Ya=[1,2,5,10,20,50,100,200,500,1e3];function Ja(t){if(t.poly&&t.poly.length){const e=t.poly.map(t=>t[0]),i=t.poly.map(t=>t[1]),s=Math.min(...e),n=Math.min(...i);return{x:s,y:n,w:Math.max(...e)-s,h:Math.max(...i)-n}}return{x:t.x??0,y:t.y??0,w:t.w??0,h:t.h??0}}function Za(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}const Xa=["furniture","appliance","sanitary","other"],Qa=[{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]]}],tl=new Map(Qa.map(t=>[t.id,t]));function el(t){return t&&tl.get(t)||null}function il(t){return Qa.filter(e=>e.group===t)}function sl(t,e,i=Ra,s=1e3){const n=Number(e)>0?Number(e):5;return(Number(t)||0)/n*i/s}const nl=5e-4,ol=Ca;function rl(t){return Number.isFinite(t)?Math.max(nl,Math.min(ol,t)):nl}const al=t=>{const e=Math.round(1e3*t)/1e3;return Object.is(e,-0)?"0":String(e)};function ll(t,e,i){const s=el(t);if(!(s&&e>0&&i>0))return"";const n=t=>al(t*e),o=t=>al(t*i),r=[];for(const t of s.g)if("r"===t[0]){const[,e,i,s,a]=t;r.push(`M${n(e)} ${o(i)}H${n(e+s)}V${o(i+a)}H${n(e)}Z`)}else if("l"===t[0]){const[,e,i,s,a]=t;r.push(`M${n(e)} ${o(i)}L${n(s)} ${o(a)}`)}else if("e"===t[0]){const[,s,a,l,c]=t;r.push(`M${n(s-l)} ${o(a)}A${al(l*e)} ${al(c*i)} 0 0 1 ${n(s+l)} ${o(a)}A${al(l*e)} ${al(c*i)} 0 0 1 ${n(s-l)} ${o(a)}Z`)}else{const e=t.slice(1);if(e.length<4)continue;let i=`M${n(e[0])} ${o(e[1])}`;for(let t=2;t+1{let e=(t%360+360)%360;return e>180&&(e-=360),e};function hl(t,e,i,s,n,o=0){let r=null,a=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=((t-s)*d+(e-l)*p)/u;m=Math.max(0,Math.min(1,m));let g=s+m*d,f=l+m*p;const v=Math.hypot(t-g,e-f);if(!(v0){const t=Math.round(m*_/o)*o;g=s+t/_*d,f=l+t/_*p}r={cx:g+y*(i/2),cy:f+b*(i/2),angle:cl(180*Math.atan2(-y,b)/Math.PI),dist:v}}return r}function dl(t,e,i){const s=i;return{a:[t[0]/s,t[1]/s],b:[e[0]/s,e[1]/s]}}function pl(t,e){const i=e;return[t.a[0]*i,t.a[1]*i,t.b[0]*i,t.b[1]*i]}function ul(t){return Array.isArray(t)&&t.length>=2&&Number.isFinite(Number(t[0]))&&Number.isFinite(Number(t[1]))}function _l(t){if(!Array.isArray(t))return[];const e=[];for(const i of t){if(!i||"object"!=typeof i)continue;const t=i;if(!ul(t.a)||!ul(t.b))continue;const s=[Number(t.a[0]),Number(t.a[1])],n=[Number(t.b[0]),Number(t.b[1])];Math.hypot(n[0]-s[0],n[1]-s[1])<.001||e.push({a:s,b:n})}return e}function ml(t,e){const i=e[0],s=e[1],n=e[2]-i,o=e[3]-s,r=n*n+o*o;if(r<1e-18){return{q:[i,s],t:0,d:Math.hypot(t[0]-i,t[1]-s)}}let a=((t[0]-i)*n+(t[1]-s)*o)/r;a=Math.max(0,Math.min(1,a));const l=[i+a*n,s+a*o];return{q:l,t:a,d:Math.hypot(t[0]-l[0],t[1]-l[1])}}function gl(t,e){return ml(t,e).q}function fl(t,e,i,s,n){const o=ml(t,e).q;let r=o,a=1/0;for(const t of i){const e=Math.hypot(o[0]-t[0],o[1]-t[1]);e<=n&&e0?s:1,m=Math.round(u/_)*_,g=Math.max(0,Math.min(p,m))/p;return[l+h*g,c+d*g]}function vl(t,e){const i=[],s=(t||[]).filter(t=>t?.id);for(let t=0;tt?.id),r=_l(e);if(r.length)return Cl(r,t,i,s).map(t=>pl(t,i));if(!n)return[];const a=[],l=(t,e)=>(t.open_to||[]).includes(e.id)||(e.open_to||[]).includes(t.id);for(let t=0;tdl([t[0],t[1]],[t[2],t[3]],e))}function wl(t,e,i,s){for(const e of t||[])e.open_to&&delete e.open_to;if(!i.length)return;const n=(e||[]).filter(t=>t?.id);new Map(n.map(t=>[t.id,t]));const o=new Map((t||[]).filter(t=>t?.id).map(t=>[t.id,t])),r=(t,e)=>{const i=o.get(t),s=o.get(e);i&&s&&((i.open_to||[]).includes(e)||(i.open_to=[...i.open_to||[],e]),(s.open_to||[]).includes(t)||(s.open_to=[...s.open_to||[],t]))};for(let t=0;tSs(i,t)<4*s)){r(n[t].id,n[e].id);break}}}for(const e of t||[])e.open_to&&!e.open_to.length&&delete e.open_to}function kl(t,e,i,s=0){const n=e[2]-e[0],o=e[3]-e[1],r=Math.hypot(n,o);if(!(r>1e-9))return null;const a=n/r,l=o/r,c=(t[0]-e[0])*a+(t[1]-e[1])*l;if(c<-s||c>r+s)return null;return Math.abs((t[0]-e[0])*l-(t[1]-e[1])*a)>i?null:Ss(t,e)}function $l(t,e,i){const s=t[2]-t[0],n=t[3]-t[1],o=e[2]-e[0],r=e[3]-e[1],a=Math.hypot(s,n),l=Math.hypot(o,r);return a>1e-9&&l>1e-9&&(!(Math.abs(s/a*(r/l)-n/a*(o/l))>1e-6)&&(Ss([e[0],e[1]],t)<=4*i||Ss([t[0],t[1]],e)<=4*i))}function xl(t,e,i){if(!t.length)return null;t.sort((t,e)=>t.distance-e.distance);const s=t[0];for(let n=1;ne)break;if(o.semantic!==s.semantic||!$l(s.seg,o.seg,i))return"ambiguous"}return s}function Sl(t,e,i,s,n=1,o=15){const r=function(t,e,i,s,n=1,o=15){const[r,a]=ur([e[0],e[1]],[e[2],e[3]]);let l=0,c=1/0;const h=[(e[0]+e[2])/2,(e[1]+e[3])/2];for(const e of i){const[i,o]=ur([e[0],e[1]],[e[2],e[3]]);if(Math.abs(r*o-a*i)>.05)continue;const d=kr(t,[e[0],e[1]],[e[2],e[3]],s,n);if(!(d>0))continue;const p=[(e[0]+e[2])/2,(e[1]+e[3])/2],u=Math.hypot(p[0]-h[0],p[1]-h[1]);u0?l:o}(t,e,i,s,n,o);return Mr(t,[e[0],e[1]],[e[2],e[3]],r,s,n)}function Ml(t,e,i){const s=[[t[0],t[1]],[t[2],t[3]]],n=[(t[0]+t[2])/2,(t[1]+t[3])/2];for(const o of e)if(!(Ss(n,o)>Math.hypot(t[2]-t[0],t[3]-t[1])&&Ss([o[0],o[1]],t)>i))for(const e of[[o[0],o[1]],[o[2],o[3]]])Ss(e,t)<=2*i&&s.push(e);return s}function Cl(t,e,i,s){const n=_l(t);if(!n.length)return[];const o=vl(e,s);if(!o.length)return[];const r=[],a=Math.max(4*s,1e-6);for(const t of n){const e=pl(t,i),n=e[0],l=e[1],c=e[2]-n,h=e[3]-l,d=Math.hypot(c,h);if(d4*s||o>4*s)continue;const r=(t[0]-n)*p+(t[1]-l)*u,c=(t[2]-n)*p+(t[3]-l)*u,h=Math.max(0,Math.min(r,c)),m=Math.min(d,Math.max(r,c));if(m-ht.lo-e.lo||t.hi-e.hi);const i=[];for(const t of e){const e=i[i.length-1];e&&t.lo<=e.hi+a?e.hi=Math.max(e.hi,t.hi):i.push({...t})}for(const e of i){const i=[n+p*e.lo,l+u*e.lo],s=[n+p*e.hi,l+u*e.hi];Math.hypot(s[0]-i[0],s[1]-i[1])i.pair===t&&Math.abs(i.ux*r-i.uy*o)<=1e-6&&Math.abs((e[0]-i.origin[0])*i.uy-(e[1]-i.origin[1])*i.ux)<=c&&Math.abs((e[2]-i.origin[0])*i.uy-(e[3]-i.origin[1])*i.ux)<=c);h||(h={pair:t,origin:[e[0],e[1]],ux:o,uy:r,ranges:[]},l.push(h));const d=(e[0]-h.origin[0])*h.ux+(e[1]-h.origin[1])*h.uy,p=(e[2]-h.origin[0])*h.ux+(e[3]-h.origin[1])*h.uy;h.ranges.push({lo:Math.min(d,p),hi:Math.max(d,p)})}const h=[];for(const t of l){t.ranges.sort((t,e)=>t.lo-e.lo||t.hi-e.hi);const e=[];for(const i of t.ranges){const t=e[e.length-1];t&&i.lo<=t.hi+a?t.hi=Math.max(t.hi,i.hi):e.push({...i})}for(const s of e){const e=[t.origin[0]+t.ux*s.lo,t.origin[1]+t.uy*s.lo],n=[t.origin[0]+t.ux*s.hi,t.origin[1]+t.uy*s.hi];Math.hypot(n[0]-e[0],n[1]-e[1])>=a&&h.push(dl(e,n,i))}}return h}function Dl(t,e,i,s){const n=_l(t);if(!n.length)return[];if(e.length!==i.length)return n;const o=[];for(const t of n){const n=pl(t,s),r=[n[0],n[1]],a=[n[2],n[3]];let l=r,c=a;for(let t=0;t{const e=ml(t,[s[0],s[1],n[0],n[1]]);if(e.d>.001)return t;const i=h[0]-o[0],r=h[1]-o[1];return[o[0]+i*e.t,o[1]+r*e.t]};if(ml([(r[0]+a[0])/2,(r[1]+a[1])/2],[s[0],s[1],n[0],n[1]]).d<.01){l=d(r),c=d(a);break}}o.push(dl(l,c,s))}return o}const Tl=15e3,Rl=new WeakMap;class Pl{constructor(t,e=()=>Date.now()){this.onUpdate=t,this.now=e,this.fallbackAuthority={},this.referenceOwner={},this.referenced=new Set,this.queued=new Set,this.disposed=!1,this.sharedUpdate=()=>{this.disposed||this.onUpdate()}}bind(t){const e=((t,e)=>{const i=t?.connection||t;return!i||"object"!=typeof i&&"function"!=typeof i?e:i})(t,this.fallbackAuthority);return this.shared&&this.authority===e||(this.shared&&(this.shared.listeners.delete(this.sharedUpdate),this.shared.references.delete(this.referenceOwner)),this.authority=e,this.shared=(t=>{let e=Rl.get(t);return e||(e={signed:{},queued:new Set,inFlight:new Map,retry:new Map,listeners:new Set,references:new Map},Rl.set(t,e)),e})(e),this.disposed||(this.shared.listeners.add(this.sharedUpdate),this.shared.references.set(this.referenceOwner,new Set(this.referenced)))),this.shared}start(t,e){this.disposed=!1,this.referenced=new Set(e());const i=this.bind(t());i.listeners.add(this.sharedUpdate),i.references.set(this.referenceOwner,new Set(this.referenced)),this.stopTimer(),this.resignTimer=setInterval(()=>this.resign(t(),e()),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 t of this.queued)this.shared.queued.delete(t)}this.queued.clear()}stopTimer(){void 0!==this.resignTimer&&clearInterval(this.resignTimer),this.resignTimer=void 0}display(t,e){const i=ys(e);if(!i.startsWith("/api/houseplan/content/"))return i;const s=this.bind(t);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 e=[...this.queued];this.queued.clear();for(const t of e)i.queued.delete(t);this.sign(t,e)},30))}sign(t,e){if(!e.length||!t?.callWS)return;const i=this.bind(t);for(const s of function(t,e){const i=Math.max(1,Math.floor(e)),s=[];for(let e=0;e{const s=i.inFlight.get(t);return!(void 0!==s&&e-s{const e=this.now();let s=0,o=!1;for(const r of n){const n=t?.urls?.[r];if("string"==typeof n&&n){const t=i.signed[r];t?.loaded&&t.url!==n?(t.pending={url:n,at:e},this.preloadReplacement(i,r,n,e)):(i.signed[r]={url:n,at:e,loaded:t?.url===n&&!!t.loaded},o=!0),i.retry.delete(r),s++}else this.backOff(i,r)}if(s&&(this.trimShared(i),o))for(const t of[...i.listeners])t()}).catch(()=>{for(const t of n)this.backOff(i,t)}).finally(()=>{for(const t of n)i.inFlight.get(t)===e&&i.inFlight.delete(t)})}}preloadReplacement(t,e,i,s){const n=()=>{const n=t.signed[e];if(n?.pending?.url===i){t.signed[e]={url:i,at:s,loaded:!0},t.retry.delete(e),this.trimShared(t);for(const e of[...t.listeners])e()}},o=()=>{const s=t.signed[e];s?.pending?.url===i&&(delete s.pending,this.backOff(t,e))};if("undefined"==typeof Image)return void n();const r=new Image;r.onload=()=>{("function"==typeof r.decode?r.decode():Promise.resolve()).then(n).catch(o)},r.onerror=o,r.src=i}preloadCurrentImage(t,e,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,r=o=>{const r=t.signed[e];if(r===i&&delete r.preload,o&&r===i&&r.url===s){r.loaded=!0;for(const e of[...t.listeners])e();n(!0)}else o||r!==i||this.backOff(t,e),n(!1)};o.onload=()=>{("function"==typeof o.decode?o.decode():Promise.resolve()).then(()=>r(!0)).catch(()=>r(!1))},o.onerror=()=>r(!1),o.src=s}),i.preload}prepareImage(t,e){const i=ys(e);if(!i.startsWith("/api/houseplan/content/"))return Promise.resolve(!0);const s=this.bind(t),n=()=>{const e=s.signed[i];return e&&this.now()-e.at{let e=!1,i=null;const o=i=>{e||(e=!0,clearTimeout(a),s.listeners.delete(r),t(i))},r=()=>{e||i||(i=n(),i&&i.then(o).catch(()=>o(!1)))},a=setTimeout(()=>o(!1),Tl);s.listeners.add(r),r()})}backOff(t,e){const i=t.retry.get(e)?.delay||0,s=Math.min(6e4,i?2*i:2e3);t.retry.set(e,{notBefore:this.now()+s,delay:s})}trimShared(t){const e=Object.entries(t.signed);e.length<=96||(e.sort((t,e)=>e[1].at-t[1].at),t.signed=Object.fromEntries(e.slice(0,96)))}resign(t,e){const i=this.bind(t);this.referenced=new Set(e),i.references.set(this.referenceOwner,new Set(this.referenced));const s=this.now(),n=new Set;for(const t of i.references.values())for(const e of t)n.add(e);const o={};for(const[t,e]of Object.entries(i.signed))n.has(t)&&s-e.at!!o[t]&&!o[t].pending))}markLoaded(t,e){const i=ys(e);if(!i.startsWith("/api/houseplan/content/"))return;const s=this.bind(t).signed[i];if(s&&!s.loaded){s.loaded=!0;for(const e of[...this.bind(t).listeners])e()}}isReady(t,e){const i=ys(e);if(!i.startsWith("/api/houseplan/content/"))return!0;const s=this.bind(t).signed[i];return!!s&&this.now()-s.at{if(null==t||""===t)return null;const e=Number(t);return Number.isFinite(e)?e:null},El=t=>{const e=t,i=Il(Array.isArray(e)?e[0]:e?.x),s=Il(Array.isArray(e)?e[1]:e?.y);return null==i||null==s?null:[i,s]};function Fl(t){if(!Array.isArray(t))return null;const e=[];for(const i of t){const t=El(i);if(!t)return null;e.push(t)}if(e.length>1){const t=e[0],i=e[e.length-1];t[0]===i[0]&&t[1]===i[1]&&e.pop()}if(e.length<3)return null;let i=0,s=0,n=0;for(let t=0;tt+e[0],0)/e.length,e.reduce((t,e)=>t+e[1],0)/e.length]:[s/(3*i),n/(3*i)]}function Nl(t){if(!Array.isArray(t))return null;const e=[];for(const i of t){const t=El(i);if(!t)return null;e.push(t)}if(e.length>1){const t=e[0],i=e[e.length-1];t[0]===i[0]&&t[1]===i[1]&&e.pop()}const i=Fl(e);if(!i)return null;const s=e.map(t=>t[0]),n=e.map(t=>t[1]);return{centroid:i,bbox:[Math.min(...s),Math.min(...n),Math.max(...s),Math.max(...n)]}}function Ol(t){let e=t;if(e&&"object"==typeof e&&!Array.isArray(e)){const t=e;e=t.path??t.points??e}if(!Array.isArray(e)||!e.length)return[];let i=(e.some(t=>(t=>Array.isArray(t)?t.length>=2&&!Array.isArray(t[0])&&("object"!=typeof t[0]||null==t[0]):!!t&&"object"==typeof t&&("x"in t||"y"in t))(t))?[e]:e.filter(Array.isArray)).map(t=>t.map(El).filter(t=>null!=t)).filter(t=>t.length>=2);i.length>64&&(i=i.slice(-64));if(i.reduce((t,e)=>t+e.length,0)<=4e3)return i.map(t=>t.slice());const s=4e3-2*i.length,n=i.map(t=>t.length-2),o=n.reduce((t,e)=>t+e,0),r=n.map(t=>s*t/o),a=r.map(Math.floor);let l=s-a.reduce((t,e)=>t+e,0);const c=r.map((t,e)=>({index:e,fraction:t-Math.floor(t)})).sort((t,e)=>e.fraction-t.fraction||t.index-e.index);for(let t=0;tfunction(t,e){if(e>=t.length)return t.slice();if(e<=2)return[t[0],t[t.length-1]];const i=[];for(let s=0;st.slice()),i=e[e.length-1];return i?(i.length>2?i.pop():e.pop(),e):[]}function ql(t){if(!t)return null;const e=t.vacuum_position||t.robot_position||null,i=e&&null!=Il(e.x)&&null!=Il(e.y)?{x:Il(e.x),y:Il(e.y),a:Il(e.a??e.angle??e.theta)}:null,s=Ol(t.path?.path??t.path?.points??t.path),n=[],o=t.rooms,r=Array.isArray(o)?o.map((t,e)=>[String(t?.id??e),t]):o&&"object"==typeof o?Object.entries(o):[];for(const[t,e]of r){if(!e||"object"!=typeof e)continue;const i=String(e.name??e.label??"").trim(),s=[[Il(e.cx),Il(e.cy)],[Il(e.center?.x),Il(e.center?.y)],[Il(e.x),Il(e.y)]].find(([t,e])=>null!=t&&null!=e);let o=s?.[0]??null,r=s?.[1]??null;const a=Nl(e.outline),l=Il(e.x0),c=Il(e.y0),h=Il(e.x1),d=Il(e.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)||a?.bbox||null;if(null!=o&&null!=r||!a||([o,r]=a.centroid),null!=o&&null!=r||!p||(o=(p[0]+p[2])/2,r=(p[1]+p[3])/2),i&&null!=o&&null!=r){const e={id:t,name:i,cx:o,cy:r};p&&([e.x0,e.y0,e.x1,e.y1]=p),n.push(e)}}const a=function(t){return String(t.map_name??t.current_map??t.map_index??t.selected_map??"default")}(t);return i||n.length||s.length?{pos:i,path:s,rooms:n,mapId:a}:null}function Ul(t,e,i){if(!t||!e)return null;const s=e.attributes||{},n=t.split(".")[0]||"",o=null!=i?.platform?String(i.platform):null,r=!!El(s.vacuum_position??s.robot_position),a=!(!s.rooms||"object"!=typeof s.rooms),l=Ol(s.path?.path??s.path?.points??s.path).length>0,c=[s.map_name,s.current_map,s.map_index,s.selected_map].some(t=>null!=t),h="xiaomi_cloud_map_extractor"===o,d=a||l||c;let p=null;if(r?p="compatible":h?p="known_xcme_incomplete":d?p="partial":"camera"===n&&(p="camera"),!p)return null;const u=r?"camera"===n?300:200:h?100:d?50:0;return{entityId:t,name:String(s.friendly_name||t),platform:o,category:p,hasPosition:r,hasRooms:a,hasPath:l,hasMapId:c,score:u}}const Bl=t=>t.toLowerCase().replace(/[\s_\-.,]+/g,"");function Wl(t,e){const i=new Map(e.map(t=>[Bl(t.name),t])),s=[],n=[];for(const e of t){const t=i.get(Bl(e.name));t&&(s.push([[e.cx,e.cy],[t.cx,t.cy]]),n.push(e.name))}if(s.length<3)return null;const o=function(t){if(t.length<3)return null;let e=0,i=0,s=0,n=0,o=0,r=0,a=0,l=0,c=0,h=0,d=0,p=0;for(const[[u,_],[m,g]]of t){if(![u,_,m,g].every(Number.isFinite))return null;e+=u*u,i+=u*_,s+=u,n+=_*_,o+=_,r+=1,a+=u*m,l+=_*m,c+=m,h+=u*g,d+=_*g,p+=g}const u=[e,i,s,i,n,o,s,o,r],_=t=>{const[e,i,s,n,o,r,a,l,c]=u,h=e*(o*c-r*l)-i*(n*c-r*a)+s*(n*l-o*a);if(!Number.isFinite(h)||Math.abs(h)<1e-9)return null;const d=[(o*c-r*l)/h,(s*l-i*c)/h,(i*r-s*o)/h,(r*a-n*c)/h,(e*c-s*a)/h,(s*n-e*r)/h,(n*l-o*a)/h,(i*a-e*l)/h,(e*o-i*n)/h];return[d[0]*t[0]+d[1]*t[1]+d[2]*t[2],d[3]*t[0]+d[4]*t[1]+d[5]*t[2],d[6]*t[0]+d[7]*t[1]+d[8]*t[2]]},m=_([a,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:zl(o,s)}:null}function jl(t,e,i){const s=t[t.length-1];if(s&&s[0]===e[0]&&s[1]===e[1])return t;if(t.push(e),t.length<=600)return t;let n=function(t,e){if(t.length<3)return t.slice();const i=new Uint8Array(t.length);i[0]=i[t.length-1]=1;const s=[[0,t.length-1]];for(;s.length;){const[n,o]=s.pop(),[r,a]=t[n],[l,c]=t[o],h=l-r,d=c-a,p=Math.hypot(h,d)||1e-9;let u=0,_=-1;for(let e=n+1;eu&&(u=i,_=e)}_>0&&u>e&&(i[_]=1,s.push([n,_],[_,o]))}const n=[];for(let e=0;e600&&(n=n.filter((t,e)=>e%2==0||e===n.length-1)),n}function Gl(t){return"cleaning"===t||"returning"===t||"on"===t}const Vl={0:[1,0],90:[0,1],180:[-1,0],270:[0,-1]};function Kl(t){const[e,i]=Vl[t.rot]||[1,0],s=t.mir?-1:1;return[t.s*e*s,-t.s*i,t.ox,t.s*i*s,t.s*e,t.oy]}function Yl(t){const e=t[0]*t[4]-t[1]*t[3];if(!Number.isFinite(e)||Math.abs(e)<1e-12)return null;const i=e<0,s=Math.sqrt(Math.abs(e));let n=180*Math.atan2(-t[1],t[4])/Math.PI;return n=(90*Math.round(n/90)%360+360)%360,{ox:t[2],oy:t[5],s:s,rot:n,mir:i}}function Jl(t,e,i,s){const[n,o]=Al(Kl(e),i,s),r=Kl({...t,ox:0,oy:0}),[a,l]=Al(r,i,s);return{...t,ox:n-a,oy:o-l}}function Zl(t){const e=t?.trail_mode;return"never"===e||"cleaning"===e||"always"===e?e:!1===t?.trail?"never":"cleaning"}const Xl=1e-5;function Ql(t){const e=t.map(()=>[]);for(let i=0;i=1-1e-9||p<=1e-9||p>=1-1e-9||(e[i].push(d),e[r].push(p))}}const i=[];for(let s=0;st-e),1];for(let t=1;t{const i=e[2]-e[0],s=e[3]-e[1],n=i*i+s*s;if(!(n>0))return Math.hypot(t[0]-e[0],t[1]-e[1]);const o=Math.max(0,Math.min(1,((t[0]-e[0])*i+(t[1]-e[1])*s)/n));return Math.hypot(t[0]-(e[0]+o*i),t[1]-(e[1]+o*s))},ic=(t,e,i,s)=>{const n=s[2]-s[0],o=s[3]-s[1],r=e*o-i*n;if(Math.abs(r)<1e-12)return 1/0;const a=s[0]-t[0],l=s[1]-t[1],c=(a*o-l*n)/r;if(!(c>1e-9))return 1/0;const h=(a*i-l*e)/r;return h<-1e-9||h>1+1e-9?1/0:c};function sc(t,e,i,s=96){if(!(e>0&&Number.isFinite(t[0])&&Number.isFinite(t[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=ec(t,s);if(i<1e-7)return[];i>e||n.push(s)}const o=[],r=Math.max(12,Math.round(s));for(let t=0;tt-e);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 r=e;for(const e of n){const i=ic(t,s,o,e);i=3?l:[]}class nc extends ct{constructor(){super(...arguments),this._spaces=null,this._spacesLoading=!1}setConfig(t){this._config=t}async _loadSpaces(){if(!this._spaces&&!this._spacesLoading&&this.hass){this._spacesLoading=!0;try{const t=await this.hass.callWS({type:"houseplan/config/get"});this._spaces=(t?.config?.spaces||[]).map(t=>({value:t.id,label:t.title||t.id}))}catch{this._spaces=[]}finally{this._spacesLoading=!1}}}get _lang(){return co(this.hass,this._config?.language)}get _schema(){const t=this._spaces||[],e=this._lang;return[{name:"title",selector:{text:{}}},t.length?{name:"default_floor",selector:{select:{mode:"dropdown",options:t}}}:{name:"default_floor",selector:{text:{}}},{name:"language",selector:{select:{mode:"dropdown",options:[{value:"",label:ho(e,"editor.lang_auto")},{value:"en",label:ho(e,"editor.lang_en")},{value:"ru",label:ho(e,"editor.lang_ru")}]}}},{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 t=this._lang,e={title:ho(t,"editor.title"),default_floor:ho(t,"editor.default_floor"),language:ho(t,"editor.language"),icon_size:ho(t,"editor.icon_size"),show_temperature:ho(t,"editor.show_temperature"),live_states:ho(t,"editor.live_states"),show_signal:ho(t,"editor.show_signal"),kiosk:ho(t,"editor.kiosk"),cycle:ho(t,"editor.cycle")};return B`e[t.name]||t.name} @value-changed=${this._valueChanged} - >`}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}function oc(t){const e=Ca(t.cfg),i={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(i[t.area]=e.id);const s=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):Et,o=Ft(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:Nt);return Fo({hass:t.hass,registry:t.registry,areaToSpace:i,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:s,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>dn(t.lang,e),iconRules:o})}function nc(t){const e=Ca(t.cfg).find(e=>e.id===t.spaceId);if(!e)return null;const i=Yi(t.cfg.spaces.find(e=>e.id===t.spaceId)),s=Xi(t.cfg.settings),o=t.iconSize??2.5,n=o>8?2.5:o,r=t.registry?no(t.hass,t.registry):t.hass,a=(t.devices||oc(t)).filter(e=>e.space===t.spaceId),l=a.filter(t=>!t.hidden),c=function(t,e,i){const s={},o=i/100*Ka(e)*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const n=Za(i),r=.1*Math.min(n.w,n.h),a=n.w-2*r,l=n.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,d=l/Math.max(Math.ceil(e.length/c),1),p=e.map((t,e)=>({x:n.x+r+h*(e%c+.5),y:n.y+r+d*(Math.floor(e/c)+.5)}));ki(p,n,o,.5*r),e.forEach((t,e)=>s[t.id]=Ia(p[e]))}return s}(a,e,n),h=[];for(const e of l){const i=t.layout[e.id];if(i&&i.s===t.spaceId){const t=i.x*wa,e=i.y*wa;h.push({minX:t,minY:e,maxX:t,maxY:e})}}const d=t.cfg.spaces.find(e=>e.id===t.spaceId)||{},p=Array.isArray(d.walls)?d.walls:[],u=Number(d.cell_cm)>0?Number(d.cell_cm):5,_=function(t,e,i){const s=[];for(const o of t.partitions||[]){const t=ca(o.a,o.b,o.cm,e,i);t&&s.push(t)}for(const o of t.room_drafts||[])s.push(...da(o,e,i));for(const o of t.wall_columns||[])s.push(ha(o,e,i));return s}(e,u,Ra);for(const t of _){const e=t.map(t=>t[0]),i=t.map(t=>t[1]);e.length&&h.push({minX:Math.min(...e),minY:Math.min(...i),maxX:Math.max(...e),maxY:Math.max(...i)})}const m=Wa(e,h),g=[m.x,m.y,m.w,m.h],f=new Map(e.rooms.map(t=>{const e=bs(i.fill,t);return[t,ts(e,"lqi"===e&&t.area?Ho(r,a,t.area):null,"light"===e?Mo(xo(r,a,t)):"none","temp"===e&&t.area?qo(r,a,t.area):null,i.tempMin,i.tempMax,s,Ki(i.customFill,t))]})),v=e.rooms.filter(t=>t.area||i.showBorders||"none"!==bs(i.fill,t)).map(t=>{let s="room "+(e.bg?"overlay":"yard"),o="";const n=bs(i.fill,t);if(i.showBorders||"none"!==n){s+=" styled";const e=[`--room-stroke:${i.color}`,`--room-stroke-op:${i.showBorders?i.opacity:0}`],n=f.get(t)||null;n?(s+=" filled",e.push(`--room-fill:${n.color}`,`--room-fill-op:${n.opacity.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),o=e.join(";")}const r=t.id||W,a=t.area||W,l=t.poly?j``:j``;return l}),y=e.rooms.filter(t=>{const e=f.get(t);return ws(i.glow,t)&&(!e||e.opacity<=0)}).map(t=>t.poly?j``}_valueChanged(t){const e={...this._config,...t.detail.value},i=new Event("config-changed",{bubbles:!0,composed:!0});i.detail={config:e},this.dispatchEvent(i)}}function oc(t){const e=Ma(t.cfg),i={};for(const e of t.cfg.spaces||[])for(const t of e.rooms||[])t.area&&(i[t.area]=e.id);const s=t.cfg.settings?.exclude_integrations?new Set(t.cfg.settings.exclude_integrations):Et,n=Nt(t.cfg.settings?.icon_rules?.length?t.cfg.settings.icon_rules:Ft);return Nn({hass:t.hass,registry:t.registry,areaToSpace:i,markers:t.cfg.markers||[],settings:t.cfg.settings||{},excluded:s,showAll:!!t.cfg.settings?.show_all,firstSpaceId:e[0]?.id||"",loc:e=>ho(t.lang,e),iconRules:n})}function rc(t){const e=Ma(t.cfg).find(e=>e.id===t.spaceId);if(!e)return null;const i=Yi(t.cfg.spaces.find(e=>e.id===t.spaceId)),s=Xi(t.cfg.settings),n=t.iconSize??2.5,o=n>8?2.5:n,r=t.registry?rn(t.hass,t.registry):t.hass,a=(t.devices||oc(t)).filter(e=>e.space===t.spaceId),l=a.filter(t=>!t.hidden),c=function(t,e,i){const s={},n=i/100*Va(e)*1.3;for(const i of e.rooms){if(!i.area)continue;const e=t.filter(t=>t.area===i.area);if(!e.length)continue;const o=Ja(i),r=.1*Math.min(o.w,o.h),a=o.w-2*r,l=o.h-2*r,c=Math.max(1,Math.round(Math.sqrt(e.length*a/Math.max(l,1)))),h=a/c,d=l/Math.max(Math.ceil(e.length/c),1),p=e.map((t,e)=>({x:o.x+r+h*(e%c+.5),y:o.y+r+d*(Math.floor(e/c)+.5)}));ki(p,o,n,.5*r),e.forEach((t,e)=>s[t.id]=za(p[e]))}return s}(a,e,o),h=[];for(const e of l){const i=t.layout[e.id];if(i&&i.s===t.spaceId){const t=i.x*ba,e=i.y*ba;h.push({minX:t,minY:e,maxX:t,maxY:e})}}const d=t.cfg.spaces.find(e=>e.id===t.spaceId)||{},p=Array.isArray(d.walls)?d.walls:[],u=Number(d.cell_cm)>0?Number(d.cell_cm):5,_=function(t,e,i){const s=[];for(const n of t.partitions||[]){const t=ca(n.a,n.b,n.cm,e,i);t&&s.push(t)}for(const n of t.room_drafts||[])s.push(...da(n,e,i));for(const n of t.wall_columns||[])s.push(ha(n,e,i));return s}(e,u,Ra);for(const t of _){const e=t.map(t=>t[0]),i=t.map(t=>t[1]);e.length&&h.push({minX:Math.min(...e),minY:Math.min(...i),maxX:Math.max(...e),maxY:Math.max(...i)})}const m=ja(e,h),g=[m.x,m.y,m.w,m.h],f=new Map(e.rooms.map(t=>{const e=bs(i.fill,t);return[t,ts(e,"lqi"===e&&t.area?Hn(r,a,t.area):null,"light"===e?Mn(xn(r,a,t)):"none","temp"===e&&t.area?qn(r,a,t.area):null,i.tempMin,i.tempMax,s,Ki(i.customFill,t))]})),v=e.rooms.filter(t=>t.area||i.showBorders||"none"!==bs(i.fill,t)).map(t=>{let s="room "+(e.bg?"overlay":"yard"),n="";const o=bs(i.fill,t);if(i.showBorders||"none"!==o){s+=" styled";const e=[`--room-stroke:${i.color}`,`--room-stroke-op:${i.showBorders?i.opacity:0}`],o=f.get(t)||null;o?(s+=" filled",e.push(`--room-fill:${o.color}`,`--room-fill-op:${o.opacity.toFixed(3)}`)):e.push("--room-fill:transparent","--room-fill-op:0"),n=e.join(";")}const r=t.id||G,a=t.area||G,l=t.poly?W``:W``;return l}),y=e.rooms.filter(t=>{const e=f.get(t);return ws(i.glow,t)&&(!e||e.opacity<=0)}).map(t=>t.poly?W``:j``:G}`}if("ellipse"===n.kind){const i=(n.x+n.w/2)*t,h=(n.y+n.h/2)*e,d=Zo(n.angle);return W` - ${s?j``:W}`}if("furniture"===o.kind){const i=o.w*t,h=o.h*e,d=cl(o.symbol,i,h);if(!d)return W;const p=Number(o.angle)||0,u=o.x*t+i/2,_=o.y*e+h/2,m=`${p?`rotate(${p} ${u} ${_}) `:""}translate(${o.x*t} ${o.y*e})`;return j``:G}`}if("furniture"===n.kind){const i=n.w*t,h=n.h*e,d=ll(n.symbol,i,h);if(!d)return G;const p=Number(n.angle)||0,u=n.x*t+i/2,_=n.y*e+h/2,m=`${p?`rotate(${p} ${u} ${_}) `:""}translate(${n.x*t} ${n.y*e})`;return W` - ${s?j``:W}`}if("text"===o.kind){const i=this._decorTextUnits(o),s=function(t){return String(t??"").replace(/\r\n?/g,"\n").split("\n")}(function(t,e,i,s=()=>!0){const o=t??"";let n=!1;const r=o.replace(/\{([^{}\r\n]+)\}/g,(t,e)=>{const o=Ni(e);return o?(n=!0,s(o.entity||"")?Hi(i,o):Ii):t});if(n)return r;const a=(e?.entity||"").trim();if(!a)return o;if(!s(a)){const t=o.indexOf(Ei);return t>=0?o.slice(0,t)+Ii+o.slice(t+2):o?`${o} ${Ii}`:Ii}const l=Hi(i,e),c=o.indexOf(Ei);return c>=0?o.slice(0,c)+l+o.slice(c+2):o?`${o} ${l}`:l}(o.text,o,this.hass,t=>this._planEntityAvailable(t))),a=o.x*t,h=o.y*e,d=Number(o.angle)||0,p=h-(s.length-1)*i*1.2/2;return j``:G}`}if("text"===n.kind){const i=this._decorTextUnits(n),s=function(t){return String(t??"").replace(/\r\n?/g,"\n").split("\n")}(function(t,e,i,s=()=>!0){const n=t??"";let o=!1;const r=n.replace(/\{([^{}\r\n]+)\}/g,(t,e)=>{const n=Fi(e);return n?(o=!0,s(n.entity||"")?Hi(i,n):Ii):t});if(o)return r;const a=(e?.entity||"").trim();if(!a)return n;if(!s(a)){const t=n.indexOf(Ei);return t>=0?n.slice(0,t)+Ii+n.slice(t+2):n?`${n} ${Ii}`:Ii}const l=Hi(i,e),c=n.indexOf(Ei);return c>=0?n.slice(0,c)+l+n.slice(c+2):n?`${n} ${l}`:l}(n.text,n,this.hass,t=>this._planEntityAvailable(t))),a=n.x*t,h=n.y*e,d=Number(n.angle)||0,p=h-(s.length-1)*i*1.2/2;return W`${s.map((t,e)=>j`${t}`)}`}return W});let n=W;const r=this._decorDraft;if(r){const t=this._decorStyle,e=Qn(t.widthCm,this._cellCm,this._gridPitch);if("line"===r.kind)n=j``;else{const i=Math.min(r.a[0],r.b[0]),s=Math.min(r.a[1],r.b[1]),o=Math.abs(r.b[0]-r.a[0]),a=Math.abs(r.b[1]-r.a[1]);n="rect"===r.kind?j``:j``}}return j`${o}${n}`}get _editorToolbarGroups(){return[]}_renderEditorGroupLauncher(t){return this._editorSecondary.renderGroupLauncher(t,this._editorToolbarGroups,this._editorSecondaryCopy)}get _editorSecondaryContextId(){const t=`editor:${this._mode}:${this._space}:${this._cfgEpoch}`,e=this._editorSecondary.activeGroup(this._editorToolbarGroups);if(e)return`${t}:group:${e.id}:${this._editorSecondary.groupGeneration}`;if("plan"===this._mode){const e=this._physicalSel;return e?`${t}:selection:${e.kind}:${e.id}:${e.segment??""}`:`${t}:tool:${this._tool}:${this._path.length}:${this._openWallAnchor?1:0}`}return"decor"===this._mode?"furniture"===this._decorTool?`${t}:palette:furniture:${this._furnPalette?.symbol||"none"}`:"select"===this._decorTool&&this._decorSel?`${t}:selection:decor:${this._decorSel}`:`${t}:tool:${this._decorTool}:${this._bdMoved?1:0}:${this._bdDrag?1:0}`:`${t}:none`}_runEditorContext(t,e){this._editorSecondary.runContext(t,this._editorSecondaryContextId,e)}_renderEditorGroupModel(t){return this._editorSecondary.renderGroupModel(t,this._editorSecondaryContextId,this._editorSecondaryCopy)}_renderDrawWallControl(){return B``:G} + `:G} ${this._kiosk&&this._kioskDots&&this._model.length>1?B`
${this._model.map(t=>B``)} -
`:W} - ${this._kioskDialog?this._renderKioskDialog():W} + `:G} + ${this._kioskDialog?this._renderKioskDialog():G} ${this._vacFit?B`
${this._t("vac.fit_hint")} -
`:W} + `:G} ${this._tapConfirm?B`this._tapConfirm=null}>

${this._tapConfirm.text}

@@ -4681,30 +4751,30 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 ${this._t("btn.run")} -
`:W} - ${this._toast?B``:W} + `:G} + ${this._toast?B``:G} - `}_vacCandidateStatus(t,e){const i=this._bindingStatus("entity:"+t);if("ha_disabled"===i.kind)return"disabled";if("orphaned"===i.kind)return"missing";if("unverified"===i.kind)return"unverified";const s=this.hass?.states?.[t];return"unavailable"===s?.state?"unavailable":e?.hasPosition?"ok":"unsupported"}_vacOpenAllCameras(t){const e=this._haRegistry.entities||{},i=[];for(const[t,s]of Object.entries(this.hass?.states||{})){if(!t.startsWith("camera."))continue;const o=ql(t,s,e[t]);o&&i.push(o)}this._vacAllCameraCache={devId:t.id,candidates:i},this._vacAllCamerasFor=t.id}_vacSourceResolution(t,e=!1){const i=t.marker?.vacuum,s="string"==typeof i?.source&&!!i.source,o=new Set(t.entities||[]),n=new Set(o);s&&n.add(i.source);const r=this._haRegistry.entities||{},a=[];for(const t of n){const e=this.hass?.states?.[t],o=ql(t,e,r[t]);o?a.push(o):s&&t===i.source&&a.push({entityId:t,name:String(e?.attributes?.friendly_name||t),platform:r[t]?.platform?String(r[t].platform):null,category:t.startsWith("camera.")?"camera":"partial",hasPosition:!1,hasRooms:!1,hasPath:!1,hasMapId:!1,score:0})}if(e&&this._vacAllCameraCache?.devId===t.id){const t=new Set(a.map(t=>t.entityId));for(const e of this._vacAllCameraCache.candidates)t.has(e.entityId)||a.push(e)}const l={};for(const t of a)l[t.entityId]=this._vacCandidateStatus(t.entityId,t);return function(t,e,i,s){const o="string"==typeof t&&t.length>0,n=new Set(e),r=Array.from(i).sort((e,i)=>o&&e.entityId===t?-1:o&&i.entityId===t?1:i.score-e.score||e.entityId.localeCompare(i.entityId));if(o){const e=r.find(e=>e.entityId===t);return{entityId:t,status:s[t]||(e?.hasPosition?"ok":"unverified"),pinned:!0,candidates:r}}const a=r.find(t=>n.has(t.entityId)&&t.hasPosition&&"ok"===s[t.entityId]);return a?{entityId:a.entityId,status:"ok",pinned:!1,candidates:r}:{entityId:null,status:"none",pinned:!1,candidates:r}}(i?.source,o,a,l)}_vacSource(t){if(!1===t.marker?.vacuum?.live)return null;const e=this._vacSourceResolution(t);return"ok"===e.status||"unsupported"===e.status?e.entityId:null}_vacEntity(t){return t.primary?.startsWith("vacuum.")?t.primary:(t.entities||[]).find(t=>t.startsWith("vacuum."))||null}_isVacDev(t){return!!this._vacEntity(t)}_activitySourceKey(t){return Yo(this._planHass,t,!1!==this._config?.show_temperature)}_activitySnapshot(t){const e=Wo(this._planHass,t);return{samples:e.samples,sourceKey:Yo(this._planHass,t,!1!==this._config?.show_temperature,e)}}_stampActivity(t,e,i){let s=this._activityRt.get(t);s||(s={sources:i||"",last:{},flashTs:0,flashKind:null,timer:0,gen:0},this._activityRt.set(t,s)),null!=i&&(s.sources=i),s.flashTs&&Date.now()-s.flashTs<3300&&"event"===s.flashKind&&"transition"===e||(s.flashTs=Date.now(),s.flashKind=e,s.gen++,clearTimeout(s.timer),s.timer=window.setTimeout(()=>this.requestUpdate(),3360))}_syncActivityRuntime(){const t=new Map;if(!this.hass)return t;const e=new Set;for(const i of this._devices){if(i.hidden)continue;if("static_icon"===Si(i.marker?.display))continue;e.add(i.id);const s=this._activitySnapshot(i);t.set(i.id,s);const{samples:o,sourceKey:n}=s;let r=this._activityRt.get(i.id);if(r){if(r.sources!==n){clearTimeout(r.timer),r.sources=n,r.last={},r.flashTs=0,r.flashKind=null;for(const t of o)r.last[t.eid]=t.state}}else{r={sources:n,last:{},flashTs:0,flashKind:null,timer:0,gen:0};for(const t of o)r.last[t.eid]=t.state;this._activityRt.set(i.id,r)}}for(const[t,i]of this._activityRt)e.has(t)||(clearTimeout(i.timer),this._activityRt.delete(t));return t}_activityTick(){if(!this.hass)return;const t=this._syncActivityRuntime();for(const e of this._devices){if(e.hidden)continue;if("static_icon"===Si(e.marker?.display))continue;const i=t.get(e.id)||this._activitySnapshot(e),{samples:s,sourceKey:o}=i,n=this._activityRt.get(e.id);if(!n||n.sources!==o)continue;"transition"===n.flashKind&&s.some(t=>"transition"===t.activity)&&(clearTimeout(n.timer),n.flashTs=0,n.flashKind=null);let r=null;for(const t of s){const e=Bs(n.last[t.eid],t);("event"===e||!r&&e)&&(r=e),n.last[t.eid]=t.state}r&&this._stampActivity(e.id,r,o)}}_vacTick(){if(this.hass)for(const t of this._devices){if(t.hidden||!this._isVacDev(t))continue;if("static_icon"===Si(t.marker?.display)){this._vacRt.delete(t.id);continue}const e=this._vacSource(t);if(!e)continue;const i=this._vacEntity(t),s=Gl(this.hass.states[i||""]?.state),o=Hl(this.hass.states[e]?.attributes);let n=this._vacRt.get(t.id);n||(n={trail:[],lastKey:"",lastTs:0,moving:!1,jump:!1,endedTs:0,lastPos:null},this._vacRt.set(t.id,n)),s&&!n.moving&&(n.trail=[],n.lastPos=null);const r="never"!==Jl(t.marker?.vacuum)&&!o?.path.length;!s&&n.moving&&(n.endedTs=Date.now(),r&&n.lastPos&&(n.trail=jl(n.trail,n.lastPos,40)),n.lastPos=null),n.moving=s;const a=o?.pos;if(s&&a){const t=a.x+":"+a.y;if(t!==n.lastKey){const e=Date.now();n.jump=n.lastTs>0&&e-n.lastTs>1e4,n.lastKey=t,n.lastTs=e,r&&n.lastPos&&(n.trail=jl(n.trail,n.lastPos,40)),n.lastPos=[a.x,a.y]}}}}_vacEnsureMarker(t){const e=this._serverCfg;if(!e)return null;e.markers=e.markers||[];const i=e.markers.find(e=>e.id===t.id);if(i)return i;if("device"!==t.bindingKind&&"entity"!==t.bindingKind||!t.bindingRef)return null;const s={id:t.id,binding:t.bindingKind+":"+t.bindingRef,space:t.space||null,area:t.area||null,hidden:!!t.hidden};return e.markers.push(s),s}_renderVacSection(t){const e=this._devices.find(e=>e.id===t.devId);if(!e||!this._isVacDev(e))return W;const i=e.marker?.vacuum||{},s=this._vacAllCamerasFor===e.id,o=this._vacSourceResolution(e,s),n=o.entityId,r=o.candidates.find(t=>t.entityId===n)||null,a=n?Hl(this.hass?.states[n]?.attributes):null,l=!1!==i.live&&("ok"===o.status||"unsupported"===o.status),c=this._vacPlanRoomAnchors(e.space),h=a?function(t,e){const i=new Set(e.map(Ul).filter(Boolean)),s=new Set;for(const e of t){const t=Ul(e.name||"");t&&i.has(t)&&s.add(t)}return s.size}(a.rooms,c.map(t=>t.name)):0,d=!!(l&&a&&h>=3),p=Object.keys(i.calibration||{}),u=t=>{const i=this._vacEnsureMarker(e);i&&(i.vacuum={...i.vacuum||{},...t},this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate())},_=new Set(e.entities||[]),m=o.candidates.filter(t=>_.has(t.entityId)||t.entityId===i.source),g=s?o.candidates.filter(t=>t.entityId.startsWith("camera.")&&!m.some(e=>e.entityId===t.entityId)):[],f=t=>[t.hasPosition?this._t("vac.cap_position"):"",t.hasRooms?this._t("vac.cap_rooms_short"):"",t.hasPath?this._t("vac.cap_path"):"",t.hasMapId?this._t("vac.cap_map"):""].filter(Boolean).join(" · ")||this._t("vac.cap_none"),v=t=>B` - `,y=o.candidates.some(t=>_.has(t.entityId)&&"known_xcme_incomplete"===t.category)||!!o.pinned&&!!n?.startsWith("camera.")&&!r?.hasPosition;return B` + `,y=n.candidates.some(t=>_.has(t.entityId)&&"known_xcme_incomplete"===t.category)||!!n.pinned&&!!o?.startsWith("camera.")&&!r?.hasPosition;return B`
-
${this._t("vac.diag_source")}${n||this._t("vac.source_none")}
- ${r?.platform?B`
${this._t("vac.diag_platform")}${r.platform}
`:W} -
${this._t("vac.diag_status")}${this._t(`vac.source_status_${o.status}`)}
+
${this._t("vac.diag_source")}${o||this._t("vac.source_none")}
+ ${r?.platform?B`
${this._t("vac.diag_platform")}${r.platform}
`:G} +
${this._t("vac.diag_status")}${this._t(`vac.source_status_${n.status}`)}
${this._t("vac.diag_position")}${a?.pos?this._t("common.yes"):this._t("common.no")}
${this._t("vac.diag_rooms")}${Bi(this._t("vac.diag_rooms_value"),{total:String(a?.rooms.length||0),matched:String(h),readiness:this._t(h>=3?"vac.autocal_ready":"vac.autocal_not_ready")})}
${this._t("vac.diag_path")}${a?.path.length?this._t("common.yes"):this._t("common.no")}
${this._t("vac.diag_map")}${a?.mapId??"default"}
- ${"missing"===o.status||"disabled"===o.status||"unverified"===o.status?B`
- ${this._t(`vac.source_banner_${o.status}`)} + ${"missing"===n.status||"disabled"===n.status||"unverified"===n.status?B`
+ ${this._t(`vac.source_banner_${n.status}`)} -
`:W} +
`:G} ${y?B`
${this._t("vac.xcme_hint")}
attributes:
@@ -4712,11 +4782,11 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
   - rooms
   - path
   - map_name
-
`:W} +
`:G}
${this._t("vac.choose_source")}
- @@ -4725,13 +4795,13 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 @toggle=${t=>{t.currentTarget.open?this._vacOpenAllCameras(e):(this._vacAllCamerasFor=null,this._vacAllCameraCache=null)}}> ${this._t("vac.all_cameras")}
${this._t("vac.all_cameras_warn")}
- ${s?g.length?g.map(v):B`
${this._t("vac.all_cameras_empty")}
`:W} + ${s?g.length?g.map(v):B`
${this._t("vac.all_cameras_empty")}
`:G}
@@ -4744,35 +4814,35 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 - ${p.length?B`
${Bi(this._t("vac.cal_maps"),{maps:p.join(", ")})}
`:W} - `:W} - `}_vacMapId(t,e){const i=this._vacEntity(t),s=i?this.hass?.states[i]?.attributes?.selected_map:null;return o=e.mapId,n=s,"default"!==o?o:null!=n?String(n):"default";var o,n}_vacSaveMatrix(t,e,i,s){const o=this._devices.find(e=>e.id===t),n=o?this._vacEnsureMarker(o):this._serverCfg?.markers?.find(e=>e.id===t);if(!n)return!1;const r={...n.vacuum||{}};return r.source=e,r.calibration={...r.calibration||{},[i]:s.map(t=>Number(t.toFixed(6)))},n.vacuum=r,this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate(),!0}_vacPlanRoomAnchors(t){return(this._spaceModel(t||void 0)?.rooms||[]).map(t=>({room:t,poly:Je(t)})).filter(({room:t,poly:e})=>t.name&&e).map(({room:t,poly:e})=>{const i=El(e);return i?{name:String(t.name),cx:i[0],cy:i[1]}:null}).filter(Boolean)}_vacAutoCalibrate(t){const e=this._vacSource(t),i=e?Hl(this.hass?.states[e]?.attributes):null;if(!e||!i||i.rooms.length<3)return void this._showToast(this._t("vac.autocal_no_rooms"));const s=this._vacPlanRoomAnchors(t.space),o=Bl(i.rooms,s);if(!o)return void this._showToast(this._t("vac.autocal_no_match"));const n=this._serverCfg?.spaces?.find(e=>e.id===t.space),r=Number(n?.cell_cm),a=Number.isFinite(r)&&r>0?r:5,l=function(t,e,i){return![t,e,i].every(Number.isFinite)||t<0||e<=0||i<=0?Number.POSITIVE_INFINITY:t/e*i}(o.residual,this._gridPitch,a),c=this._vacMapId(t,i);l>40?this._vacCalConfirm={markerId:t.id,source:e,mapId:c,matrix:o.matrix,rooms:o.matched.length,error:Ke(l,"mi"===this.hass?.config?.unit_system?.length)}:this._vacSaveMatrix(t.id,e,c,o.matrix)&&this._showToast(Bi(this._t("vac.autocal_done"),{rooms:String(o.matched.length)}))}_vacApplyCalibrationProposal(t){const e=this._vacCalConfirm;if(e){if(this._vacCalConfirm=null,t){const t=this._devices.find(t=>t.id===e.markerId),i=Kl(e.matrix);if(!t||!i)return;return this._markerDialog=null,t.space!==this._space&&(this._space=t.space),void(this._vacFit={markerId:e.markerId,source:e.source,mapId:e.mapId,p:i,drag:null})}this._vacSaveMatrix(e.markerId,e.source,e.mapId,e.matrix)&&this._showToast(Bi(this._t("vac.autocal_done"),{rooms:String(e.rooms)}))}}_vacStartFit(t){const e=this._vacSource(t),i=e?Hl(this.hass?.states[e]?.attributes):null;if(!e||!i)return void this._showToast(this._t("vac.cal_need_pos"));const s=this._vacMapId(t,i),o=t.marker?.vacuum?.calibration?.[s],n=this._spaceModel(t.space),r=n?.vb||[0,0,eh,eh],a=o&&6===o.length&&Kl(o)||function(t,e){const i=[],s=[];for(const e of t)null!=e.x0?(i.push(e.x0,e.x1),s.push(e.y0,e.y1)):(i.push(e.cx),s.push(e.cy));if(!i.length)return{ox:e[0]+e[2]/2,oy:e[1]+e[3]/2,s:e[2]/1e4,rot:0,mir:!0};const o=Math.min(...i),n=Math.max(...i),r=Math.min(...s),a=Math.max(...s),l=Math.max(n-o,a-r)||1,c={ox:0,oy:0,s:.6*Math.min(e[2],e[3])/l,rot:0,mir:!0},h=Vl(c),[d,p]=Rl(h,(o+n)/2,(r+a)/2);return c.ox=e[0]+e[2]/2-d,c.oy=e[1]+e[3]/2-p,c}(i.rooms,r);this._markerDialog=null,t.space!==this._space&&(this._space=t.space),this._vacFit={markerId:t.id,source:e,mapId:s,p:a,drag:null}}_vacFitSave(){const t=this._vacFit;if(!t)return;const e=this._vacSaveMatrix(t.markerId,t.source,t.mapId,Vl(t.p));this._vacFit=null,e&&this._showToast(this._t("vac.cal_done"))}_vacFitTurn(t){const e=this._vacFit;if(!e)return;const i=Hl(this.hass?.states[e.source]?.attributes),s=this._vacGhostCentre(i?.rooms||[]),o={...e.p,...t};this._vacFit={...e,p:Yl(o,e.p,s[0],s[1])}}_vacGhostCentre(t){const e=[],i=[];for(const s of t)e.push(s.x0??s.cx,s.x1??s.cx),i.push(s.y0??s.cy,s.y1??s.cy);return e.length?[(Math.min(...e)+Math.max(...e))/2,(Math.min(...i)+Math.max(...i))/2]:[0,0]}_vacDelta(t,e,i){const s=this._stageEl,o=s?.clientWidth||1,n=s?.clientHeight||1;return[e/o*t.w,i/n*t.h]}_vacFitPointer(t,e){const i=this._vacFit;if(!i)return;if(t.stopPropagation(),"pointerdown"===t.type){const e=t.target,s=e.getAttribute?.("data-corner");try{t.currentTarget.setPointerCapture?.(t.pointerId)}catch{}return void(this._vacFit={...i,drag:s?{kind:"scale",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:Number(s.split(",")[0]),fy:Number(s.split(",")[1])}:{kind:"move",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:0,fy:0}})}const s=i.drag;if(s){if("pointermove"===t.type){const[o,n]=this._vacDelta(e,t.clientX-s.sx,t.clientY-s.sy);if("move"===s.kind)this._vacFit={...i,p:{...s.p0,ox:s.p0.ox+o,oy:s.p0.oy+n}};else{const t=Hl(this.hass?.states[i.source]?.attributes),e=this._vacGhostCentre(t?.rooms||[]),r=Vl(s.p0),[a,l]=Rl(r,e[0],e[1]),[c,h]=Rl(r,s.fx,s.fy),d=Math.hypot(a-c,l-h)||1,[p,u]=[2*a-c,2*l-h],_=Math.hypot(p+2*o-c,u+2*n-h)/2,m=Math.max(.05,_/d),g={...s.p0,s:s.p0.s*m};this._vacFit={...i,p:Yl(g,s.p0,s.fx,s.fy)}}return}"pointerup"!==t.type&&"pointercancel"!==t.type||(this._vacFit={...i,drag:null})}}_renderVacFit(t){const e=this._vacFit;if(!e)return W;const i=Hl(this.hass?.states[e.source]?.attributes);if(!i)return W;const s=Vl(e.p),o=[],n=[],r=[];for(const t of i.rooms){if(null==t.x0)continue;const e=[[t.x0,t.y0],[t.x1,t.y0],[t.x1,t.y1],[t.x0,t.y1]].map(([t,e])=>Rl(s,t,e));e.forEach(([t,e])=>{n.push(t),r.push(e)});const[i,a]=Rl(s,t.cx,t.cy);o.push(j` - ${t.name}`)}let a=W;if(i.pos){const[e,o]=Rl(s,i.pos.x,i.pos.y);a=j``}const l=[];if(n.length){const e=(()=>{const t=s[0]*s[4]-s[1]*s[3];return(e,i)=>[(s[4]*(e-s[2])-s[1]*(i-s[5]))/t,(-s[3]*(e-s[2])+s[0]*(i-s[5]))/t]})(),i=Math.min(...n),o=Math.max(...n),a=Math.min(...r),c=Math.max(...r),h=.022*t.w,d=h/4;for(const[t,s,n,r]of[[i,a,o,c],[o,a,i,c],[o,c,i,a],[i,c,o,a]]){const i=e(n,r);l.push(j`${Bi(this._t("vac.cal_maps"),{maps:p.join(", ")})}`:G} + `:G} + `}_vacMapId(t,e){const i=this._vacEntity(t),s=i?this.hass?.states[i]?.attributes?.selected_map:null;return n=e.mapId,o=s,"default"!==n?n:null!=o?String(o):"default";var n,o}_vacSaveMatrix(t,e,i,s){const n=this._devices.find(e=>e.id===t),o=n?this._vacEnsureMarker(n):this._serverCfg?.markers?.find(e=>e.id===t);if(!o)return!1;const r={...o.vacuum||{}};return r.source=e,r.calibration={...r.calibration||{},[i]:s.map(t=>Number(t.toFixed(6)))},o.vacuum=r,this._regSignature="",this._maybeRebuildDevices(),this._saveConfig(),this.requestUpdate(),!0}_vacPlanRoomAnchors(t){return(this._spaceModel(t||void 0)?.rooms||[]).map(t=>({room:t,poly:Je(t)})).filter(({room:t,poly:e})=>t.name&&e).map(({room:t,poly:e})=>{const i=Fl(e);return i?{name:String(t.name),cx:i[0],cy:i[1]}:null}).filter(Boolean)}_vacAutoCalibrate(t){const e=this._vacSource(t),i=e?ql(this.hass?.states[e]?.attributes):null;if(!e||!i||i.rooms.length<3)return void this._showToast(this._t("vac.autocal_no_rooms"));const s=this._vacPlanRoomAnchors(t.space),n=Wl(i.rooms,s);if(!n)return void this._showToast(this._t("vac.autocal_no_match"));const o=this._serverCfg?.spaces?.find(e=>e.id===t.space),r=Number(o?.cell_cm),a=Number.isFinite(r)&&r>0?r:5,l=function(t,e,i){return![t,e,i].every(Number.isFinite)||t<0||e<=0||i<=0?Number.POSITIVE_INFINITY:t/e*i}(n.residual,this._gridPitch,a),c=this._vacMapId(t,i);l>40?this._vacCalConfirm={markerId:t.id,source:e,mapId:c,matrix:n.matrix,rooms:n.matched.length,error:Ke(l,"mi"===this.hass?.config?.unit_system?.length)}:this._vacSaveMatrix(t.id,e,c,n.matrix)&&this._showToast(Bi(this._t("vac.autocal_done"),{rooms:String(n.matched.length)}))}_vacApplyCalibrationProposal(t){const e=this._vacCalConfirm;if(e){if(this._vacCalConfirm=null,t){const t=this._devices.find(t=>t.id===e.markerId),i=Yl(e.matrix);if(!t||!i)return;return this._markerDialog=null,t.space!==this._space&&(this._space=t.space),void(this._vacFit={markerId:e.markerId,source:e.source,mapId:e.mapId,p:i,drag:null})}this._vacSaveMatrix(e.markerId,e.source,e.mapId,e.matrix)&&this._showToast(Bi(this._t("vac.autocal_done"),{rooms:String(e.rooms)}))}}_vacStartFit(t){const e=this._vacSource(t),i=e?ql(this.hass?.states[e]?.attributes):null;if(!e||!i)return void this._showToast(this._t("vac.cal_need_pos"));const s=this._vacMapId(t,i),n=t.marker?.vacuum?.calibration?.[s],o=this._spaceModel(t.space),r=o?.vb||[0,0,oh,oh],a=n&&6===n.length&&Yl(n)||function(t,e){const i=[],s=[];for(const e of t)null!=e.x0?(i.push(e.x0,e.x1),s.push(e.y0,e.y1)):(i.push(e.cx),s.push(e.cy));if(!i.length)return{ox:e[0]+e[2]/2,oy:e[1]+e[3]/2,s:e[2]/1e4,rot:0,mir:!0};const n=Math.min(...i),o=Math.max(...i),r=Math.min(...s),a=Math.max(...s),l=Math.max(o-n,a-r)||1,c={ox:0,oy:0,s:.6*Math.min(e[2],e[3])/l,rot:0,mir:!0},h=Kl(c),[d,p]=Al(h,(n+o)/2,(r+a)/2);return c.ox=e[0]+e[2]/2-d,c.oy=e[1]+e[3]/2-p,c}(i.rooms,r);this._markerDialog=null,t.space!==this._space&&(this._space=t.space),this._vacFit={markerId:t.id,source:e,mapId:s,p:a,drag:null}}_vacFitSave(){const t=this._vacFit;if(!t)return;const e=this._vacSaveMatrix(t.markerId,t.source,t.mapId,Kl(t.p));this._vacFit=null,e&&this._showToast(this._t("vac.cal_done"))}_vacFitTurn(t){const e=this._vacFit;if(!e)return;const i=ql(this.hass?.states[e.source]?.attributes),s=this._vacGhostCentre(i?.rooms||[]),n={...e.p,...t};this._vacFit={...e,p:Jl(n,e.p,s[0],s[1])}}_vacGhostCentre(t){const e=[],i=[];for(const s of t)e.push(s.x0??s.cx,s.x1??s.cx),i.push(s.y0??s.cy,s.y1??s.cy);return e.length?[(Math.min(...e)+Math.max(...e))/2,(Math.min(...i)+Math.max(...i))/2]:[0,0]}_vacDelta(t,e,i){const s=this._stageEl,n=s?.clientWidth||1,o=s?.clientHeight||1;return[e/n*t.w,i/o*t.h]}_vacFitPointer(t,e){const i=this._vacFit;if(!i)return;if(t.stopPropagation(),"pointerdown"===t.type){const e=t.target,s=e.getAttribute?.("data-corner");try{t.currentTarget.setPointerCapture?.(t.pointerId)}catch{}return void(this._vacFit={...i,drag:s?{kind:"scale",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:Number(s.split(",")[0]),fy:Number(s.split(",")[1])}:{kind:"move",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:0,fy:0}})}const s=i.drag;if(s){if("pointermove"===t.type){const[n,o]=this._vacDelta(e,t.clientX-s.sx,t.clientY-s.sy);if("move"===s.kind)this._vacFit={...i,p:{...s.p0,ox:s.p0.ox+n,oy:s.p0.oy+o}};else{const t=ql(this.hass?.states[i.source]?.attributes),e=this._vacGhostCentre(t?.rooms||[]),r=Kl(s.p0),[a,l]=Al(r,e[0],e[1]),[c,h]=Al(r,s.fx,s.fy),d=Math.hypot(a-c,l-h)||1,[p,u]=[2*a-c,2*l-h],_=Math.hypot(p+2*n-c,u+2*o-h)/2,m=Math.max(.05,_/d),g={...s.p0,s:s.p0.s*m};this._vacFit={...i,p:Jl(g,s.p0,s.fx,s.fy)}}return}"pointerup"!==t.type&&"pointercancel"!==t.type||(this._vacFit={...i,drag:null})}}_renderVacFit(t){const e=this._vacFit;if(!e)return G;const i=ql(this.hass?.states[e.source]?.attributes);if(!i)return G;const s=Kl(e.p),n=[],o=[],r=[];for(const t of i.rooms){if(null==t.x0)continue;const e=[[t.x0,t.y0],[t.x1,t.y0],[t.x1,t.y1],[t.x0,t.y1]].map(([t,e])=>Al(s,t,e));e.forEach(([t,e])=>{o.push(t),r.push(e)});const[i,a]=Al(s,t.cx,t.cy);n.push(W` + ${t.name}`)}let a=G;if(i.pos){const[e,n]=Al(s,i.pos.x,i.pos.y);a=W``}const l=[];if(o.length){const e=(()=>{const t=s[0]*s[4]-s[1]*s[3];return(e,i)=>[(s[4]*(e-s[2])-s[1]*(i-s[5]))/t,(-s[3]*(e-s[2])+s[0]*(i-s[5]))/t]})(),i=Math.min(...o),n=Math.max(...o),a=Math.min(...r),c=Math.max(...r),h=.022*t.w,d=h/4;for(const[t,s,o,r]of[[i,a,n,c],[n,a,i,c],[n,c,i,a],[i,c,n,a]]){const i=e(o,r);l.push(W` `)}}return B`this._vacFitPointer(e,t)} @pointermove=${e=>this._vacFitPointer(e,t)} @pointerup=${e=>this._vacFitPointer(e,t)} - @pointercancel=${e=>this._vacFitPointer(e,t)}>${o}${a}${l}`}_vacRafLoop(){this._vacRaf=requestAnimationFrame(()=>{const t=this.renderRoot,e=this._stageEl,i=this._vacLastView,s=t?.querySelectorAll?.(".vacpuck")||[];if(!e||!i||!s.length)return void(this._vacRaf=0);const o=e.getBoundingClientRect();for(const e of s){const s=e.getAttribute("data-mid"),n=e.getBoundingClientRect(),r=i.x+(n.left+n.width/2-o.left)/o.width*i.w,a=i.y+(n.top+n.height/2-o.top)/o.height*i.h;for(const e of t.querySelectorAll(`line.tip[data-mid="${s}"]`))e.setAttribute("x2",r.toFixed(1)),e.setAttribute("y2",a.toFixed(1))}this._vacRafLoop()})}_renderVacuums(t,e){if(this._markup||"decor"===this._mode)return W;const i=this._space+"|"+e.x+"|"+e.y+"|"+e.w+"|"+e.h,s=this._vacJumpOnce||i!==this._vacViewKey;this._vacViewKey=i,this._vacJumpOnce=!1;const o=[],n=[];for(const i of t){if(i.hidden||!this._isVacDev(i))continue;if("static_icon"===Si(i.marker?.display))continue;const t=this._vacSource(i);if(!t)continue;const r=Hl(this.hass?.states[t]?.attributes);if(!r)continue;const a=i.marker?.vacuum?.calibration?.[this._vacMapId(i,r)];if(!a||6!==a.length)continue;const l=this._vacRt.get(i.id),c=l?.moving??!1,h=Jl(i.marker?.vacuum),d="always"===h||"cleaning"===h&&c,p=this._vacSrvTrails[i.id],u=this._vacMapId(i,r),_=p?.current?.map_id===u&&Array.isArray(p.current.points)?p.current:null,m=p?.previous?.map_id===u&&Array.isArray(p.previous.points)?p.previous:null;if("always"===h&&m&&m.points.length>1){const t=m.points.map(([t,e])=>{const[i,s]=Rl(a,t,e);return i.toFixed(1)+","+s.toFixed(1)}).join(" ");n.push(j``)}if(d){const t=Ol(r,_,l?.trail||[]),e=!c||"integration"!==t.source&&"server"!==t.source?t.path:Ll(t.path);if(e.length){const t=e.map(t=>t.map(([t,e],i)=>{const[s,o]=Rl(a,t,e);return`${i?"L":"M"} ${s.toFixed(1)} ${o.toFixed(1)}`}).join(" ")).join(" ");n.push(j``);const s=e[e.length-1];if(c&&s?.length>=2){const t=s[s.length-1],[e,o]=Rl(a,t[0],t[1]),r=e.toFixed(1),l=o.toFixed(1);n.push(j``)}}}if(!c||!r.pos)continue;const[g,f]=Rl(a,r.pos.x,r.pos.y),v=(g-e.x)/e.w*100,y=(f-e.y)/e.h*100,b=l&&l.lastTs>0&&Date.now()-l.lastTs>6e4,w=i.marker?.icon||i.icon||"mdi:robot-vacuum";o.push(B`
this._vacFitPointer(e,t)}>${n}${a}${l}`}_vacRafLoop(){this._vacRaf=requestAnimationFrame(()=>{const t=this.renderRoot,e=this._stageEl,i=this._vacLastView,s=t?.querySelectorAll?.(".vacpuck")||[];if(!e||!i||!s.length)return void(this._vacRaf=0);const n=e.getBoundingClientRect();for(const e of s){const s=e.getAttribute("data-mid"),o=e.getBoundingClientRect(),r=i.x+(o.left+o.width/2-n.left)/n.width*i.w,a=i.y+(o.top+o.height/2-n.top)/n.height*i.h;for(const e of t.querySelectorAll(`line.tip[data-mid="${s}"]`))e.setAttribute("x2",r.toFixed(1)),e.setAttribute("y2",a.toFixed(1))}this._vacRafLoop()})}_renderVacuums(t,e){if(this._markup||"decor"===this._mode)return G;const i=this._space+"|"+e.x+"|"+e.y+"|"+e.w+"|"+e.h,s=this._vacJumpOnce||i!==this._vacViewKey;this._vacViewKey=i,this._vacJumpOnce=!1;const n=[],o=[];for(const i of t){if(i.hidden||!this._isVacDev(i))continue;if("static_icon"===Si(i.marker?.display))continue;const t=this._vacSource(i);if(!t)continue;const r=ql(this.hass?.states[t]?.attributes);if(!r)continue;const a=i.marker?.vacuum?.calibration?.[this._vacMapId(i,r)];if(!a||6!==a.length)continue;const l=this._vacRt.get(i.id),c=l?.moving??!1,h=Zl(i.marker?.vacuum),d="always"===h||"cleaning"===h&&c,p=this._vacSrvTrails[i.id],u=this._vacMapId(i,r),_=p?.current?.map_id===u&&Array.isArray(p.current.points)?p.current:null,m=p?.previous?.map_id===u&&Array.isArray(p.previous.points)?p.previous:null;if("always"===h&&m&&m.points.length>1){const t=m.points.map(([t,e])=>{const[i,s]=Al(a,t,e);return i.toFixed(1)+","+s.toFixed(1)}).join(" ");o.push(W``)}if(d){const t=Ll(r,_,l?.trail||[]),e=!c||"integration"!==t.source&&"server"!==t.source?t.path:Hl(t.path);if(e.length){const t=e.map(t=>t.map(([t,e],i)=>{const[s,n]=Al(a,t,e);return`${i?"L":"M"} ${s.toFixed(1)} ${n.toFixed(1)}`}).join(" ")).join(" ");o.push(W``);const s=e[e.length-1];if(c&&s?.length>=2){const t=s[s.length-1],[e,n]=Al(a,t[0],t[1]),r=e.toFixed(1),l=n.toFixed(1);o.push(W``)}}}if(!c||!r.pos)continue;const[g,f]=Al(a,r.pos.x,r.pos.y),v=(g-e.x)/e.w*100,y=(f-e.y)/e.h*100,b=l&&l.lastTs>0&&Date.now()-l.lastTs>6e4,w=i.marker?.icon||i.icon||"mdi:robot-vacuum";n.push(B`
{t.stopPropagation();const e=this._vacEntity(i);e&&this._openMoreInfo(e)}}> -
`)}return this._vacLastView=e,o.length&&!this._vacRaf&&this._vacRafLoop(),o.length||n.length?B` - ${n.length?j`${n}`:W} - ${o}`:W}_renderDevice(t,e,i=!0){const s=this._pos(t),o=(s.x-e.x)/e.w*100,n=(s.y-e.y)/e.h*100,r=this._devicePresentation(t,i),a=[`left:${o}%`,`top:${n}%`,...zt(r)],l=r.disabledReason,c=r.haDisabled?this._t(`marker.ha_disabled_${l}`):t.userHidden?this._t("marker.hidden_ghost"):t.name,h=[t.model,null!=r.tempText?r.tempText+"°":"",null!=r.humText?r.humText+"%":"",null!=r.lqiText?"LQI "+r.lqiText:""].filter(Boolean).join(" · ");return B`
`)}return this._vacLastView=e,n.length&&!this._vacRaf&&this._vacRafLoop(),n.length||o.length?B` + ${o.length?W`${o}`:G} + ${n}`:G}_renderDevice(t,e,i=!0){const s=this._pos(t),n=(s.x-e.x)/e.w*100,o=(s.y-e.y)/e.h*100,r=this._devicePresentation(t,i),a=[`left:${n}%`,`top:${o}%`,...zt(r)],l=r.disabledReason,c=r.haDisabled?this._t(`marker.ha_disabled_${l}`):t.userHidden?this._t("marker.hidden_ghost"):t.name,h=[t.model,null!=r.tempText?r.tempText+"°":"",null!=r.humText?r.humText+"%":"",null!=r.lqiText?"LQI "+r.lqiText:""].filter(Boolean).join(" · ");return B`
`}_roomArea(t){const e=Je(t);if(!e)return null;const i=this._spaceWalls,s=i.length&&t.id&&jr(this._spaceModel().rooms,t.id,i,this._openPairs().flatMap(t=>t.segs),this._wallKeyPitch,this._cellCm,this._gridPitch,eh)||e,o=this._cleanFloor(t,s),n=this._cellCm/this._gridPitch;return In(o.area*n*n/1e4,"mi"===this.hass?.config?.unit_system?.length)}_roomTemp(t){const e=t.settings?.temp_source;return e?Oo(this._planHass,e,"temp",this._markers):t.area?this._climate().get(t.area)?.temp??null:null}_roomHum(t){const e=t.settings?.hum_source;return e?Oo(this._planHass,e,"hum",this._markers):t.area?this._climate().get(t.area)?.hum??null:null}_climate(){const t=this._serverCfg?.markers,e=this._planHass,i=this._climateCache;if(i&&i.h===e&&i.r===this._iconRules&&i.mk===t)return i.m;const s=function(t,e,i){const s=new Map;if(!t?.entities)return s;const o=Io(i),n=new Set;for(const t of i||[]){if(t?.removed||!0!==t?.use_climate_temp)continue;const e=(t.binding||"").indexOf(":");e>0&&n.add(t.binding.slice(e+1))}const r=new Map;for(const[e,i]of Object.entries(t.entities)){if(i.device_id&&o.devices.has(i.device_id)||!i.device_id&&o.entities.has(e))continue;const s=i.device_id?t.devices?.[i.device_id]:null,a=i.area_id||s?.area_id||null;if(!a)continue;if(i.entity_category)continue;if(!(n.size>0&&e.startsWith("climate.")&&(n.has(e)||i.device_id&&n.has(i.device_id)))){if(Et.has(i.platform))continue;if(Lo.test(e))continue}let l=r.get(a);l||(l=new Map,r.set(a,l));const c=i.device_id||e;let h=l.get(c);if(!h){const o=t.states?.[e];h={name:(s?s.name_by_user||s.name:i.name||o?.attributes?.friendly_name||e)||e,model:s?.model,ents:[]},l.set(c,h)}h.ents.push(e)}for(const[i,o]of r){const r=[],a=[];for(const[i,s]of o){const o=zo(t,s.name,s.model,s.ents,e),l="mdi:thermometer"===o||"mdi:air-filter"===o;if(l){const e=Do(t,s.ents);null!=e&&r.push(e)}if(n.size>0&&(n.has(i)||s.ents.some(t=>n.has(t)))){const e=To(t,s.ents);null!=e&&r.push(e)}if(l||"mdi:water-percent"===o){const e=Ro(t,s.ents);null!=e&&a.push(e)}}(r.length||a.length)&&s.set(i,{temp:r.length?Math.round(r.reduce((t,e)=>t+e,0)/r.length*10)/10:null,hum:a.length?Math.round(a.reduce((t,e)=>t+e,0)/a.length):null})}return s}(e,this._iconRules,t);return this._climateCache={h:e,r:this._iconRules,mk:t,m:s},s}_resetRoomDialogFields(){this._roomEditId=null,this._roomFill="",this._roomCustomFill=null,this._roomTempSrc="",this._roomHumSrc="",this._roomSrcOpen=null,this._roomSrcFilter="",this._roomNameScale=1,this._roomLabelScale=1}_openRoomEdit(t){if(!t.id)return;this._roomEditId=t.id,this._nameSel=t.name||"",this._areaSel=t.area||"",this._roomFill="glow"===t.settings?.fill_mode?"":t.settings?.fill_mode||"";const e=t.settings?.custom_fill;this._roomCustomFill=e&&"object"==typeof e?Vi(e,Yi(this._curSpaceCfg).customFill):null,this._roomTempSrc=t.settings?.temp_source||"",this._roomHumSrc=t.settings?.hum_source||"",this._roomNameScale=ks(t.settings?.name_scale),this._roomLabelScale=ks(t.settings?.label_scale),this._roomSrcOpen=null,this._roomSrcFilter="",this._roomDialog=!0}_roomSettingsFromDialog(){const t={};return this._roomFill&&(t.fill_mode=this._roomFill),this._roomCustomFill&&(t.custom_fill=this._roomCustomFill),this._roomTempSrc&&(t.temp_source=this._roomTempSrc),this._roomHumSrc&&(t.hum_source=this._roomHumSrc),1!==this._roomNameScale&&(t.name_scale=this._roomNameScale),1!==this._roomLabelScale&&(t.label_scale=this._roomLabelScale),Object.keys(t).length?t:null}_saveRoomEdit(){const t=this._curSpaceCfg,e=t?.rooms.find(t=>t.id===this._roomEditId);if(!e)return this._roomDialog=!1,void(this._roomEditId=null);e.name=this._nameSel.trim()||e.name,e.area=this._areaSel||null;const i=e.settings||{},s={...i};this._roomFill?s.fill_mode=this._roomFill:delete s.fill_mode,this._roomCustomFill?s.custom_fill=this._roomCustomFill:delete s.custom_fill,"glow"===i.fill_mode&&"boolean"!=typeof i.glow&&(s.glow=!0),this._roomTempSrc?s.temp_source=this._roomTempSrc:delete s.temp_source,this._roomHumSrc?s.hum_source=this._roomHumSrc:delete s.hum_source,1!==this._roomNameScale?s.name_scale=this._roomNameScale:delete s.name_scale,1!==this._roomLabelScale?s.label_scale=this._roomLabelScale:delete s.label_scale,Object.keys(s).length?e.settings=s:delete e.settings,this._saveConfig(),this._roomDialog=!1,this._roomEditId=null,this._nameSel="",this._areaSel="",this._regSignature="",this._maybeRebuildDevices(),this.requestUpdate(),this._showToast(this._t("toast.room_updated"))}_roomSrcCandidates(){const t=this._planHass,e=Io(this._markers),i=this._roomSrcFilter.trim().toLowerCase(),s=[];for(const o of Object.values(t.devices)){if("service"===o.entry_type)continue;if(e.devices.has(o.id))continue;const t=(o.name_by_user||o.name||o.id).trim();i&&!t.toLowerCase().includes(i)||s.push({value:"device:"+o.id,label:t,sub:o.model||this._t("marker.sub_device")})}for(const[o,n]of Object.entries(t.entities)){if(!o.startsWith("sensor.")||n.hidden)continue;if(Eo(t,o,e))continue;const r=n.name||t.states[o]?.attributes?.friendly_name||o;i&&!(r+" "+o).toLowerCase().includes(i)||s.push({value:"entity:"+o,label:r,sub:o})}return s.sort((t,e)=>t.label.localeCompare(e.label)),s.slice(0,200)}_roomSrcLabel(t){const e=t.indexOf(":"),i=t.slice(0,e),s=t.slice(e+1);return"device"===i?this._fullRegistryHass.devices[s]?.name_by_user||this._fullRegistryHass.devices[s]?.name||s:this._fullRegistryHass.entities[s]?.name||this.hass.states[s]?.attributes?.friendly_name||s}_labelPos(t,e){const i=this._layout["rl_"+(t.id||"")];if(i&&i.s===e)return{x:i.x*eh,y:i.y*eh};const s=this._snap(this._roomCenter(t));return{x:s[0],y:s[1]}}_labelDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=this._labelPos(e,i);this._drag={id:"rl_"+(e.id||""),sx:t.clientX,sy:t.clientY,ox:s.x,oy:s.y,moved:!1},lh(t),this._tip=null}_labelMove(t,e,i){const s="rl_"+(e.id||"");if(!this._drag||this._drag.id!==s)return;const o=this._stageEl;if(!o)return;const n=this._spaceModel(i).vb,r=o.getBoundingClientRect(),a=this._viewOr(n),l=(t.clientX-this._drag.sx)/r.width*a.w,c=(t.clientY-this._drag.sy)/r.height*a.h;Math.abs(t.clientX-this._drag.sx)+Math.abs(t.clientY-this._drag.sy)>3&&(this._drag.moved=!0);const h=Ea(this._drag.ox+l),d=Ea(this._drag.oy+c);this._savePos({id:s,space:i},h,d)}_labelUp(t){const e="rl_"+(t.id||"");if(!this._drag||this._drag.id!==e)return;const i=this._drag.moved;this._drag=i?this._drag:null,i&&window.setTimeout(()=>this._drag=null,0)}_labelScale(t){const e=this._layout["rl_"+(t.id||"")]?.k;return"number"==typeof e&&Number.isFinite(e)?Math.min(3,Math.max(.5,e)):1}_rlResizeDown(t,e,i){if("plan"!==this._mode)return;t.preventDefault(),t.stopPropagation();const s=t.target.closest(".roomlabel");if(!s)return;const o=s.getBoundingClientRect(),n=o.left+o.width/2,r=o.top+o.height/2,a=Math.max(8,Math.hypot(t.clientX-n,t.clientY-r));this._rlResize={id:"rl_"+(e.id||""),space:i,k0:this._labelScale(e),cx:n,cy:r,d0:a},lh(t)}_rlResizeMove(t){const e=this._rlResize;if(!e)return;t.stopPropagation();const i=Math.max(8,Math.hypot(t.clientX-e.cx,t.clientY-e.cy)),s=Math.min(3,Math.max(.5,e.k0*(i/e.d0))),o=this._layout[e.id];if(o)this._layout={...this._layout,[e.id]:{...o,k:s}};else{const t=e.id.slice(3),i=this._spaceModel(e.space).rooms.find(e=>e.id===t);if(!i)return;const o=this._labelPos(i,e.space);this._layout={...this._layout,[e.id]:{s:e.space,x:o.x/eh,y:o.y/eh,k:s}}}this._dirtyPos.add(e.id)}_rlResizeUp(){this._rlResize&&(this._rlResize=null,this._persistLayout())}_renderRoomGear(t,e,i){if(!t.id)return W;let s=null;if(t.poly?(s=this._gearPtCache.get(t.poly)||null,s||(s=ci(t.poly),this._gearPtCache.set(t.poly,s))):null!=t.x&&null!=t.y&&(s=[t.x+(t.w||0)/2,t.y+(t.h||0)/2]),!s)return W;const o=(s[0]-i.x)/i.w*100,n=(s[1]-i.y)/i.h*100;return B``}_renderRoomLabel(t,e,i,s){if(!t.name&&!this._markup)return W;const o=this._labelPos(t,e.id),n=(o.x-i.x)/i.w*100,r=(o.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if(t.area||t.settings?.temp_source||t.settings?.hum_source||s.labelLight){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight){const e=(h=xo(this._planHass,this._devices,t)).length?{on:h.filter(t=>t.on).length,total:h.length}:null;if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}var h;return B`
`}_renderRoomLabel(t,e,i,s){if(!t.name&&!this._markup)return G;const n=this._labelPos(t,e.id),o=(n.x-i.x)/i.w*100,r=(n.y-i.y)/i.h*100,a=Math.min(1,s.opacity+.25),l=this._labelScale(t),c=[];if(t.area||t.settings?.temp_source||t.settings?.hum_source||s.labelLight){if(s.labelTemp){const e=this._roomTemp(t);null!=e&&c.push(B`${e}°`)}if(s.labelHum){const e=this._roomHum(t);null!=e&&c.push(B`${e}%`)}if(s.labelLqi&&t.area){const e=this._roomLqi(t.area);null!=e&&c.push(B`${e}`)}if(s.labelLight){const e=(h=xn(this._planHass,this._devices,t)).length?{on:h.filter(t=>t.on).length,total:h.length}:null;if(e){const t=0===e.on?this._t("roomcard.light_off"):e.on===e.total?this._t("roomcard.light_on"):this._t("roomcard.light_partial",{on:e.on,total:e.total});c.push(B`${t}`)}}}var h;return B`
this._labelDown(i,t,e.id)} @pointermove=${i=>this._labelMove(i,t,e.id)} @pointerup=${()=>this._labelUp(t)} @@ -4802,38 +4872,38 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 >${t.name||(this._markup?this._t("room.unnamed"):"")}${!this._markup&&t.area?B`{e.stopPropagation(),this._clickRoom(t)}} - @pointerdown=${t=>t.stopPropagation()}>`:W} - ${c.length?B`${c}`:W} + @pointerdown=${t=>t.stopPropagation()}>`:G} + ${c.length?B`${c}`:G} ${"plan"===this._mode?["tl","tr","bl","br"].map(i=>B`this._rlResizeDown(i,t,e.id)} @pointermove=${t=>this._rlResizeMove(t)} @pointerup=${()=>this._rlResizeUp()} - @pointercancel=${()=>this._rlResizeUp()}>`):W} -
`}get _measureAnchor(){return this._markup&&this._cursorPt?"draw"!==this._tool&&"partition"!==this._tool||!this._path.length||this._contourClosed?"split"===this._tool&&this._splitSel?.pts?.length?this._splitSel.pts[this._splitSel.pts.length-1]:null:this._path[this._path.length-1]:null}_renderMeasureLabel(t){const e=this._measureAnchor,i=this._cursorPt,s=(i[0]-t.x)/t.w*100,o=(i[1]-t.y)/t.h*100,n=$s(e,i),r=Math.round(10*n)/10,a=xs(n);return B`
- ${this._fmtLen(e,i)} · ${r}°
`}get _decorMeasure(){const t=this._decorDraft;if(!t||"decor"!==this._mode)return null;const[e,i]=t.a,[s,o]=t.b;if(Math.abs(e-s)<1e-6&&Math.abs(i-o)<1e-6)return null;const n=(e+s)/2,r=(i+o)/2;if("line"===t.kind){const e=$s(t.a,t.b);return{x:n,y:r,on45:xs(e),text:`${this._fmtLen(t.a,t.b)} · ${Math.round(10*e)/10}°`}}const a=this._fmtLen([e,i],[s,i]),l=this._fmtLen([s,i],[s,o]);if("ellipse"===t.kind){const t=this._fmtLen([0,0],[Math.abs(s-e)/2,0]),a=this._fmtLen([0,0],[0,Math.abs(o-i)/2]);return{x:n,y:r,on45:!1,text:Math.abs(Math.abs(s-e)-Math.abs(o-i))<1e-6?`R ${t}`:`Rx ${t} × Ry ${a}`}}return{x:n,y:r,on45:!1,text:`${a} × ${l} · ${In(tr(Math.abs(s-e),this._cellCm,this._gridPitch)*tr(Math.abs(o-i),this._cellCm,this._gridPitch)/1e4,this._imperial)}`}}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=eh,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=Je(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id||"ha_disabled"===e.bindingStatus?.kind)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const e=this._decorMove?.id;return t.push(...this._decorSnapGeometry(e).points),this._decorDraft&&t.push(this._decorDraft.a),t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return j``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,o=null;for(const n of e)if(!(Math.abs(n[0]-t[0])<1e-6&&Math.abs(n[1]-t[1])<1e-6)){if(Math.abs(n[0]-t[0])<=i){const e=Math.abs(n[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!o||e - ${i.map(e=>{const[i,n,r,a]="x"===e.axis?[e.at,e.from[1],e.at,t[1]+Math.sign(t[1]-e.from[1])*o]:[e.from[0],e.at,t[0]+Math.sign(t[0]-e.from[0])*o,e.at];return j` + @pointercancel=${()=>this._rlResizeUp()}>`):G} +
`}get _measureAnchor(){return this._markup&&this._cursorPt?"draw"!==this._tool&&"partition"!==this._tool||!this._path.length||this._contourClosed?"split"===this._tool&&this._splitSel?.pts?.length?this._splitSel.pts[this._splitSel.pts.length-1]:null:this._path[this._path.length-1]:null}_renderMeasureLabel(t){const e=this._measureAnchor,i=this._cursorPt,s=(i[0]-t.x)/t.w*100,n=(i[1]-t.y)/t.h*100,o=$s(e,i),r=Math.round(10*o)/10,a=xs(o);return B`
+ ${this._fmtLen(e,i)} · ${r}°
`}get _decorMeasure(){const t=this._decorDraft;if(!t||"decor"!==this._mode)return null;const[e,i]=t.a,[s,n]=t.b;if(Math.abs(e-s)<1e-6&&Math.abs(i-n)<1e-6)return null;const o=(e+s)/2,r=(i+n)/2;if("line"===t.kind){const e=$s(t.a,t.b);return{x:o,y:r,on45:xs(e),text:`${this._fmtLen(t.a,t.b)} · ${Math.round(10*e)/10}°`}}const a=this._fmtLen([e,i],[s,i]),l=this._fmtLen([s,i],[s,n]);if("ellipse"===t.kind){const t=this._fmtLen([0,0],[Math.abs(s-e)/2,0]),a=this._fmtLen([0,0],[0,Math.abs(n-i)/2]);return{x:o,y:r,on45:!1,text:Math.abs(Math.abs(s-e)-Math.abs(n-i))<1e-6?`R ${t}`:`Rx ${t} × Ry ${a}`}}return{x:o,y:r,on45:!1,text:`${a} × ${l} · ${Io(tr(Math.abs(s-e),this._cellCm,this._gridPitch)*tr(Math.abs(n-i),this._cellCm,this._gridPitch)/1e4,this._imperial)}`}}get _alignPoint(){if(this._markup){if("draw"===this._tool&&this._path.length&&!this._contourClosed&&this._cursorPt)return this._cursorPt;if("split"===this._tool&&this._splitSel?.pts?.length&&this._cursorPt)return this._cursorPt;if(this._drag?.id.startsWith("rl_")&&this._drag.moved){const t=this._drag.id.slice(3),e=this._spaceModel().rooms.find(e=>e.id===t);return e?(()=>{const t=this._labelPos(e,this._space);return[t.x,t.y]})():null}return null}if("devices"===this._mode&&this._drag?.moved){const t=this._devices.find(t=>t.id===this._drag.id);return t?(()=>{const e=this._pos(t);return[e.x,e.y]})():null}if("decor"===this._mode){if(this._decorDraft)return this._decorDraft.b;if(this._decorMove){const t=this._decorList.find(t=>t.id===this._decorMove.id);if(!t)return null;const e=oh,i=this._decorH;return"line"===t.kind?[t.x1*e,t.y1*i]:[t.x*e,t.y*i]}return null}return null}_alignCandidates(){const t=[],e=this._spaceModel();if(this._markup){if(this._drag?.id.startsWith("rl_")){const i=this._drag.id.slice(3);for(const s of e.rooms){if(!s.name||s.id===i)continue;const e=this._labelPos(s,this._space);t.push([e.x,e.y])}return t}for(const i of e.rooms){const e=Je(i);if(e)for(const i of e)t.push(i)}if("draw"===this._tool)for(const e of this._path)t.push(e);if("split"===this._tool&&this._splitSel?.pts)for(const e of this._splitSel.pts)t.push(e);return t}if("devices"===this._mode){for(const e of this._devices){if(e.space!==this._space||e.id===this._drag?.id||"ha_disabled"===e.bindingStatus?.kind)continue;const i=this._pos(e);t.push([i.x,i.y])}return t}if("decor"===this._mode){const e=this._decorMove?.id;return t.push(...this._decorSnapGeometry(e).points),this._decorDraft&&t.push(this._decorDraft.a),t}return t}_renderAlignGuides(){const t=this._alignPoint;if(!t)return W``;const e=this._drag?.id.startsWith("rl_")?.5*this._gridPitch:.05*this._gridPitch,i=function(t,e,i){let s=null,n=null;for(const o of e)if(!(Math.abs(o[0]-t[0])<1e-6&&Math.abs(o[1]-t[1])<1e-6)){if(Math.abs(o[0]-t[0])<=i){const e=Math.abs(o[1]-t[1]);e>1e-6&&(!s||e1e-6&&(!n||e + ${i.map(e=>{const[i,o,r,a]="x"===e.axis?[e.at,e.from[1],e.at,t[1]+Math.sign(t[1]-e.from[1])*n]:[e.from[0],e.at,t[0]+Math.sign(t[0]-e.from[0])*n,e.at];return W` `})} - `}_renderOpeningCenterTick(t){const e=(t.angle+90)*Math.PI/180;return j``}_renderOpeningCenterTick(t){const e=(t.angle+90)*Math.PI/180;return W``}_roomCenter(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}_openingAmt(t){const e=t.contact&&this._planEntityAvailable(t.contact)?this.hass.states[t.contact]?.state:null;return ei(t.type,e,!!t.invert)}_planEntityAvailable(t){return!!t&&(!Eo(this._fullRegistryHass,t,Io(this._markers))&&"active"===this._bindingStatus("entity:"+t).kind)}_renderOpenings(t){const e=this._openingsR;if(!e.length)return j``;const i=t.color,s=this._spaceWalls,o=this._openPairs().flatMap(t=>t.segs),n=this._openingWallIndexFor(this._spaceModel(),o).value;return j`${e.map(t=>{const e=t.rlen/2,o=this._openingAmt(t),r=o>0&&!!t.contact&&this._planEntityAvailable(t.contact)?"var(--hp-open)":i,a="gate"===t.type?!t.flip_v:t.flip_v,l=s.length||"gate"===t.type?ta(n,{x:t.rx,y:t.ry,angle:t.angle,length:t.rlen,flip_v:a}):{ox:0,oy:0,cm:0,side:-1},c=l.cm>0?l.cm/this._cellCm*this._gridPitch/2:4,h=t.flip_h?-1:1,d=t.flip_v?-1:1;let p,u=0,_=0;if(l.cm>0&&(l.ox||l.oy)){const e=-t.angle*Math.PI/180,i=Math.cos(e),s=Math.sin(e);u=l.ox*i-l.oy*s,_=l.ox*s+l.oy*i,_*=d,u*=h}if("window"===t.type){const t=Math.PI/2*e,i=l.cm>0?j``:W;p=j` + x2="${t.x+15*Math.cos(e)}" y2="${t.y+15*Math.sin(e)}">`}_roomCenter(t){if(t.poly){const e=t.poly.length;return[t.poly.reduce((t,e)=>t+e[0],0)/e,t.poly.reduce((t,e)=>t+e[1],0)/e]}return[t.x+t.w/2,t.y+.1*Math.min(t.w,t.h)]}_openingAmt(t){const e=t.contact&&this._planEntityAvailable(t.contact)?this.hass.states[t.contact]?.state:null;return ei(t.type,e,!!t.invert)}_planEntityAvailable(t){return!!t&&(!En(this._fullRegistryHass,t,In(this._markers))&&"active"===this._bindingStatus("entity:"+t).kind)}_renderOpenings(t){const e=this._openingsR;if(!e.length)return W``;const i=t.color,s=this._spaceWalls,n=this._openPairs().flatMap(t=>t.segs),o=this._openingWallIndexFor(this._spaceModel(),n).value;return W`${e.map(t=>{const e=t.rlen/2,n=this._openingAmt(t),r=n>0&&!!t.contact&&this._planEntityAvailable(t.contact)?"var(--hp-open)":i,a="gate"===t.type?!t.flip_v:t.flip_v,l=s.length||"gate"===t.type?ta(o,{x:t.rx,y:t.ry,angle:t.angle,length:t.rlen,flip_v:a}):{ox:0,oy:0,cm:0,side:-1},c=l.cm>0?l.cm/this._cellCm*this._gridPitch/2:4,h=t.flip_h?-1:1,d=t.flip_v?-1:1;let p,u=0,_=0;if(l.cm>0&&(l.ox||l.oy)){const e=-t.angle*Math.PI/180,i=Math.cos(e),s=Math.sin(e);u=l.ox*i-l.oy*s,_=l.ox*s+l.oy*i,_*=d,u*=h}if("window"===t.type){const t=Math.PI/2*e,i=l.cm>0?W``:G;p=W` + stroke="${r}" stroke-dasharray="${t}" stroke-dashoffset="${t*(1-n)}"> + stroke="${r}" stroke-dasharray="${t}" stroke-dashoffset="${t*(1-n)}"> - + - + ${i} - `}else if("gate"===t.type){const t=l.side*d*10*o;p=j` + `}else if("gate"===t.type){const t=l.side*d*10*n;p=W` @@ -4845,16 +4915,16 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 - `}else{const i=t.rlen,s=Math.PI/2*i;p=j` + `}else{const i=t.rlen,s=Math.PI/2*i;p=W` + stroke="${r}" stroke-dasharray="${s}" stroke-dashoffset="${s*(1-n)}"> - + - `}const m="gate"===t.type?Math.sin(10*Math.PI/180)*e:0,g=Math.max(16,c+8,m+8),f=Math.max(20,c+10,m+12);return j``}const m="gate"===t.type?Math.sin(10*Math.PI/180)*e:0,g=Math.max(16,c+8,m+8),f=Math.max(20,c+10,m+12);return W` @@ -4868,25 +4938,25 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 @pointermove=${e=>this._opPointerMove(e,t)} @pointerup=${e=>this._opPointerUp(e,t)} @pointercancel=${e=>this._opPointerUp(e,t)}> - `})}`}_renderOpeningLocks(t){const e=this._openingsR.filter(t=>"window"!==t.type&&t.lock&&this._planEntityAvailable(t.lock));if(!e.length)return B``;const i=this._openPairs().flatMap(t=>t.segs),s=this._openingWallIndexFor(this._spaceModel(),i).value;return B`${e.map(e=>{const i=this.hass.states[e.lock]?.state,o="locked"===i,n=o||["unlocked","open","opening","unlocking","locking"].includes(String(i)),r=(e.angle+90)*Math.PI/180,a="gate"===e.type?ta(s,{x:e.rx,y:e.ry,angle:e.angle,length:e.rlen,flip_v:!e.flip_v}):null,l=a?-16*a.side:16*(e.flip_v?-1:1),c=e.rx+Math.cos(r)*l,h=e.ry+Math.sin(r)*l,d=(c-t.x)/t.w*100,p=(h-t.y)/t.h*100;return B`
`})}`}_renderOpeningLocks(t){const e=this._openingsR.filter(t=>"window"!==t.type&&t.lock&&this._planEntityAvailable(t.lock));if(!e.length)return B``;const i=this._openPairs().flatMap(t=>t.segs),s=this._openingWallIndexFor(this._spaceModel(),i).value;return B`${e.map(e=>{const i=this.hass.states[e.lock]?.state,n="locked"===i,o=n||["unlocked","open","opening","unlocking","locking"].includes(String(i)),r=(e.angle+90)*Math.PI/180,a="gate"===e.type?ta(s,{x:e.rx,y:e.ry,angle:e.angle,length:e.rlen,flip_v:!e.flip_v}):null,l=a?-16*a.side:16*(e.flip_v?-1:1),c=e.rx+Math.cos(r)*l,h=e.ry+Math.sin(r)*l,d=(c-t.x)/t.w*100,p=(h-t.y)/t.h*100;return B`
{t.stopPropagation(),"view"===this._mode&&(this._openingInfo=e)}}> - -
`})}`}_lockAction(t,e){if(this._planEntityAvailable(t)){if("unlock"===e){const e=this.hass?.states?.[t]?.attributes?.friendly_name||t;if(!confirm(this._t("confirm.unlock",{name:e})))return}this.hass?.callService?.("lock",e,{entity_id:t})}}_renderOpeningInfoCard(){const t=this._openingInfo,e=t.contact&&this._planEntityAvailable(t.contact)?t.contact:null,i=t.lock&&this._planEntityAvailable(t.lock)?t.lock:null,s=e?this.hass.states[e]?.state:null,o=this._openingAmt(t),n=i?this.hass.states[i]?.state:null,r="door"===t.type?"opening.door":"gate"===t.type?"opening.gate":"opening.window",a="door"===t.type?"mdi:door":"gate"===t.type?"mdi:gate":"mdi:window-closed-variant",l="gate"===t.type?o>0?"mdi:gate-open":"mdi:gate":o>0?"mdi:door-open":"mdi:door-closed",c=(t,e,i,s="")=>B`
${e}${i}
`;return B` +
`})}`}_lockAction(t,e){if(this._planEntityAvailable(t)){if("unlock"===e){const e=this.hass?.states?.[t]?.attributes?.friendly_name||t;if(!confirm(this._t("confirm.unlock",{name:e})))return}this.hass?.callService?.("lock",e,{entity_id:t})}}_renderOpeningInfoCard(){const t=this._openingInfo,e=t.contact&&this._planEntityAvailable(t.contact)?t.contact:null,i=t.lock&&this._planEntityAvailable(t.lock)?t.lock:null,s=e?this.hass.states[e]?.state:null,n=this._openingAmt(t),o=i?this.hass.states[i]?.state:null,r="door"===t.type?"opening.door":"gate"===t.type?"opening.gate":"opening.window",a="door"===t.type?"mdi:door":"gate"===t.type?"mdi:gate":"mdi:window-closed-variant",l="gate"===t.type?n>0?"mdi:gate-open":"mdi:gate":n>0?"mdi:door-open":"mdi:door-closed",c=(t,e,i,s="")=>B`
${e}${i}
`;return B`this._openingInfo=null}>
- ${e?c(l,this._t("opening.contact_label"),"unavailable"===s||null==s?this._t("opening.state_unknown"):this._t(o>0?"opening.open":"opening.closed"),o>0?"warn":"ok"):W} - ${i?c("locked"===n?"mdi:lock":"mdi:lock-open-variant",this._t("opening.lock_label"),"locked"===n?this._t("opening.locked"):["unlocked","open"].includes(String(n))?this._t("opening.unlocked"):this._t("opening.state_unknown"),"locked"===n?"ok":"warn"):W} - ${i&&("locked"===n||["unlocked","open"].includes(String(n)))?B``:i&&["locking","unlocking"].includes(String(n))?B``:i&&["locking","unlocking"].includes(String(o))?B``:W} - ${e||i?W:B`

${this._t("opening.no_entities")}

`} + `:G} + ${e||i?G:B`

${this._t("opening.no_entities")}

`}
@@ -4912,98 +4982,101 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 {const i=Hc(e.target.value);null!=i&&(this._openingDialog={...t,lengthCm:i})}} /> + @input=${e=>{const i=Vc(e.target.value);null!=i&&(this._openingDialog={...t,lengthCm:i})}} /> ${i(this._contactCandidates(),t.contact,e=>this._openingDialog={...t,contact:e})} ${t.contact?B``:W} + ${this._t("opening.invert")}`:G} ${"window"!==t.type?B` - ${i(this._lockCandidates(),t.lock,e=>this._openingDialog={...t,lock:e})}`:W} + ${i(this._lockCandidates(),t.lock,e=>this._openingDialog={...t,lock:e})}`:G} ${"gate"!==t.type?B``:W} + ${this._t("opening.flip_h")}`:G}
-
- ${t.id?B``:W} - - - + - `}_gridLevels(){const t=this._stageEl,e=this._viewOr(this._baseVb()),i=t&&t.clientWidth&&e.w?t.clientWidth/e.w:1;return function(t,e,i=7){if(!(t>0&&e>0&&Number.isFinite(e)))return null;const s=Ja.find(s=>t*s*e>=i);if(void 0===s)return null;const o=Ja.find(t=>t>=5*s)??5*s;return{fine:s,coarse:o}}(this._gridPitch,i)}_renderMarkupDefs(t){const e=this._gridLevels();if(!e)return j``;const i=this._gridPitch*e.fine,s=this._gridPitch*e.coarse,o=this._gridPitch*e.fine*.14;return j` + `}_gridLevels(){const t=this._stageEl,e=this._viewOr(this._baseVb()),i=t&&t.clientWidth&&e.w?t.clientWidth/e.w:1;return function(t,e,i=7){if(!(t>0&&e>0&&Number.isFinite(e)))return null;const s=Ya.find(s=>t*s*e>=i);if(void 0===s)return null;const n=Ya.find(t=>t>=5*s)??5*s;return{fine:s,coarse:n}}(this._gridPitch,i)}_renderMarkupDefs(t){const e=this._gridLevels();if(!e)return W``;const i=this._gridPitch*e.fine,s=this._gridPitch*e.coarse,n=this._gridPitch*e.fine*.14;return W` - - - - + + + + - - - - + + + + - `}_renderPhysicalEditorLayer(){const t=this._spaceModel(),e=this._gridPitch,i=this._viewOr(this._baseVb()),s=this._stageEl,o=s?.clientWidth?i.w/s.clientWidth:e/8,n=Math.max(.22*e,8*o),r=Math.max(n,12*o),a=t=>`M ${t.map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`,l=(t,e)=>this._physicalSel?.kind===t&&this._physicalSel.id===e||"column"===t&&this._duplicateColumnId===e,c=(t.room_drafts||[]).flatMap(t=>t.points.slice(0,-1).map((e,i)=>{const s=t.points[i+1],n=dr(t.segments[i]?.cm||15,this._cellCm,this._gridPitch);return j``}_renderPhysicalEditorLayer(){const t=this._spaceModel(),e=this._gridPitch,i=this._viewOr(this._baseVb()),s=this._stageEl,n=s?.clientWidth?i.w/s.clientWidth:e/8,o=Math.max(.22*e,8*n),r=Math.max(o,12*n),a=t=>`M ${t.map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`,l=(t,e)=>this._physicalSel?.kind===t&&this._physicalSel.id===e||"column"===t&&this._duplicateColumnId===e,c=(t.room_drafts||[]).flatMap(t=>t.points.slice(0,-1).map((e,i)=>{const s=t.points[i+1],o=dr(t.segments[i]?.cm||15,this._cellCm,this._gridPitch);return W`{this._physicalSel={kind:"draft",id:t.id,segment:i},this._registerPhysicalTap("draft",t.id,i)}}>`})),h=(t.partitions||[]).map(t=>{const e=ca(t.a,t.b,t.cm,this._cellCm,this._gridPitch);return e?j`{this._physicalSel={kind:"draft",id:t.id,segment:i},this._registerPhysicalTap("draft",t.id,i)}}>`})),h=(t.partitions||[]).map(t=>{const e=ca(t.a,t.b,t.cm,this._cellCm,this._gridPitch);return e?W`this._physicalDown(e,"partition",t.id)} @pointermove=${t=>this._physicalMove(t)} - @pointerup=${t=>this._physicalUp(t)}>`:W}),d=(t.wall_columns||[]).map(t=>{const e=ha(this._physicalRotate?.id===t.id?{...t,angle:this._physicalRotate.angle}:t,this._cellCm,this._gridPitch);return j`this._physicalUp(t)}>`:G}),d=(t.wall_columns||[]).map(t=>{const e=ha(this._physicalRotate?.id===t.id?{...t,angle:this._physicalRotate.angle}:t,this._cellCm,this._gridPitch);return W`this._physicalDown(e,"column",t.id)} @pointermove=${t=>this._physicalMove(t)} - @pointerup=${t=>this._physicalUp(t)}>`}),p=this._physicalDrag,u=(()=>{if(!p?.moved)return W;const t="partition"===p.kind?ca(p.base.a,p.base.b,p.base.cm,this._cellCm,this._gridPitch):ha(p.base,this._cellCm,this._gridPitch);return t?j``:W})(),_=(()=>{const i=this._physicalSel;if(!i)return W;if("draft"===i.kind){const e=t.room_drafts.find(t=>t.id===i.id);return e?j` + @pointerup=${t=>this._physicalUp(t)}>`}),p=this._physicalDrag,u=(()=>{if(!p?.moved)return G;const t="partition"===p.kind?ca(p.base.a,p.base.b,p.base.cm,this._cellCm,this._gridPitch):ha(p.base,this._cellCm,this._gridPitch);return t?W``:G})(),_=(()=>{const i=this._physicalSel;if(!i)return G;if("draft"===i.kind){const e=t.room_drafts.find(t=>t.id===i.id);return e?W` t.join(",")).join(" ")}> - ${e.points.map(t=>j``)} - `:W}if("partition"===i.kind){const e=t.partitions.find(t=>t.id===i.id);if(!e)return W;const s=ca(e.a,e.b,e.cm,this._cellCm,this._gridPitch);if(!s)return W;const o=(e.a[0]+e.b[0])/2,r=(e.a[1]+e.b[1])/2;return j` + ${e.points.map(t=>W``)} + `:G}if("partition"===i.kind){const e=t.partitions.find(t=>t.id===i.id);if(!e)return G;const s=ca(e.a,e.b,e.cm,this._cellCm,this._gridPitch);if(!s)return G;const n=(e.a[0]+e.b[0])/2,r=(e.a[1]+e.b[1])/2;return W` - - - - `}const s=t.wall_columns.find(t=>t.id===i.id);if(!s)return W;const l=this._physicalRotate?.id===s.id?{...s,angle:this._physicalRotate.angle}:s,c=ha(l,this._cellCm,this._gridPitch);if("square"!==l.shape)return j` + + + + `}const s=t.wall_columns.find(t=>t.id===i.id);if(!s)return G;const l=this._physicalRotate?.id===s.id?{...s,angle:this._physicalRotate.angle}:s,c=ha(l,this._cellCm,this._gridPitch);if("square"!==l.shape)return W` - - `;const h=dr(l.cm,this._cellCm,this._gridPitch),d=ra(l.angle)*Math.PI/180,p=Math.sin(d),u=-Math.cos(d),_=[l.center[0]+p*h/2,l.center[1]+u*h/2],m=[_[0]+p*Math.max(e,24*o),_[1]+u*Math.max(e,24*o)];return j` + + `;const h=dr(l.cm,this._cellCm,this._gridPitch),d=ra(l.angle)*Math.PI/180,p=Math.sin(d),u=-Math.cos(d),_=[l.center[0]+p*h/2,l.center[1]+u*h/2],m=[_[0]+p*Math.max(e,24*n),_[1]+u*Math.max(e,24*n)];return W` - + this._physicalRotateDown(t,s)} @pointermove=${t=>this._physicalRotateMove(t)} @pointerup=${t=>this._physicalRotateUp(t)}> - `})();return j`${c}${h}${d}${u}${_}`}_renderMarkupLayer(t){const e=this._openPairs().flatMap(t=>t.segs),i=this._thickWallCuts(),s=e.concat(i),o=s.length?_s(this._segments,s,.02*this._gridPitch):this._segments,n=this._path,r=this._gridPitch,a=this._viewOr(this._baseVb()),l="draw"===this._tool||"partition"===this._tool?this._drawWallCm:null,c=(()=>"draw"!==this._tool&&"partition"!==this._tool||!n.length||!(null!=l&&l>0)?null:this._contourClosed?n:this._cursorPt?[...n,this._cursorPt]:n.length>=2?n:null)(),h=c?Dr(c,dr(l,this._cellCm,this._gridPitch)/2,this._contourClosed):"";return j` - ${this._gridLevels()?j``:W} - ${o.map(t=>j``)} + `})();return W`${c}${h}${d}${u}${_}`}_renderMarkupLayer(t){const e=this._openPairs().flatMap(t=>t.segs),i=this._thickWallCuts(),s=e.concat(i),n=s.length?_s(this._segments,s,.02*this._gridPitch):this._segments,o=this._path,r=this._gridPitch,a=this._viewOr(this._baseVb()),l="draw"===this._tool||"partition"===this._tool?this._drawWallCm:null,c=(()=>"draw"!==this._tool&&"partition"!==this._tool||!o.length||!(null!=l&&l>0)?null:this._contourClosed?o:this._cursorPt?[...o,this._cursorPt]:o.length>=2?o:null)(),h=c?Dr(c,dr(l,this._cellCm,this._gridPitch)/2,this._contourClosed):"";return W` + ${this._gridLevels()?W``:G} + ${n.map(t=>W``)} ${this._renderPhysicalEditorLayer()} - ${"column"===this._tool&&this._cursorPt&&this._drawWallCm?j``M ${ha({shape:"square",center:this._cursorPt,cm:this._drawWallCm},this._cellCm,this._gridPitch).map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`)()}>`:W} - ${h?j` - `:W} - ${n.length>1?j``:W} - ${!n.length||!this._cursorPt||"draw"!==this._tool&&"partition"!==this._tool||this._contourClosed?W:j``M ${ha({shape:"square",center:this._cursorPt,cm:this._drawWallCm},this._cellCm,this._gridPitch).map(t=>`${t[0]} ${t[1]}`).join(" L ")} Z`)()}>`:G} + ${h?W` + `:G} + ${o.length>1?W``:G} + ${!o.length||!this._cursorPt||"draw"!==this._tool&&"partition"!==this._tool||this._contourClosed?G:W``} - ${n.map((t,e)=>j``)} - ${(()=>{const t=this._openingPreview;if(!t)return W;const e=t.angle*Math.PI/180,i=Math.cos(e)*t.rlen/2,s=Math.sin(e)*t.rlen/2;return j`W``)} + ${(()=>{const t=this._openingPreview;if(!t)return G;const e=t.angle*Math.PI/180,i=Math.cos(e)*t.rlen/2,s=Math.sin(e)*t.rlen/2;return W` `})()} - ${"split"===this._tool&&this._splitSel?.pts?.length?j`${this._splitSel.pts.length>1?j``:W} - ${this._splitSel.pts.map((t,e)=>j``)} - ${this._cursorPt?j``:W}`:W} + ${"split"===this._tool&&this._splitSel?.pts?.length?W`${this._splitSel.pts.length>1?W``:G} + ${this._splitSel.pts.map((t,e)=>W``)} + ${this._cursorPt?W``:G}`:G} `}_renderPhysicalDialog(){const t=this._physicalDialog,e="column"===t.kind;return B`{const i=e.target.value;this._physicalDialog={...t,shape:i}}}> - `:W} + `:G}
this._physicalDialog={...t,angle:e.target.value}} />`:W} - ${t.length?B`
${this._t("physical.length")}: ${t.length}
`:W} + @input=${e=>this._physicalDialog={...t,angle:e.target.value}} />`:G} + ${t.length?B`
${this._t("physical.length")}: ${t.length}
`:G}
-
`}_renderMarkupBar(){const t=this._geometryHistory.undoName,e=this._geometryHistory.redoName,i="boundary"===this._tool&&!!this._openWallAnchor,s=i?this._t("markup.boundary_cancel"):t?this._t("history.undo_named",{name:t}):this._t("history.undo_empty"),o=i?this._t("markup.boundary_cancel"):e?this._t("history.redo_named",{name:e}):this._t("history.redo_empty");return B`
+ `}_renderMarkupBar(){const t=this._geometryHistory.undoName,e=this._geometryHistory.redoName,i="boundary"===this._tool&&!!this._openWallAnchor,s=i?this._t("markup.boundary_cancel"):t?this._t("history.undo_named",{name:t}):this._t("history.undo_empty"),n=i?this._t("markup.boundary_cancel"):e?this._t("history.redo_named",{name:e}):this._t("history.redo_empty");return B`
@@ -5116,7 +5189,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
@@ -5147,33 +5220,33 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
-
`}_cardEntities(t){const e=this._planHass,i=[],s=new Set,o=t=>{if(!t||s.has(t)||!e.states[t])return;const o=e.entities[t];if("config"===o?.entity_category||"diagnostic"===o?.entity_category)return;s.add(t);const n=t.split(".")[0];["light","switch","fan","humidifier","siren","input_boolean"].includes(n)?i.push({eid:t,kind:"toggle"}):["cover","valve","lock","climate","media_player","vacuum","water_heater"].includes(n)?i.push({eid:t,kind:"open"}):["sensor","binary_sensor","number","select"].includes(n)&&i.push({eid:t,kind:"value"})};for(const i of xo(e,[t]))o(i.eid);t.primary&&o(t.primary);for(const e of t.entities)o(e);return i.slice(0,12)}_cardToggle(t){const e=t.split(".")[0];"lock"!==e&&"alarm_control_panel"!==e&&this._planEntityAvailable(t)&&this.hass.callService("homeassistant","toggle",{entity_id:t}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}_renderInfoCard(){const t=this._infoCard,e=t.primary?this.hass.states[t.primary]:void 0,i=e?Oi(this.hass,t.primary)?.text??e.state:null,s=(t.controls??t.marker?.controls??[]).filter(ps).filter(t=>this._planEntityAvailable(t));return B``}_cardEntities(t){const e=this._planHass,i=[],s=new Set,n=t=>{if(!t||s.has(t)||!e.states[t])return;const n=e.entities[t];if("config"===n?.entity_category||"diagnostic"===n?.entity_category)return;s.add(t);const o=t.split(".")[0];["light","switch","fan","humidifier","siren","input_boolean"].includes(o)?i.push({eid:t,kind:"toggle"}):["cover","valve","lock","climate","media_player","vacuum","water_heater"].includes(o)?i.push({eid:t,kind:"open"}):["sensor","binary_sensor","number","select"].includes(o)&&i.push({eid:t,kind:"value"})};for(const i of xn(e,[t]))n(i.eid);t.primary&&n(t.primary);for(const e of t.entities)n(e);return i.slice(0,12)}_cardToggle(t){const e=t.split(".")[0];"lock"!==e&&"alarm_control_panel"!==e&&this._planEntityAvailable(t)&&this.hass.callService("homeassistant","toggle",{entity_id:t}).catch(t=>this._showToast(this._t("toast.error",{err:this._errText(t)})))}_renderInfoCard(){const t=this._infoCard,e=t.primary?this.hass.states[t.primary]:void 0,i=e?Oi(this.hass,t.primary)?.text??e.state:null,s=(t.controls??t.marker?.controls??[]).filter(ps).filter(t=>this._planEntityAvailable(t));return B`
${(()=>{const e=this._cardEntities(t);return e.length?B`
- ${e.map(({eid:t,kind:e})=>{const i=this.hass.states[t],s=this.hass.entities[t]?.name||i?.attributes?.friendly_name||t,o=i?Oi(this.hass,t)?.text??i.state:"",n="on"===i?.state||["open","unlocked","playing","cleaning"].includes(i?.state);return B`
- + ${e.map(({eid:t,kind:e})=>{const i=this.hass.states[t],s=this.hass.entities[t]?.name||i?.attributes?.friendly_name||t,n=i?Oi(this.hass,t)?.text??i.state:"",o="on"===i?.state||["open","unlocked","playing","cleaning"].includes(i?.state);return B`
+ ${s} - ${"toggle"===e?B``:"open"===e?B``:B`${o}`} + ${"toggle"===e?B``:"open"===e?B``:B`${n}`}
`})} -
`:W})()} - ${t.model?B`
${this._t("info.model")}${t.model}
`:W} - ${i&&!this._cardEntities(t).length?B`
${this._t("info.state")}${i}
`:W} +
`:G})()} + ${t.model?B`
${this._t("info.model")}${t.model}
`:G} + ${i&&!this._cardEntities(t).length?B`
${this._t("info.state")}${i}
`:G} ${$i(t.link)?B`
${this._t("info.link")} - ${t.link}
`:W} - ${t.description?B`
${t.description}
`:W} + ${t.link}
`:G} + ${t.description?B`
${t.description}
`:G} ${t.pdfs&&t.pdfs.length?B`
${this._t("info.manuals")} ${t.pdfs.map(t=>B` - ${t.name}`)}
`:W} + ${t.name}`)}
`:G} ${s.length?B`
${this._t("info.controls")} ${s.map(t=>{const e=this.hass.states[t],i="on"===e?.state;return B` ${e?.attributes?.friendly_name||t}`})} -
`:W} - ${t.model||i||t.link||t.description||t.pdfs&&t.pdfs.length||s.length?W:B`
${this._t("info.none")}
`} +
`:G} + ${t.model||i||t.link||t.description||t.pdfs&&t.pdfs.length||s.length?G:B`
${this._t("info.none")}
`}
${t.primary?B``:W} + `:G}
- `}_markerDraft(t){if("ha"===t.bindingMode&&(!t.binding||"virtual"===t.binding))return null;const e=Ds(t.room),i=yi(t.binding,t.devId,()=>"__hp_device_preview__"),s=this._markers.find(e=>e.id===i||e.id===t.devId),o=yo(t.binding,t.controls,this._bindingEntities(t.binding)),n={id:i,binding:t.binding,name:t.name.trim()||null,icon:t.icon||null,display:"badge"!==t.display?t.display:null,ripple_color:"icon_ripple"===t.display&&t.rippleColor?t.rippleColor:null,ripple_size:"icon_ripple"===t.display&&3!==t.rippleSize?t.rippleSize:null,size:1!==t.size?t.size:null,angle:t.angle||null,tap_action:t.tapAction||null,tap_target:"run"===t.tapAction&&t.tapTarget||null,tap_confirm:!!t.tapConfirm||null,controls:o.length?o:null,...this._markerLightFields(t),use_climate_temp:!!t.useClimateTemp||null,glow_radius_cm:(()=>{const e=Hc(t.glowRadius);return null==e||e<=0?null:Math.round(this._imperial?30.48*e:100*e)})(),model:t.model.trim()||null,link:t.link.trim()||null,description:t.description.trim()||null,pdfs:t.pdfs,hidden:t.hideFromPlan,vacuum:s?.vacuum||null};return("virtual"===t.binding||t.room)&&(n.space=e?.space||("virtual"===t.binding?this._space:null),n.area=e?.area||null,n.room_id=e?.roomId||null),n}_markerPreviewDevice(t){const e=this._markerDraft(t);if(!e)return null;const i=`${this._haRegistry.revision}\n${JSON.stringify(e)}`;if(this._markerPreviewMemo?.key===i)return this._markerPreviewMemo.device;const s=function(t){const{marker:e,...i}=t;return Fo({...i,markers:[e]}).find(t=>t.id===e.id)||null}({hass:this.hass,registry:this._haRegistry,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),marker:e,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||this._space,loc:t=>this._t(t),iconRules:this._iconRules});return this._markerPreviewMemo={key:i,device:s},s}_markerSpatialSource(t){const e=this._markerPreviewDevice(t);return e?So(xo(this._planHass,[{...e,hidden:!1}])):null}_markerAutoHasSpatialSource(t){const e={...t,lightRole:"auto",lightRoleTouched:!0},i=this._markerPreviewDevice(e);return!!i&&ko(this._planHass,{...i})}_setMarkerLightRole(t){const e=this._markerDialog;e&&(this._markerDialog={...e,lightRole:t,lightRoleTouched:!0})}_setMarkerGlowMode(t){const e=this._markerDialog;if(!e)return;if("auto"===t)return void(this._markerDialog={...e,glowMode:t,glowTouched:!0});const i=this._markerSpatialSource(e),s=cs(i?this._planHass.states[i.eid]:void 0,null,this._fillColors.glow_light.c),o=!e.glowColorDrafted,n="fixed"===t&&!e.glowBrightnessDrafted;this._markerDialog={...e,glowMode:t,glowColor:o?s.c:e.glowColor,glowBrightness:n?Math.max(1,Math.round(100*s.bri)):e.glowBrightness,glowColorDrafted:!0,glowBrightnessDrafted:e.glowBrightnessDrafted||"fixed"===t,glowTouched:!0}}_renderMarkerDialog(){const t=this._markerDialog,e="virtual"===t.bindingMode,i=this._bindingCandidates(),s=this._bindingEntities(t.binding),o=e?null:this._bindingStatus(t.binding),n=!e&&this._bindingHasHaPage(t.binding),r=this._markerPreviewDevice(t),a=r?Yi(this._serverCfg?.spaces.find(t=>t.id===r.space)):null,l=r?Zo(this._planHass,r,{liveStates:!1!==this._config?.live_states,showTemperature:!1!==this._config?.show_temperature,showSignal:a?.showLqi??!1!==this._config?.show_signal,designPreview:!0,activityRuntime:this._activityRt.get(r.id)}):null,c=this._markerAutoHasSpatialSource(t),h=this._markerSpatialSource(t),d="never"===t.lightRole||!h,p="never"===t.lightRole?this._t("marker.glow_disabled_never"):"auto"!==t.lightRole||c?this._t("marker.glow_disabled_no_entity"):this._t("marker.glow_disabled_auto"),u=(()=>{if(e)return null;const s=i.find(e=>e.value===t.binding);if(s)return s.label;const[o,n]=t.binding.split(":");return"device"===o?this._fullRegistryHass.devices[n]?.name_by_user||this._fullRegistryHass.devices[n]?.name||n:this._fullRegistryHass.entities[n]?.name||this.hass.states[n]?.attributes?.friendly_name||n})();return B``}_markerDraft(t){if("ha"===t.bindingMode&&(!t.binding||"virtual"===t.binding))return null;const e=Ds(t.room),i=yi(t.binding,t.devId,()=>"__hp_device_preview__"),s=this._markers.find(e=>e.id===i||e.id===t.devId),n=yn(t.binding,t.controls,this._bindingEntities(t.binding)),o={id:i,binding:t.binding,name:t.name.trim()||null,icon:t.icon||null,display:"badge"!==t.display?t.display:null,ripple_color:"icon_ripple"===t.display&&t.rippleColor?t.rippleColor:null,ripple_size:"icon_ripple"===t.display&&3!==t.rippleSize?t.rippleSize:null,size:1!==t.size?t.size:null,angle:t.angle||null,tap_action:t.tapAction||null,tap_target:"run"===t.tapAction&&t.tapTarget||null,tap_confirm:!!t.tapConfirm||null,controls:n.length?n:null,...this._markerLightFields(t),use_climate_temp:!!t.useClimateTemp||null,glow_radius_cm:(()=>{const e=Vc(t.glowRadius);return null==e||e<=0?null:Math.round(this._imperial?30.48*e:100*e)})(),model:t.model.trim()||null,link:t.link.trim()||null,description:t.description.trim()||null,pdfs:t.pdfs,hidden:t.hideFromPlan,vacuum:s?.vacuum||null};return("virtual"===t.binding||t.room)&&(o.space=e?.space||("virtual"===t.binding?this._space:null),o.area=e?.area||null,o.room_id=e?.roomId||null),o}_markerPreviewDevice(t){const e=this._markerDraft(t);if(!e)return null;const i=`${this._haRegistry.revision}\n${JSON.stringify(e)}`;if(this._markerPreviewMemo?.key===i)return this._markerPreviewMemo.device;const s=function(t){const{marker:e,...i}=t;return Nn({...i,markers:[e]}).find(t=>t.id===e.id)||null}({hass:this.hass,registry:this._haRegistry,areaToSpace:Object.fromEntries(Object.entries(this._areaToSpace).map(([t,e])=>[t,e.space])),marker:e,settings:this._settings,excluded:this._excluded,showAll:this._showAll,firstSpaceId:this._model[0]?.id||this._space,loc:t=>this._t(t),iconRules:this._iconRules});return this._markerPreviewMemo={key:i,device:s},s}_markerSpatialSource(t){const e=this._markerPreviewDevice(t);return e?Sn(xn(this._planHass,[{...e,hidden:!1}])):null}_markerAutoHasSpatialSource(t){const e={...t,lightRole:"auto",lightRoleTouched:!0},i=this._markerPreviewDevice(e);return!!i&&kn(this._planHass,{...i})}_setMarkerLightRole(t){const e=this._markerDialog;e&&(this._markerDialog={...e,lightRole:t,lightRoleTouched:!0})}_setMarkerGlowMode(t){const e=this._markerDialog;if(!e)return;if("auto"===t)return void(this._markerDialog={...e,glowMode:t,glowTouched:!0});const i=this._markerSpatialSource(e),s=cs(i?this._planHass.states[i.eid]:void 0,null,this._fillColors.glow_light.c),n=!e.glowColorDrafted,o="fixed"===t&&!e.glowBrightnessDrafted;this._markerDialog={...e,glowMode:t,glowColor:n?s.c:e.glowColor,glowBrightness:o?Math.max(1,Math.round(100*s.bri)):e.glowBrightness,glowColorDrafted:!0,glowBrightnessDrafted:e.glowBrightnessDrafted||"fixed"===t,glowTouched:!0}}_renderMarkerDialog(){const t=this._markerDialog,e="virtual"===t.bindingMode,i=this._bindingCandidates(),s=this._bindingEntities(t.binding),n=e?null:this._bindingStatus(t.binding),o=!e&&this._bindingHasHaPage(t.binding),r=this._markerPreviewDevice(t),a=r?Yi(this._serverCfg?.spaces.find(t=>t.id===r.space)):null,l=r?Zn(this._planHass,r,{liveStates:!1!==this._config?.live_states,showTemperature:!1!==this._config?.show_temperature,showSignal:a?.showLqi??!1!==this._config?.show_signal,designPreview:!0,activityRuntime:this._activityRt.get(r.id)}):null,c=this._markerAutoHasSpatialSource(t),h=this._markerSpatialSource(t),d="never"===t.lightRole||!h,p="never"===t.lightRole?this._t("marker.glow_disabled_never"):"auto"!==t.lightRole||c?this._t("marker.glow_disabled_no_entity"):this._t("marker.glow_disabled_auto"),u=(()=>{if(e)return null;const s=i.find(e=>e.value===t.binding);if(s)return s.label;const[n,o]=t.binding.split(":");return"device"===n?this._fullRegistryHass.devices[o]?.name_by_user||this._fullRegistryHass.devices[o]?.name||o:this._fullRegistryHass.entities[o]?.name||this.hass.states[o]?.attributes?.friendly_name||o})();return B`this._markerDialog=null}>
- ${"ha_disabled"===o?.kind?B`
+ ${"ha_disabled"===n?.kind?B`
- ${this._t(`marker.ha_disabled_${o.reason}`)} - ${n?B``:W} -
`:"unverified"===o?.kind&&t.binding?B`
+ `:G} +
`:"unverified"===n?.kind&&t.binding?B`
${this._t("marker.ha_registry_limited")} -
`:W} +
`:G}
@@ -5223,7 +5296,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
${"ha"===t.bindingMode?B` ${t.bindingOpen?B`
@@ -5232,12 +5305,12 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 @input=${e=>this._markerDialog={...t,bindingFilter:e.target.value}} />
${i.map(e=>B`
this._markerDialog={...t,binding:e.value,bindingOpen:!1,controls:vo(e.value,t.controls,this._bindingEntities(e.value)),autoIcon:this._autoIconForBinding(e.value)}}> + @click=${()=>this._markerDialog={...t,binding:e.value,bindingOpen:!1,controls:vn(e.value,t.controls,this._bindingEntities(e.value)),autoIcon:this._autoIconForBinding(e.value)}}> ${e.label}${e.sub}
`)} - ${i.length?W:B`
${this._t("marker.nothing_found")}
`} + ${i.length?G:B`
${this._t("marker.nothing_found")}
`}
-
`:W}`:W} +
`:G}`:G}
@@ -5256,22 +5329,22 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 ${"run"===t.tapAction?(()=>{const e=t.runFilter.trim().toLowerCase(),i=this._runCandidates().filter(t=>!e||t.label.toLowerCase().includes(e)||t.value.includes(e)),s=t.tapTarget?this._runCandidates().find(e=>e.value===t.tapTarget):null;return B` - ${t.tapTarget&&!s?B`
${this._t("marker.run_target_gone",{id:t.tapTarget})}
`:W} + ${t.tapTarget&&!s?B`
${this._t("marker.run_target_gone",{id:t.tapTarget})}
`:G} {t.target.select()}} @input=${e=>this._markerDialog={...t,runFilter:e.target.value,tapTarget:""}} /> - ${s?W:B`
+ ${s?G:B`
${i.slice(0,40).map(e=>B`
this._markerDialog={...t,tapTarget:e.value,runFilter:""}}> ${e.label}${e.sub}
`)} - ${i.length?W:B`
${this._t("marker.nothing_found")}
`} -
`}`})():W} + ${i.length?G:B`
${this._t("marker.nothing_found")}
`} +
`}`})():G} ${"run"===t.tapAction||"toggle"===t.tapAction||"cover"===t.tapAction||!t.tapAction&&"toggle"===t.defaultTap?B``:W} + `:G}
${this._t("marker.controls_hint")}
@@ -5280,23 +5353,23 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 ${this.hass.states[e]?.attributes?.friendly_name||e} this._markerDialog={...t,controls:t.controls.filter(t=>t!==e)}}> `)} - `:W} + `:G} this._markerDialog={...t,controlsFilter:e.target.value}} /> ${t.controlsFilter.trim()?B`
- ${Object.keys(this.hass.states).filter(e=>vo(t.binding,[e],s).length>0&&!t.controls.includes(e)&&this._planEntityAvailable(e)).filter(e=>{const i=t.controlsFilter.trim().toLowerCase(),s=String(this.hass.states[e]?.attributes?.friendly_name||"");return e.toLowerCase().includes(i)||s.toLowerCase().includes(i)}).slice(0,8).map(e=>B``)} -
`:W} + `:G} ${this._bindingHasClimate(t.binding)?B``:W} + `:G}
${this._t("marker.light_role_label")}

${this._t("marker.light_role_tip")}

@@ -5314,7 +5387,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
+ aria-describedby=${d?"marker-glow-disabled-hint":G}> ${this._t("marker.glow_color_label")}

${this._t("marker.glow_color_tip")}

@@ -5339,9 +5412,9 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 ${"fixed"===t.glowMode?B` ${this._t("marker.glow_brightness")} ${this._rangeInput(1,100,1,t.glowBrightness,e=>{this._markerDialog={...t,glowBrightness:e,glowBrightnessDrafted:!0,glowTouched:!0}},d,this._t("marker.glow_brightness"))} - ${Math.round(t.glowBrightness)}%`:W} -
`:W} - ${"fixed"===t.glowMode?B`

${this._t("marker.glow_brightness_hint")}

`:W} + ${Math.round(t.glowBrightness)}%`:G} + `:G} + ${"fixed"===t.glowMode?B`

${this._t("marker.glow_brightness_hint")}

`:G}
${p} -

`:W} +

`:G} ${customElements.get("ha-icon-picker")?B`this._markerDialog={...t,icon:t.autoIcon}}> ${this._t("marker.icon_pin_auto")} -

`:W} +

`:G} this._spaceDialog={...t,source:"draw"}} /> @@ -5496,9 +5569,9 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96
- {const i=Hc(e.target.value);this._spaceDialog={...t,cellCm:null!=i&&i>0?Math.max(.1,Math.min(Fc,i)):t.cellCm}}} /> + @input=${e=>{const i=Vc(e.target.value);this._spaceDialog={...t,cellCm:null!=i&&i>0?Math.max(.1,Math.min(jc,i)):t.cellCm}}} /> ${this._t("space.scale_unit")}
@@ -5552,20 +5625,20 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 - ${"static"===(t.bgMode??Wn(this._settings,{}))?B` + ${"static"===(t.bgMode??Go(this._settings,{}))?B`
this._spaceDialog={...t,bgColor:e.target.value}} /> ${t.bgColor?B``:B`${this._t("space.bg_inherited")}`} -
`:W} +
`:G}
{const i=e.target.value.trim(),s=""===i?null:Math.round(Number(i));this._spaceDialog={...t,northDeg:null!==s&&Number.isFinite(s)?Math.min(359,Math.max(0,s)):null}}} /> - ${null===t.northDeg?this._t("space.north_inherited",{v:null===Gn(this._settings,{})?"—":String(Gn(this._settings,{}))+"°"}):"°"} + ${null===t.northDeg?this._t("space.north_inherited",{v:null===jo(this._settings,{})?"—":String(jo(this._settings,{}))+"°"}):"°"}
{const i=Hc(e.target.value);null!=i&&(this._spaceDialog={...t,tempMin:i})}} /> + @input=${e=>{const i=Vc(e.target.value);null!=i&&(this._spaceDialog={...t,tempMin:i})}} /> – {const i=Hc(e.target.value);null!=i&&(this._spaceDialog={...t,tempMax:i})}} /> + @input=${e=>{const i=Vc(e.target.value);null!=i&&(this._spaceDialog={...t,tempMax:i})}} /> °C - `:W} + `:G} ${"custom"===e&&"custom"===t.fillMode?B`
${this._t("space.custom_fill")} {this._spaceDialog={...t,customFill:{c:e.detail.color,a:e.detail.opacity}}}}> ${t.customFill?B``:W} -
`:W}`)} + ${this._t("btn.reset")}`:G} + `:G}`)} -
- ${"edit"===t.mode?B``:W} - - ${this._importTotal>0&&"create"===t.mode?B``:W} - - + - `}_renderMergeDialog(){const t=this._mergeDialog,e=this._spaceModel().rooms,i=(i,s)=>{const o=e.find(t=>t.id===i),n=o?.area?this.hass.areas[o.area]?.name:null;return B`
- `:W}`:W}`}_renderRoomDialog(){const t=!!this._roomEditId,e=!!this._areaSel||!!this._nameSel.trim(),i=Yi(this._curSpaceCfg),s=this._roomFill||i.fill,o=this._roomCustomFill||i.customFill,n=[...this._freeAreas];if(t&&this._areaSel&&!n.some(t=>t.area_id===this._areaSel)){const t=this.hass.areas[this._areaSel];t&&n.unshift(t)}return B``:G}`:G}`}_renderRoomDialog(){const t=!!this._roomEditId,e=!!this._areaSel||!!this._nameSel.trim(),i=Yi(this._curSpaceCfg),s=this._roomFill||i.fill,n=this._roomCustomFill||i.customFill,o=[...this._freeAreas];if(t&&this._areaSel&&!o.some(t=>t.area_id===this._areaSel)){const t=this.hass.areas[this._areaSel];t&&o.unshift(t)}return B`
@@ -5684,7 +5760,7 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 @@ -5699,11 +5775,11 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 {this._roomCustomFill={c:t.detail.color,a:t.detail.opacity}}}> - ${this._roomCustomFill?B``:W} -
`:W} + ${this._roomCustomFill?B``:G} + `:G} ${this._renderRoomSource("temp")} ${this._renderRoomSource("hum")} @@ -5725,11 +5801,11 @@ globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="258d82cce98d0b96a77008ecb76a90516f96 ${t?B``:B`${this._pendingSplit?W:B``:B`${this._pendingSplit?G:B``} `} -
`}}ch.properties={_hdrH:{state:!0},_booting:{state:!0},_bootFading:{state:!0},_bootSoft:{state:!0},_tapConfirm:{state:!0},hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_hoverRoom:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_wallDialog:{state:!0},_drawWallField:{state:!0},_activeDraftId:{state:!0},_physicalSel:{state:!0},_physicalDialog:{state:!0},_physicalDrag:{state:!0},_physicalRotate:{state:!0},_duplicateColumnId:{state:!0},_rszSel:{state:!0},_rszLive:{state:!0},_opMeasure:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_openWallAnchor:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorEraseConfirm:{state:!0},_decorTextDialog:{state:!0},_decorShapeDialog:{state:!0},_backdropDialog:{state:!0},_furnPalette:{state:!0},_bdDrag:{state:!0},_dtBox:{state:!0},_dtDrag:{state:!0},_kioskDialog:{state:!0},_vacFit:{state:!0},_vacAllCamerasFor:{state:!0},_vacCalConfirm:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomCustomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_alignDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},ch.ZOOM_MAX=8,ch.ZOOM_MIN=1/3,ch._touchSeen=!1,ch._noHoverMq="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,ch.styles=[At,mc],customElements.get("houseplan-card")||customElements.define("houseplan-card",ch),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info(`%c HOUSEPLAN-CARD %c v${Ec} `,"background:#3ea6ff;color:#04121f;font-weight:700",""); +
`}}uh.properties={_hdrH:{state:!0},_booting:{state:!0},_bootFading:{state:!0},_bootSoft:{state:!0},_continuityEpoch:{state:!0},_tapConfirm:{state:!0},hass:{attribute:!1},_config:{state:!0},_space:{state:!0},_layout:{state:!0},_devices:{state:!0},_tip:{state:!0},_hoverRoom:{state:!0},_selId:{state:!0},_toast:{state:!0},_serverCfg:{state:!0},_mode:{state:!0},_tool:{state:!0},_wallDialog:{state:!0},_drawWallField:{state:!0},_activeDraftId:{state:!0},_physicalSel:{state:!0},_physicalDialog:{state:!0},_physicalDrag:{state:!0},_physicalRotate:{state:!0},_duplicateColumnId:{state:!0},_rszSel:{state:!0},_rszLive:{state:!0},_opMeasure:{state:!0},_path:{state:!0},_cursorPt:{state:!0},_mergeSel:{state:!0},_openingDialog:{state:!0},_openingInfo:{state:!0},_mergeDialog:{state:!0},_openWallAnchor:{state:!0},_splitSel:{state:!0},_decorTool:{state:!0},_decorStyle:{state:!0},_decorDraft:{state:!0},_decorSel:{state:!0},_decorEraseConfirm:{state:!0},_decorTextDialog:{state:!0},_decorShapeDialog:{state:!0},_backdropDialog:{state:!0},_furnPalette:{state:!0},_bdDrag:{state:!0},_dtBox:{state:!0},_dtDrag:{state:!0},_kioskDialog:{state:!0},_vacFit:{state:!0},_vacAllCamerasFor:{state:!0},_vacCalConfirm:{state:!0},_kioskDots:{state:!0},_areaSel:{state:!0},_nameSel:{state:!0},_roomDialog:{state:!0},_roomEditId:{state:!0},_roomFill:{state:!0},_roomCustomFill:{state:!0},_roomTempSrc:{state:!0},_roomHumSrc:{state:!0},_roomSrcOpen:{state:!0},_roomSrcFilter:{state:!0},_roomNameScale:{state:!0},_roomLabelScale:{state:!0},_spaceDialog:{state:!0},_infoCard:{state:!0},_rulesDialog:{state:!0},_settingsDialog:{state:!0},_alignDialog:{state:!0},_importDialog:{state:!0},_markerDialog:{state:!0},_zoom:{state:!0},_view:{state:!0}},uh.ZOOM_MAX=8,uh.ZOOM_MIN=1/3,uh._touchSeen=!1,uh._noHoverMq="undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(hover: none)").matches,uh.styles=[At,$c],customElements.get("houseplan-card")||customElements.define("houseplan-card",uh),window.customCards=window.customCards||[],window.customCards.find(t=>"houseplan-card"===t.type)||window.customCards.push({type:"houseplan-card",name:"House Plan Card",description:"Interactive house plan: spaces, rooms and devices with live states and drag layout."}),console.info(`%c HOUSEPLAN-CARD %c v${Bc} `,"background:#3ea6ff;color:#04121f;font-weight:700",""); diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index b41105af..59f90243 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -552,6 +552,29 @@ out must not queue the same url again, a failure backs off rather than retrying on the next frame, and an in-flight entry expires after `SIGN_INFLIGHT_MS` so a promise that never settles cannot block retries forever. +**Visual continuity is a frame contract, not a loading screen** (#73). +`src/visual-continuity.ts` owns one tokenised state machine shared by the full +and static cards. A complete frame remains mounted during resume, reconnect, +structural revalidation and positive-size changes; `0×0` observations never +change the viewport. Config and layout carry independent revision plus +content-fingerprint identities, so revision-only echoes preserve authoritative +objects and geometry caches while changed content cannot hide behind an equal +revision. A candidate becomes complete only after Lit settles, required signed +assets are loaded, and two animation-frame opportunities pass for the current +token. A bounded trace and the production `data-continuity-state`, +`data-continuity-token`, `data-frame-fingerprint` and conditional +`data-recovery-reason` attributes expose this contract without entity ids or +URLs. + +The signed-asset runtime is authority-scoped (`hass.connection`), bounded and +shared across placements. A warm remount can therefore use an already loaded +backdrop synchronously. Refresh is stale-while-decode: the painted signed URL +stays authoritative until its replacement has loaded and decoded off-DOM. +Only when no complete/stale frame can be retained may the controller show the +localized opaque recovery overlay, after a 150 ms delay. The overlay never +steals initial focus; while visible it alone is interactive and the scene is +`inert`. + **Room climate is one pass per hass snapshot** (review R2-3). `areaClimateMap()` classifies the whole registry once and returns `Map`; the card memoizes it on `hass` identity, which Home Assistant replaces on every @@ -582,9 +605,12 @@ Shared, framework-light modules keep the two views from diverging: use the shared `ResolvedDevicePresentation` and `renderDeviceFace`; optional card settings can disable ordinary live dressing, temperature or signal without creating another semantic implementation. -- `src/config-store.ts` — module-level `{config, rev, layout}` cache shared by all embedded +- `src/config-store.ts` — module-level `{config, rev, configFingerprint, layout, + layoutRev, layoutFingerprint}` cache shared by all embedded cards (dedupes `houseplan/config/get`), seeded synchronously from the full card's - localStorage snapshot (`houseplan_card_cfg_v1`) and invalidated on `houseplan_config_updated`. + localStorage snapshot (`houseplan_card_cfg_v1`) and invalidated on + `houseplan_config_updated` or `houseplan_layout_updated` without first + clearing the visible static snapshot. **Static contract:** the schematic layer (`.hp-static-stage`) is `pointer-events:none`; the footer button lives outside it and stays clickable. @@ -652,8 +678,8 @@ hash falls back to the default. `_lightBarriers` collects everything opaque: the wall bodies exactly as the plan draws them (`wallBodiesGeometry`, real thickness, mitred junctions), every independent body (partition, column, draft), and the bare outline of - any edge that carries no thickness. It cuts out the exceptions: doorways, - gates and arches — cut through the masonry, so an opening is a real gap + any edge that carries no thickness. It cuts out the exceptions: doorways + and gates — cut through the masonry, so an opening is a real gap between two jamb faces and a beam is narrowed by the returns of a thick wall — plus virtual (open) boundaries, which are not walls at all. Windows stay solid, so an indoor lamp never washes the street; the light's masonry is cut @@ -680,7 +706,8 @@ hash falls back to the default. room ignored). There is no spill layer, no sector, no tunnel rectangle, no open-zone graph and no shadow mask left in the light path. - Barriers are cached by a fingerprint of their own geometry, never by + Barriers are cached per space by a fingerprint of their complete geometry + (every body point, both wall endpoints and scale inputs), never by `_cfgEpoch`: the epoch lags behind geometry edited in place, and a stale barrier set is invisible — the plan simply keeps lighting through a wall that now exists. The same fingerprint keys the per-source region cache. A cached per- diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 808398dc..aae27804 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,31 @@ ## Unreleased +## v1.61.0-beta.7 — 2026-08-11 + +- Fixed rare but severe Glow geometry failures: a clipping exception can no + longer paint outside the house, a grid-snapped source on a wall stays dark, + and the angular seam no longer cuts a wedge from an otherwise valid pool. + Light barriers now invalidate for every changed body point, wall endpoint, + scale or space. +- Delete/Cancel/Save in opening and space dialogs now use the same responsive + grouped footer as physical-object properties, keeping localized actions + inside narrow dialogs. +- Returning to a dashboard no longer hides or rebuilds an already complete + plan. Full and compact cards keep the last coherent frame through tab sleep, + reconnect, remount and transient resize; identical config/layout echoes are + adopted without geometry churn, and protected backdrops share a bounded + loaded cache. A localized recovery layer is reserved for the rare case where + no complete frame can be retained. ([#73](https://github.com/Matysh/houseplan-card/issues/73)) +- Restored the room-hover contract without bringing back the Glow flash: a + neutral clean-floor wash now darkens the resolved room fill while its late + outline remains above walls; Glow, sun, openings and devices stay visually + independent. ([#79](https://github.com/Matysh/houseplan-card/issues/79)) +- Working device markers keep their yellow resting plate with the ordinary + neutral elevation shadow, use the same hover as other interactive markers, + and no longer add an unrelated yellow outer glow. + ([#80](https://github.com/Matysh/houseplan-card/issues/80)) + ## v1.61.0-beta.6 — 2026-08-11 - Fixed shared dialog layout for localized content: multi-line titles now grow @@ -22,8 +47,9 @@ for light — there is no floor behind a front door to light, so it no longer glows halfway. Pools also fade over their whole radius instead of holding a flat plateau to 70%, so distant floor is faint because it is distant. As a - side effect the light layer became a single clipped circle per source and got - about four times cheaper to paint on a large plan. + side effect the light layer became a single clipped circle per source. Cold + geometry calculation on a complex plan became dramatically faster; the warm + render path remains broadly comparable with beta.5 and is tracked separately. ([#71](https://github.com/Matysh/houseplan-card/issues/71)) - Fixed a one-frame Glow brightness flash when hovering any room. Room hover now uses a plain SVG wash and double outline without compositor filters. diff --git a/docs/CHANGELOG.ru.md b/docs/CHANGELOG.ru.md index 824962d0..c83d77fb 100755 --- a/docs/CHANGELOG.ru.md +++ b/docs/CHANGELOG.ru.md @@ -8,6 +8,32 @@ ## Unreleased +## v1.61.0-beta.7 — 2026-08-11 + +- Исправлены редкие, но серьёзные сбои геометрии Glow: ошибка clipping больше + не может залить светом пространство вне дома, источник на привязанной к + сетке стене остаётся тёмным, а угловой шов больше не вырезает клин из + корректного пятна. Кэш барьеров теперь инвалидируется при изменении любой + точки тела, конца стены, масштаба или пространства. +- «Удалить»/«Отмена»/«Сохранить» в диалогах проёма и пространства переведены + на тот же адаптивный групповой футер, что свойства физических объектов: + локализованные действия не выходят за границы узкого диалога. +- Возврат на дашборд больше не скрывает и не пересобирает уже готовый план. + Полная и компактная карточки удерживают последний согласованный кадр при сне + вкладки, reconnect, remount и переходном resize; одинаковые ответы + config/layout принимаются без пересборки геометрии, а защищённые подложки + используют общий ограниченный cache загруженных ресурсов. Локализованный + слой восстановления оставлен только для редкого случая, когда удержать + полноценный кадр невозможно. ([#73](https://github.com/Matysh/houseplan-card/issues/73)) +- Восстановлен контракт hover комнаты без возврата вспышки Glow: нейтральный + слой по чистому полу затемняет фактическую заливку комнаты, а поздний контур + остаётся над стенами; Glow, солнце, проёмы и устройства визуально не + затрагиваются. ([#79](https://github.com/Matysh/houseplan-card/issues/79)) +- Работающие устройства сохраняют жёлтую подложку в покое с обычной нейтральной + тенью, получают тот же hover, что остальные интерактивные маркеры, и больше + не создают лишнее жёлтое свечение вокруг подложки. + ([#80](https://github.com/Matysh/houseplan-card/issues/80)) + ## v1.61.0-beta.6 — 2026-08-11 - Исправлена общая компоновка диалогов для длинных локализованных строк: @@ -28,8 +54,9 @@ света глухие — за входной дверью нет пола, освещать нечего, и она больше не светится наполовину. Пул вдобавок затухает по всему радиусу, а не держит плато до 70%, поэтому дальний пол слабее просто потому, что он дальше. Побочно - слой света стал одной обрезанной окружностью на источник и рисуется примерно - вчетверо быстрее на большом плане. + слой света стал одной обрезанной окружностью на источник. Холодный расчёт + геометрии сложного плана стал радикально быстрее; тёплый путь в целом остался + сопоставим с beta.5 и контролируется отдельно. ([#71](https://github.com/Matysh/houseplan-card/issues/71)) - Исправлена однокадровая вспышка яркости Glow при наведении на любую комнату. Hover теперь использует обычную SVG-заливку и двойной контур без фильтров diff --git a/docs/LIGHT.md b/docs/LIGHT.md index 649dbb5a..ee67e0bb 100644 --- a/docs/LIGHT.md +++ b/docs/LIGHT.md @@ -32,7 +32,7 @@ plan geometry and shares it between every lamp in the space. **Transparent** -- doorways, gates and arches — cut through the masonry, so an opening is a real +- doorways and gates — cut through the masonry, so an opening is a real gap between two jamb faces and a thick wall's returns narrow the beam; - virtual (open) boundaries, which are not walls to begin with. @@ -103,9 +103,10 @@ region cache (`_glowClipCache`). - `test/light-visibility.test.mjs` — the sweep itself: a wall stops light, a doorway lets a beam through and only through, a column's shadow has the - angular width its size dictates, an occluder out of range changes nothing, an - edge through the lamp is ignored, and a corner made by two crossing barriers - is lit right up to the corner. + angular width its size dictates, an occluder out of range changes nothing, a + source on an opaque edge is rejected, the ±π seam cannot cut a wedge from + the fan, and a corner made by two crossing barriers is lit right up to the + corner. - `demo/smoke_glow.mjs` — pixels on a rendered plan: the aperture itself is lit, the floor behind a door is lit, the visible beam is no wider than twice the opening, there is no light behind a column while the floor beside it is lit, @@ -121,6 +122,11 @@ region cache (`_glowClipCache`). ## Performance -One clipped circle per source and one blur for the layer: 60 sources in the -`large-light-blend-v1` profile render in ~75 ms against ~259 ms for the layered -model on the same runner (CPU throttled 4×). +The large cold geometry recalculation (20 rooms, 20 partitions, 14 columns, +61 sources) dropped from about 23.5 s in beta.5 to about 0.33 s in beta.6 on +the review runner — roughly 70×. The official warm `large-light-blend-v1` +path is generally level with beta.5; the 30-source sample was temporarily +slower and remains a performance watch item. During pinch/pan and the bounded +500 ms source fade, the whole-layer blur is bypassed and its parameters stay +frozen; the final screen-space feather is restored once after the transition +instead of rebuilding and evaluating the filter for every animation frame. diff --git a/docs/RELEASE-NOTES.md b/docs/RELEASE-NOTES.md index a377360d..48c053fe 100644 --- a/docs/RELEASE-NOTES.md +++ b/docs/RELEASE-NOTES.md @@ -1,14 +1,16 @@ - + ## Основное -- Свет на плане переведён на единую геометрическую модель: лампа освещает только видимый ей пол, реальные стены и колонны отбрасывают тени, а свет через дверные проёмы остаётся непрерывным и ограничивается откосами ([#71](https://github.com/Matysh/houseplan-card/issues/71)). +- План сохраняет последний цельный кадр при возврате на вкладку, reconnect и remount, поэтому больше не должен мигать или пересобираться без необходимости ([#73](https://github.com/Matysh/houseplan-card/issues/73)). +- Исправлены редкие сбои геометрии Glow, способные засветить область вне дома или исказить тени у стен. - Мелкие исправления и улучшения. ## Highlights -- Plan lighting now uses one geometric visibility model: a lamp lights only the floor it can see, real walls and columns cast shadows, and doorway light remains continuous and constrained by the jambs ([#71](https://github.com/Matysh/houseplan-card/issues/71)). +- The plan keeps its last coherent frame through tab resume, reconnect and remount, avoiding unnecessary flashes and rebuilds ([#73](https://github.com/Matysh/houseplan-card/issues/73)). +- Fixed rare Glow geometry failures that could light outside the house or distort wall shadows. - Small fixes and improvements. -[Полный список изменений на русском](https://github.com/Matysh/houseplan-card/blob/v1.61.0-beta.6/docs/CHANGELOG.ru.md) -· [Full changelog in English](https://github.com/Matysh/houseplan-card/blob/v1.61.0-beta.6/docs/CHANGELOG.md) +[Полный список изменений на русском](https://github.com/Matysh/houseplan-card/blob/v1.61.0-beta.7/docs/CHANGELOG.ru.md) +· [Full changelog in English](https://github.com/Matysh/houseplan-card/blob/v1.61.0-beta.7/docs/CHANGELOG.md) diff --git a/docs/STATUS.md b/docs/STATUS.md index 643811a7..d7ec0904 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -22,11 +22,10 @@ metadata). Only an explicit owner-approved emergency hotfix may skip this gate. | Item | State | |---|---| | Version | **v1.61.0-beta.6** everywhere (manifest, const.py, package.json, CARD_VERSION) — prerelease candidate with visibility-based light transport, stable room hover and shared responsive dialog chrome | -| Current local cycle | v1.61.0-beta.6 rebuilds light transport on ONE rule accepted by the owner after manual testing (#71): **a lamp lights the floor it can see**. `_lightBarriers` collects the drawn wall bodies with their real thickness, independent bodies and the outlines of edges without thickness, cuts out passages (a window and a door with no floor behind it stay solid) and virtual boundaries, then splits every barrier where barriers cross so the sweep never skips a junction corner; `visibilityPolygon` (`src/light-visibility.ts`) returns the region and one clipped circle paints it. The separate spill path, the doorway sector, the aperture tunnel, the open-zone graph and the shadow mask are gone, and with them the class of bugs where two layers disagreed. Pools now fade over the whole radius (`GLOW_FALLOFF`) instead of holding a plateau to 70%, and the layer gets one screen-pixel-sized blur as a penumbra. Smoke asserts pixels: aperture lit, floor behind a door lit, nothing behind a column (≤3) while the floor beside it is lit (≥8), a lit→unlit border at most 4 px wide, one painted child per spot, an outside door that changes the lit region not at all. Room hover was also moved off compositor filters to remove a one-frame Glow brightness flash (#72), and shared dialogs now grow for multiline titles and wrap destructive/commit action groups safely (#77/#78). 60 sources render ~3.5× faster than the layered model. The model, its -exceptions and its gotchas are documented in `docs/LIGHT.md`. | +| Current local cycle | After v1.61.0-beta.6, #73 visual continuity is implemented locally alongside the deterministic #79/#80 fixes and the beta.6 review corrections. Full and compact cards share one tokenised controller, keep the last coherent frame through resume/reconnect/remount/resize, adopt config/layout by revision plus content fingerprint, share loaded signed assets and expose a delayed localized recovery fallback only when no complete frame exists. Deterministic unit/frame-sequence coverage is added but not run yet under the owner's local-edit rule. Room hover remains split into a neutral clean-floor wash below Glow/sun/walls and a late outline above walls; the device CSS cascade restores common hover and neutral elevation shadow. Glow now fails dark on clipping errors and sources placed on barriers, normalizes the angular seam, fingerprints complete barrier geometry per space, freezes feather parameters through viewport gestures and has a semantic golden assertion that the receiving room is actually lit. Opening/space footers use the shared responsive action groups. `docs/LIGHT.md` remains canonical. | | Workflow | Owner's rule since 2026-08-07: ordinary fixes/features are made **locally, without tests and without commits**. A requested pre-release gets a production build plus the smallest targeted unit/smoke set covering the changed surfaces, one tested `dev` commit/tag and a GitHub Release with `prerelease=true`; `main` stays untouched. The complete local frontend/backend/smoke gate runs only before a stable release, after which `main` is fast-forwarded to the exact tested `dev` SHA and the GitHub Release uses `prerelease=false`. Release bodies are short and bilingual (Russian first): only significant user changes get individual bullets, while minor/code-only work is grouped as `Мелкие исправления и улучшения` / `Small fixes and improvements`; every body ends with separate links to the Russian and English changelogs. Detailed RU/EN changelog bullets may link the corresponding closed GitHub Issues; open or partially delivered issues are never presented as shipped. Telegram announcements are sent only for stable releases; beta and RC publication is silent. `docs/RELEASE-NOTES.md` is the current canonical body instance; `npm run release:prerelease -- --issues=… --yes` is the primary local publication path and the manual `Publish prerelease` workflow is its GitHub-only equivalent once present on `main`. Nothing is copied to the home instance by hand | | GitHub | https://github.com/Matysh/houseplan-card — [Issues](https://github.com/Matysh/houseplan-card/issues) are the canonical task records and the linked [Project v2](https://github.com/users/Matysh/projects/1) is the canonical priority/status view; both must stay current. `main` carries stable releases; pre-release tags may point directly at `dev`. Work lands on `dev` and is merged into `main` for a stable release, so `dev` is normally equal to or ahead of `main`, never behind. Push via SSH key `ha_jb` (remote git@github.com:…); API releases via the fine-grained PAT in `~/.git-credentials` (Contents R/W, issued 2026-07-23) | -| CI | Prerelease publication requires a green exact-SHA Validate: frontend/backend, smoke, golden, HACS/Hassfest and a short absolute-ceiling performance smoke. Obsolete same-ref Validate runs are cancelled. Full seven-sample base/candidate performance moved to `performance.yml` (`main` push, weekly, manual); stable release assets fail closed unless both Validate and Full Performance are green for the exact tagged SHA. | +| CI | Prerelease publication requires a green exact-SHA Validate: frontend/backend, smoke (including the #73 rAF frame sampler), golden, HACS/Hassfest and a short absolute-ceiling performance smoke. Obsolete same-ref Validate runs are cancelled. Full seven-sample base/candidate performance moved to `performance.yml` (`main` push, weekly, manual); stable release assets fail closed unless Validate and Full Performance are green for the exact tagged SHA and the stable-only CDP compositor screencast finds no empty/black presented frame. | | HACS | Custom repository works. **Inclusion PR: hacs/default#9004** — open, valid, labeled, mergeable clean, never drafted. Queue: 1212 open, 835 older than ours. Merge rate COLLAPSED: 75 in July but almost all in the first decade, 0 in the last week (checked 2026-07-29) — maintainers process in rare bursts; ETA unknowable, months at best. Nothing actionable on our side | | Home instance | ha.jbstudio.pro (SSH port **22222**, key `ha_jb`; HA config root is `/mnt/data/supervisor/homeassistant` — `/config` does NOT exist in this SSH environment), last direct copy was **v1.57.0**; from v1.58.0 on it updates itself through HACS by tag (no scp) | | Localization | UI en/ru (src/i18n/*.json), everything user-visible localized incl. kiosk popover | diff --git a/docs/TESTING.md b/docs/TESTING.md index 8c63f51a..16b59cf8 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -783,12 +783,12 @@ separately promised workflows: does not immediately arm a new opening [manual] - [ ] Open boundaries (v1.37.0): virtual stretches exist only between two rooms on their shared wall and render dashed; glow light floods the whole - connected open zone transitively, door sectors work from the zone's - outer walls; merge/split keep links by room id [auto: smoke_openwall] + connected floor transitively wherever the source has line of sight; + merge/split keep links by room id [auto: smoke_openwall] - [ ] Light transport (#71, model: `docs/LIGHT.md`): a source paints exactly ONE region — the floor it can see. Opaque: the drawn wall bodies with their real thickness, plus independent bodies, plus untouched outlines of edges without thickness. - Transparent: doorways, gates, arches and virtual boundaries — but only + Transparent: doorways, gates and virtual boundaries — but only where BOTH sides are floor; a window and an outside door are opaque, and an outside door must not change the lit region at all (no half-lit tunnel). Light must never appear inside a wall body: at an opening it @@ -801,8 +801,9 @@ separately promised workflows: beam stays no wider than twice the opening. Behind a column there is no light at all (delta ≤ 3) while the floor beside it is lit (≥ 8), and the lit→unlit border is at most 4 stage pixels wide — a geometric edge, not a - Gaussian. A spot contains exactly one painted child; no `mask`, `filter` - or `feGaussianBlur` may appear anywhere in the light layer, and the pool + Gaussian. A spot contains exactly one painted child; no `mask` or + per-source filter may appear in the light layer, exactly one + `feGaussianBlur` feathers the complete layer, and the pool clip may never leave the rooms. The pool gradient falls off monotonically over the whole radius (no plateau; at 70% at most 75% of the centre alpha). A lamp lights a room across a virtual boundary, and across two of @@ -812,7 +813,7 @@ separately promised workflows: golden: lighting-opaque-glow-two-doorways-dark] - [ ] Per-source glow radius (v1.36.2): the device dialog has a "Glow radius" field (HA units; empty = general-settings default shown as placeholder); - an override changes that source's pool and door sectors only [auto: smoke_glow] + an override changes that source's visibility-clipped pool only [auto: smoke_glow] - [ ] Hidden-light primary (v1.36.1): a lamp whose light entity is HIDDEN in the registry (folded into a light group) still toggles/reflects the lamp, not its do-not-disturb switch or identify button; visible entities of the @@ -1105,19 +1106,28 @@ that it automatically runs blocking verification. Review and accept the `golden-images` CI artifact rather than treating a developer OS raster as the canonical set. See `demo/golden/README.md`. -### Issue #73 preparation gate (2026-08-10) +### Issue #73 baseline and implementation (2026-08-11) -Visual-continuity implementation starts only after renderer dependencies #67, -#71 and #72 are published, closed and verified by the exact-SHA prerelease -gate. The v1.61.0-beta.6 candidate contains the accepted visibility-based light -model and filter-free room hover, together with matrix-v7 baselines including -`lighting-opaque-glow-two-doorways-dark`. The owner approved the complete -visual result on 2026-08-11; the canonical behaviour is `docs/LIGHT.md`. +The published v1.61.0-beta.6 exact SHA is the renderer baseline for #73: it +contains the accepted visibility-based light model and the matrix-v7 published +baseline; local review fixes prepare matrix-v8 with a semantic warm-pixel gate, +including `lighting-opaque-glow-two-doorways-dark`; canonical light behaviour +is `docs/LIGHT.md`. The owner explicitly started #73 on 2026-08-11. -Do not start #73 from a merely local result. First record the successful -v1.61.0-beta.6 Validate SHA and the blocking Linux golden verification in the -issue; the published exact SHA, rather than an ad-hoc local capture, is the -continuity baseline. +`test/visual-continuity.test.mjs` covers tokens, quick/long return, delayed +overlay timing, paint barriers and fingerprints. `demo/smoke_visual_continuity.mjs` +samples presented frames and rejects hidden/empty plans, viewport rollback, +overlay over a stale frame, missing production attributes and unbounded or +sensitive trace data. It supplements rather than replaces warm-remount, +websocket-resilience and golden verification. + +`npm run continuity:screencast` is the separate compositor-level gate required +before a stable release. It captures acknowledged PNG frames through CDP +`Page.startScreencast`, crops the real plan stage, rejects uniform/black frame +regressions and writes the exact frames plus metrics to +`artifacts/continuity-screencast`. Prereleases keep the faster mandatory rAF +smoke; the stable release workflow installs Chromium and runs the screencast +before attaching the public card asset. ## Large-house performance gate @@ -1727,9 +1737,17 @@ require hands on real hardware — they remain for the human pass. - [ ] **A quick return does not flash**: leave the browser tab or minimise the window for a few seconds and return. The existing viewport, day/night - background and room hover remain painted continuously; neither - `skysnap` nor the long-sleep `hpresume` veil is armed - [auto: smoke_sun_live_bg] + background and room hover remain painted continuously; the continuity + token stays unchanged and no recovery overlay is created + [auto: smoke_sun_live_bg, smoke_visual_continuity] +- [ ] **A long return holds the complete frame**: every sampled frame keeps a + visible `.zoomwrap`, non-empty rooms and the same viewport while + config/layout reconnect data is revalidated. A stale frame never gets a + recovery overlay [auto: smoke_visual_continuity, smoke_ws_resilience] +- [ ] **Protected backdrops survive remount and refresh**: a loaded authority- + scoped signed URL is available synchronously to another placement; an + aging URL remains painted until its replacement decodes + [unit: signing.test; auto: smoke_plan_signed, smoke_space_card_bg] - [ ] **The view does not twitch**: pan the plan into a corner and zoom in (say 2.5×), leave the tab for long enough that HA reconnects, come back — the plan is in exactly the same place at exactly the same scale. Not diff --git a/docs/USER-GUIDE.ru.md b/docs/USER-GUIDE.ru.md index d27a48aa..5e777bda 100644 --- a/docs/USER-GUIDE.ru.md +++ b/docs/USER-GUIDE.ru.md @@ -687,8 +687,8 @@ Glow и заливка «Свет» продолжают работать по даже при зафиксированной яркости. При включённом Glow лампа освещает тот пол, который видит. Стены не пропускают -свет своей настоящей толщиной, как и колонны с отдельными перегородками; двери, -ворота и арки — проходы, поэтому через них свет идёт дальше, а откосы толстой +свет своей настоящей толщиной, как и колонны с отдельными перегородками; двери +и ворота — проходы, поэтому через них свет идёт дальше, а откосы толстой стены сужают луч. Виртуальные границы не стены вовсе: комнаты за ними освещаются свободно. Окно и наружная дверь для света глухие — за наружной дверью нет пола, освещать нечего. Отсюда же берутся тени: тень колонны или угла diff --git a/docs/WARM-REMOUNT.md b/docs/WARM-REMOUNT.md index 5b5b9614..e915071d 100644 --- a/docs/WARM-REMOUNT.md +++ b/docs/WARM-REMOUNT.md @@ -144,15 +144,20 @@ Lovelace **пересоздаёт элемент карточки** — выбр `ResizeObserver` способен выдать нулевой или промежуточный размер сцены. Такой размер больше никогда не меняет `_view`. -Если вкладка была скрыта не меньше 15 секунд, обычный режим Просмотра включает -короткий `_resumeSettling`: содержимое сцены остаётся за её уже правильным -фоном, пока размер не станет ненулевым и спокойным. После этого сохранённые -центр и zoom пересчитываются по окончательной геометрии и открываются одним -кадром. Памятка `warmBoot` во время промежуточного кадра не обновляется. -Быстрое переключение вкладок, редакторы и киоск в этот путь не входят. При -быстром возврате сохраняются также уже нарисованные day/night-фон и hover: -безусловный сброс этих полей создавал отдельный `skysnap`-кадр и визуальное -мигание даже после двух секунд в фоне. +Порог 15 секунд теперь только отмечает возможный browser freeze. Быстрый +возврат — строгий no-op: не меняются token, DOM, viewport, day/night-фон и +hover. После долгого сна общий `VisualContinuityController` удерживает на +экране последний полноценный кадр, параллельно перепроверяет config/layout и +принимает только подтверждённый положительный размер сцены. Класса +`hpresume` и скрытия `.zoomwrap` больше нет ни в Просмотре, ни в редакторах, +ни в киоске. + +Памятка `warmBoot` не стала вторым lifecycle-механизмом: она по-прежнему +принадлежит placement и теперь дополнительно переносит fingerprint последнего +полного кадра и компактный roster устройств. Подписи и readiness загруженных +подложек принадлежат общему cache соединения, поэтому новый экземпляр может +нарисовать защищённую подложку в первом кадре. Промежуточный `0×0` размер, +неполный candidate и новая подписанная ссылка до decode в памятку не попадают. Толстые стены дополнительно имеют критические `fill`, `stroke`, opacity и `fill-rule` как SVG presentation attributes. Поэтому даже промежуточный кадр diff --git a/docs/specs/019-glow-additive-blending.md b/docs/specs/019-glow-additive-blending.md index 0e4842e3..8eec5b5a 100644 --- a/docs/specs/019-glow-additive-blending.md +++ b/docs/specs/019-glow-additive-blending.md @@ -12,7 +12,7 @@ - тёплый и холодный источник дают смешанный цвет в пересечении; - два одинаковых dim-источника дают более светлое пересечение; -- room/data fill, Glow base, tunnel sectors, backdrop и sun не участвуют в +- room/data fill, Glow base, opening tunnel fills, backdrop и sun не участвуют в аддитивной группе. Модель источников, `resolvedLightSources(room)`, радиусы, clipping светом стен и @@ -36,9 +36,7 @@ - - … - + … @@ -56,16 +54,16 @@ screen/normal состояний между источниками не быва .glow-pools.blend-normal > .glow-spot { mix-blend-mode: normal; } ``` -После #71 screen-композиция принадлежит внешнему `.glow-spot`, а luminance -mask — обычной внутренней `.glow-shadow-target`. Маску запрещено возвращать на -тот же SVG primitive, который несёт `mix-blend-mode`: Chromium может принять -атрибут в DOM, но проигнорировать его при compositor promotion. +После #71 screen-композиция принадлежит `.glow-spot`, а единственная форма +источника — один circle, обрезанный полигоном видимости, пересечённым с +полом. Per-source luminance mask и отдельный shadow layer удалены: тень — это +пол, не попавший в visibility polygon. -`glow_base`, resolved room/data fill, opening tunnel sectors и sun rays +`glow_base`, resolved room/data fill, opening tunnel fills и sun rays остаются sibling layers вне `glow-pools-frame`. Их порядок относительно друг друга сохраняется по #55. -Каждый circle сохраняет собственный `clip-path` после re-parenting. Clip ids +Каждый circle сохраняет собственный visibility `clip-path` после re-parenting. Clip ids остаются уникальными и стабильными в пределах SVG; объединять per-source clips в один общий clip запрещено, иначе свет начнёт проходить сквозь чужие стены. @@ -74,8 +72,11 @@ mask — обычной внутренней `.glow-shadow-target`. Маску Альфа отдельного pool задаётся только `stop-opacity` его radial gradient: ```text -Aplateau = clamp(fill_colors.glow_light.a * 0.7 * (0.4 + 0.6 * sourceBrightness^(1/2.2)), 0, 1) -A(r <= 70%) = Aplateau +Acenter = clamp(fill_colors.glow_light.a * 0.7 * (0.4 + 0.6 * sourceBrightness^(1/2.2)), 0, 1) +A(r = 0%) = Acenter +A(r = 45%) = 0.88 * Acenter +A(r = 70%) = 0.62 * Acenter +A(r = 86%) = 0.32 * Acenter A(r = 100%) = 0 screen(S, D) = 1 - (1 - S) * (1 - D) // отдельно для R, G, B в 0..1 ``` @@ -83,9 +84,13 @@ screen(S, D) = 1 - (1 - S) * (1 - D) // отдельно для R, G, B в 0.. На `.glow-pool` запрещены `opacity`, `fill-opacity`, filter и дополнительная alpha: полупрозрачность должна участвовать в blend через gradient stops, а не создавать отдельную element-композицию. Коэффициент `0.7` уже входит в -`Aplateau`; внешний `glow-pools-frame` не имеет opacity и не применяет его +`Acenter`; внешний `glow-pools-frame` не имеет постоянного opacity и не применяет его повторно. +`.glow-spot` может временно анимировать opacity 0→1/1→0 в течение 500 мс. +Это только transition появления/исчезновения; в steady-state opacity равна 1 и не +меняет alpha-формулу. + Glow base использует собственный alpha-контракт #55 и не входит в эту формулу. Blend не меняет base/tunnel opacity и не осветляет paper/backdrop. @@ -191,7 +196,7 @@ research без скрытого fallback toggle. - warm+cool overlap соответствует screen-формуле с установленным допуском; - две одинаковые dim-лампы дают overlap светлее одного pool без channel clip; -- background/base и tunnel sector сохраняют baseline pixels; +- background/base и opening tunnel fill сохраняют baseline pixels; - перестановка markers не меняет overlap; - принудительный test-only fallback совпадает с current normal baseline; - per-room clips переживают re-parenting: pool не появляется за физической @@ -238,9 +243,10 @@ Release body описывает видимую пользователю смес SVG render probe; `CSS.supports` сам по себе недостаточен. 2. Все pools документа используют одно screen/normal решение; смешанного режима нет. -3. Альфа живёт в gradient stops, per-pool и outer-frame opacity отсутствуют; +3. Альфа steady-state живёт в gradient stops; outer-frame opacity + отсутствует, а opacity `.glow-spot` используется только для 500 ms transition; коэффициент `0.7` применяется ровно один раз внутри формулы stop-opacity. -4. Glow base, data fill, paper/backdrop, tunnel sectors и sun остаются вне +4. Glow base, data fill, paper/backdrop, opening tunnel fills и sun остаются вне isolated group и сохраняют baseline pixels. 5. Каждый pool сохраняет собственный clip-path после re-parenting. 6. Shared fixture 1/10/30/60 проходит frontend и backend schema tests. @@ -250,5 +256,5 @@ Release body описывает видимую пользователю смес 9. #55 повторно использует ту же pool group и не создаёт второе смешивание. 10. Документация и ru/en changelog обновлены. -ТЗ готово к реализации только после повторного ревью capability probe, -alpha-семантики и измеримого performance gate. +Фактическая структура после #71 зафиксирована выше; каноническая модель +транспорта света и её ограничения живут в `docs/LIGHT.md`. diff --git a/docs/superpowers/specs/2026-08-10-plan-visual-continuity-design.md b/docs/superpowers/specs/2026-08-10-plan-visual-continuity-design.md index 4f0ebf04..f78861ae 100644 --- a/docs/superpowers/specs/2026-08-10-plan-visual-continuity-design.md +++ b/docs/superpowers/specs/2026-08-10-plan-visual-continuity-design.md @@ -1,7 +1,7 @@ # ТЗ: визуальная непрерывность плана при возврате на вкладку **Issue:** [#73 — Исключить мигание плана при возврате на вкладку](https://github.com/Matysh/houseplan-card/issues/73)
-**Статус:** ТЗ принято после двух раундов ревью; implementation gate пока не пройден, реализация не начата
+**Статус:** ТЗ принято после двух раундов ревью; implementation gate подтверждён публикацией `v1.61.0-beta.6` на SHA `d2bc9082807452c88e735c72d4a06d1ff873eaa9`; локальная реализация начата 2026-08-11 по прямому поручению владельца
**Область:** `houseplan-card`, `houseplan-space-card`, lifecycle браузера и Home Assistant
**Модель данных:** без изменений
**Backend API:** без обязательных изменений @@ -687,7 +687,7 @@ Sampler запускается внутри страницы, используе **Implementation gate.** Реализация #73 не начинается, пока не закрыты и не прошли code review renderer-задачи [#67](https://github.com/Matysh/houseplan-card/issues/67), [#71](https://github.com/Matysh/houseplan-card/issues/71) и [#72](https://github.com/Matysh/houseplan-card/issues/72). После их закрытия действующие golden images переснимаются только при подтверждённом ожидаемом изменении, переутверждаются и фиксируются как авторитетный baseline для frame-sequence fixture. SHA baseline записывается в issue #73/тестовую документацию до первой реализации continuity controller. -**Текущий статус gate на 2026-08-10.** Зависимость #67 закрыта после code review. Финальные локальные реализации #71 и #72 присутствуют и проходят целевые проверки, но обе issue ещё открыты и изменения не зафиксированы в `dev`, поэтому renderer-gate пока не выполнен. После пересборки `test-build` прошли 152/152 теста в `test/logic.test.mjs`, `test/golden-matrix.test.mjs` и `test/release-contract.test.mjs`; `demo/smoke_glow.mjs` и `demo/smoke_glow_blending.mjs` также зелёные. Детерминированная golden-матрица v7 снята локально полностью: 34/34 сценария без runtime-ошибок, build fingerprint `82ddcb9a6909d1bf624e4a95e511d9c15a6adbab6ae6082cd0e31472c49ad56a`. Windows-кандидат `lighting-opaque-glow-two-doorways-dark` имеет SHA-256 `b0bbb9949e661f0f46b27ea018f9bd0e1485b7e8beb30e7c41af20f83f85ae10`, но служит только диагностическим предпросмотром: владельцем не утверждён и не заменяет авторитетный Linux CI artifact. До закрытия #71/#72, owner approval полного Linux artifact и записи авторитетного baseline SHA реализация continuity controller не начинается. Отдельный frame-sequence baseline будет создан уже на этапе 5 из утверждённого статического renderer baseline, а не подменяет этот gate. +**Текущий статус gate на 2026-08-11.** Gate выполнен релизом `v1.61.0-beta.6` на exact SHA `d2bc9082807452c88e735c72d4a06d1ff873eaa9`: зависимости #71/#72 опубликованы и закрыты, Linux golden matrix v7, full browser smoke и performance smoke прошли в Validate run `31437504173` без candidate/diff artifact. Каноническое описание света после этих изменений — `docs/LIGHT.md`. По прямому поручению владельца #73 переведена в In Progress и реализация controller начата от этого baseline; отдельный frame-sequence baseline создаётся на этапе 5 и не подменяет зафиксированный renderer baseline. ### Этап 1. Наблюдаемость и state machine diff --git a/package-lock.json b/package-lock.json index 3be24380..56d1caf2 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "houseplan-card", - "version": "1.61.0-beta.6", + "version": "1.61.0-beta.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "houseplan-card", - "version": "1.61.0-beta.6", + "version": "1.61.0-beta.7", "license": "MIT", "dependencies": { "lit": "^3.1.3", diff --git a/package.json b/package.json index 5a08972b..6149c7e7 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "houseplan-card", - "version": "1.61.0-beta.6", + "version": "1.61.0-beta.7", "description": "Interactive house plan Lovelace card for Home Assistant", "license": "MIT", "type": "module", @@ -16,6 +16,7 @@ "benchmark:compare": "node demo/performance/compare.mjs", "golden:capture": "node demo/golden/run.mjs --mode=capture", "golden:verify": "node demo/golden/run.mjs --mode=verify", + "continuity:screencast": "node demo/screencast_visual_continuity.mjs", "golden:accept": "node demo/golden/accept.mjs", "release:check": "node scripts/release-prerelease.mjs --check", "release:prerelease": "node scripts/release-prerelease.mjs" diff --git a/src/config-store.ts b/src/config-store.ts index 9cb4ea4d..a8cf01fb 100644 --- a/src/config-store.ts +++ b/src/config-store.ts @@ -8,12 +8,17 @@ * localStorage (`houseplan_card_cfg_v1`) for its own instant start — we seed from it * so embedded cards paint immediately, then refresh from the server in the background. */ +import { contentFingerprint } from './visual-continuity'; + const LS_CFG = 'houseplan_card_cfg_v1'; export interface HpConfigSnapshot { config: any | null; rev: number; + configFingerprint: string; layout: Record; + layoutRev: number; + layoutFingerprint: string; } let cache: HpConfigSnapshot | null = null; @@ -27,7 +32,15 @@ export function cachedSnapshot(): HpConfigSnapshot | null { try { const c = JSON.parse(localStorage.getItem(LS_CFG) || 'null'); if (c && c.config && Array.isArray(c.config.spaces)) { - return { config: c.config, rev: c.rev || 0, layout: c.layout || {} }; + const layout = c.layout || {}; + return { + config: c.config, + rev: c.rev || 0, + configFingerprint: c.config_fingerprint || contentFingerprint(c.config), + layout, + layoutRev: c.layout_rev || 0, + layoutFingerprint: c.layout_fingerprint || contentFingerprint(layout), + }; } } catch { /* ignore */ @@ -43,7 +56,10 @@ async function fetchFresh(hass: any): Promise { cache = { config: cfgResp?.config ?? null, rev: cfgResp?.rev ?? 0, + configFingerprint: contentFingerprint(cfgResp?.config ?? null), layout: layResp?.layout ?? {}, + layoutRev: layResp?.rev ?? 0, + layoutFingerprint: contentFingerprint(layResp?.layout ?? {}), }; if (!subscribed && hass.connection?.subscribeEvents) { subscribed = true; @@ -66,7 +82,8 @@ async function fetchFresh(hass: any): Promise { } /** Get the shared config snapshot (cached, deduped across cards). */ -export function getConfig(hass: any): Promise { +export function getConfig(hass: any, force = false): Promise { + if (force) cache = null; if (cache) return Promise.resolve(cache); if (inflight) return inflight; inflight = fetchFresh(hass).finally(() => { diff --git a/src/glow-blend.ts b/src/glow-blend.ts index c0a54053..ef626ca6 100644 --- a/src/glow-blend.ts +++ b/src/glow-blend.ts @@ -1,6 +1,7 @@ /** Runtime proof that this document can rasterize isolated SVG screen blend. */ -const documentProbes = new WeakMap>(); +type ProbeState = { promise: Promise; resolved?: boolean }; +const documentProbes = new WeakMap(); export type Rgb = readonly [number, number, number]; @@ -59,9 +60,17 @@ async function renderProbe(doc: Document): Promise { /** One cached Promise per real Document; errors are a normal false fallback. */ export function svgScreenBlendSupported(doc: Document): Promise { const cached = documentProbes.get(doc); - if (cached) return cached; - const probe = renderProbe(doc).catch(() => false); - documentProbes.set(doc, probe); - return probe; + if (cached) return cached.promise; + const state: ProbeState = { promise: Promise.resolve(false) }; + state.promise = renderProbe(doc).catch(() => false).then((resolved) => { + state.resolved = resolved; + return resolved; + }); + documentProbes.set(doc, state); + return state.promise; } +/** Synchronous warm-remount path: undefined means the first cold probe is pending. */ +export function resolvedSvgScreenBlend(doc: Document): boolean | undefined { + return documentProbes.get(doc)?.resolved; +} diff --git a/src/houseplan-card.ts b/src/houseplan-card.ts index a78a73c0..11f9faff 100755 --- a/src/houseplan-card.ts +++ b/src/houseplan-card.ts @@ -125,7 +125,15 @@ import { import { optimizePlans, type OptimizeReport } from './plan-optimizer'; import { langOf, t, type I18nKey } from './i18n'; import { CommandStack } from './command-stack'; -import { svgScreenBlendSupported } from './glow-blend'; +import { resolvedSvgScreenBlend, svgScreenBlendSupported } from './glow-blend'; +import { + CONTINUITY_LONG_HIDDEN_MS, + VisualContinuityController, + contentFingerprint, + subscribePageVisibility, + visualFrameFingerprint, + type PageVisibilitySignal, +} from './visual-continuity'; import { combineVisualSamples, edgeActivity, entityVisualSample, entityVisualSamplesForDevice, type DeviceActivity, type DeviceVisualState, type EntityVisualSample, @@ -152,7 +160,7 @@ import { import { renderOpeningTunnelFills } from './render/opening-tunnels'; import { safeStoredColor } from './color'; -const CARD_VERSION = '1.61.0-beta.6'; +const CARD_VERSION = '1.61.0-beta.7'; /** Keeps every previously valid scale at the maximum 20 cm grid scale lossless. */ const DECOR_TEXT_CM_MAX = 2000; const CELL_CM_MIN = 0.1; @@ -170,18 +178,6 @@ const BOOT_MAX_MS = 1200; /** AUD-1552-02: post-reveal grace during which late chrome shifts glide * (CSS height transition on the stage) instead of snapping. */ const BOOT_SOFT_MS = 1500; -/** A long-backgrounded browser tab may resume through several stale/partial - * layout frames (HA chrome, dynamic viewport and a websocket warm re-mount - * do not necessarily wake in the same frame). In normal View we keep those - * frames behind the stage background and reveal one viewport computed from - * a quiet, measurable stage. Quick tab switches stay instant; kiosk and all - * editors deliberately bypass this path. */ -const RESUME_LONG_HIDDEN_MS = 15000; -const RESUME_RECENT_MS = 15000; -const RESUME_MIN_MS = 220; -const RESUME_QUIET_MS = 80; -const RESUME_MAX_MS = 750; - /** Numeric editor fields must consume the whole value: `50abc` is invalid, * not a surprisingly accepted 50. Decimal comma remains supported. */ const strictNumber = (value: string): number | null => { @@ -208,12 +204,6 @@ const lruWrite = (cache: Map, key: K, value: V, limit: number): void cache.delete(oldest); } }; -let pageHiddenAt = typeof document !== 'undefined' && document.visibilityState === 'hidden' ? Date.now() : 0; -let pageResumedAt = 0; -/** All card instances receive the same visibilitychange Event object. Keep the - * long-return decision on that event so a second card does not mistake the - * first card's cleared pageHiddenAt for a quick return. */ -let pageLongResumeEvent: Event | null = null; /** DEV-B703-01: warm re-mount memo — MODULE scope, so it lives with the loaded * PAGE, not with any card instance. Lovelace re-creates card elements when * the websocket reconnects after a long-backgrounded tab; the fresh instance @@ -275,6 +265,11 @@ type WarmEntry = { hdrH: number; stageH: number; vp: WarmViewport | null; + /** Last complete visual frame for #73; safe only inside the same placement. */ + frameFingerprint: string; + /** Metadata projection is immutable-by-replacement and avoids an empty + * device layer on a same-document warm remount before registry refresh. */ + devices: readonly DevItem[] | null; dlg: WarmDialog | null; /** when the instance that wrote `dlg` detached; 0 = it is still alive */ freed: number; @@ -530,9 +525,11 @@ class HouseplanCard extends LitElement { private _loadTries = 0; private _serverCfg: ServerConfig | null = null; private _cfgRev = 0; + private _cfgContentFingerprint = ''; private _unsubCfg: (() => void) | null = null; private _unsubLayout: (() => void) | null = null; private _layoutRev = 0; + private _layoutContentFingerprint = ''; /** One-deep server snapshot; invalidated by the first later plan edit. */ private _canOptimizeUndo = false; private _devices: DevItem[] = []; @@ -883,12 +880,17 @@ class HouseplanCard extends LitElement { key: string; value: { occluders: LightSegment[]; floor: number[][][]; fingerprint: string }; } | null = null; + /** Freeze the SVG blur while a pinch/pan emits animation frames, then adopt + * the final screen-space value after the gesture. */ + private _glowFeatherUnits: number | null = null; /** Active pools survive an off transition until their 500 ms fade completes. */ private _glowRenderedSources = new Map(); private _glowLastAppearance = new Map(); private _glowEnteringSources = new Set(); private _glowEnterRafs = new Map(); private _glowFadeTimers = new Map(); + private _glowFeatherSuspendUntil = 0; + private _glowFeatherResumeTimer = 0; private _glowSourceSeq = 0; /** Pending/false uses the exact historical normal-layer fallback. */ private _glowScreenBlend = false; @@ -1194,41 +1196,45 @@ class HouseplanCard extends LitElement { private _vacSrvTrails: Record = {}; private _unsubTrail?: () => void; private _vacJumpOnce = false; - private _vacVisHandler = (event: Event) => { - if (document.visibilityState === 'hidden') { - if (!pageHiddenAt) pageHiddenAt = Date.now(); - pageLongResumeEvent = null; - return; - } - if (document.visibilityState === 'visible') { - const now = Date.now(); - const hiddenFor = pageHiddenAt ? now - pageHiddenAt : 0; - if (hiddenFor >= RESUME_LONG_HIDDEN_MS) { - pageResumedAt = now; - pageLongResumeEvent = event; - } - const longReturn = pageLongResumeEvent === event; - pageHiddenAt = 0; - this._vacJumpOnce = true; - if (longReturn) { - // A genuinely sleeping tab paints nothing, so the 45 s sky transition - // stood still while the sun kept moving. Catch it up behind the resume - // veil. A quick tab switch must preserve the already painted sky and - // hover; resetting them created a visible one-frame flash on every - // return even when the tab was hidden for only a couple of seconds. - this._skyElev = null; - this._tip = null; - this._hoverRoom = null; - this._beginResumeSettle(); - } - this.requestUpdate(); + private _continuity = this._newContinuityController(); + private _continuityUnsub?: () => void; + private _continuityEpoch = 0; + private _continuityDataReady = true; + private _continuityPaintToken = -1; + private _continuityDisposed = false; + private _renderSnapshotAt = Date.now(); + private _hassSequence = 0; + private _lastValidStageSize: [number, number] | null = null; + private _pendingRefitSize: [number, number] | null = null; + private _refitRaf = 0; + + private _newContinuityController(): VisualContinuityController { + return new VisualContinuityController(() => { + this._resumeSettling = this._continuity.state !== 'steady'; + this._continuityEpoch++; + if (this.isConnected) this.requestUpdate(); + }); + } + + private _pageVisibility = (signal: PageVisibilitySignal): void => { + const token = this._continuity.visibility(signal); + if (signal.kind === 'hidden') return; + this._vacJumpOnce = true; + if (!signal.long) return; // strict quick-return no-op: preserve hover and DOM + // A long sleep may have frozen the sky/activity clocks. Recompute them as + // one candidate without clearing the currently visible hover or frame, + // and revalidate config+layout as one structural pair. + this._skyElev = null; + if (Date.now() - this._renderSnapshotAt > 1000) this._continuity.note('device-snapshot-stale'); + this._continuityDataReady = false; + this._continuityPaintToken = -1; + this._resumeSettling = true; + if (token === this._continuity.token) { + if (!this._loading) void this._loadFromServer(); + else this.requestUpdate(); } }; private _resumeSettling = false; - private _resumeRaf = 0; - private _resumeStarted = 0; - private _resumeLastSize = ''; - private _resumeLastChange = 0; private _viewportInvalidAt = 0; private _vacFit: { markerId: string; source: string; mapId: string; p: FitParams; drag: null | { kind: 'move' | 'scale'; sx: number; sy: number; p0: FitParams; @@ -1283,6 +1289,7 @@ class HouseplanCard extends LitElement { _booting: { state: true }, _bootFading: { state: true }, _bootSoft: { state: true }, + _continuityEpoch: { state: true }, _tapConfirm: { state: true }, hass: { attribute: false }, _config: { state: true }, @@ -1356,11 +1363,19 @@ class HouseplanCard extends LitElement { }; public connectedCallback(): void { - document.addEventListener('visibilitychange', this._vacVisHandler); + if (this._continuityDisposed) { + this._continuity = this._newContinuityController(); + this._continuityDisposed = false; + this._continuityPaintToken = -1; + } + const resolvedBlend = resolvedSvgScreenBlend(this.ownerDocument); + if (resolvedBlend !== undefined) this._glowScreenBlend = resolvedBlend; + this._continuityUnsub?.(); + this._continuityUnsub = subscribePageVisibility(this.ownerDocument, this._pageVisibility); super.connectedCallback(); svgScreenBlendSupported(this.ownerDocument).then((supported) => { - if (!supported || this._glowScreenBlend) return; - this._glowScreenBlend = true; + if (supported === this._glowScreenBlend) return; + this._glowScreenBlend = supported; if (this.isConnected) this.requestUpdate(); }); if (this.hass) this._ensureHaRegistryAuthority(); @@ -1398,16 +1413,9 @@ class HouseplanCard extends LitElement { this._warmRevivePending = true; this._warmReviveTimer = window.setTimeout(() => this._warmReviveDialog(), 0); } - // A successor created by Lovelace after the visibility event did not see - // that event itself. Module time bridges that small reconnect gap; if no - // card instance survived to hear `visible`, consume the pending hidden - // timestamp here instead. - const now = Date.now(); - if (document.visibilityState === 'visible' && pageHiddenAt) { - if (now - pageHiddenAt >= RESUME_LONG_HIDDEN_MS) pageResumedAt = now; - pageHiddenAt = 0; - } - if (this._warmLongReturn || now - pageResumedAt <= RESUME_RECENT_MS) this._beginResumeSettle(); + // If no card survived the document event, the placement tombstone still + // carries its detach age and enters the same shared controller here. + if (this._warmLongReturn) this._beginResumeSettle(); this._warmLongReturn = false; // Transient navigation/resume fields are intentionally not reactive. A // same-element reconnect keeps Lit's previous DOM, so explicitly repaint @@ -1416,9 +1424,10 @@ class HouseplanCard extends LitElement { } public disconnectedCallback(): void { - document.removeEventListener('visibilitychange', this._vacVisHandler); + this._continuityUnsub?.(); + this._continuityUnsub = undefined; if (this._vacRaf) { cancelAnimationFrame(this._vacRaf); this._vacRaf = 0; } - if (this._resumeRaf) { cancelAnimationFrame(this._resumeRaf); this._resumeRaf = 0; } + if (this._refitRaf) { cancelAnimationFrame(this._refitRaf); this._refitRaf = 0; } if (this._skySnapRaf) { cancelAnimationFrame(this._skySnapRaf); this._skySnapRaf = 0; } for (const rt of this._activityRt.values()) clearTimeout(rt.timer); // pending activity-window repaints window.removeEventListener('keydown', this._keyHandler); @@ -1429,6 +1438,8 @@ class HouseplanCard extends LitElement { clearTimeout(this._loadRetryTimer); this._loadRetryTimer = undefined; // a cleared id must not block a reschedule this._connHooked?.removeEventListener?.('ready', this._onConnReady); + this._connHooked?.removeEventListener?.('disconnected', this._onConnLost); + this._connHooked?.removeEventListener?.('reconnect-error', this._onConnLost); this._connHooked = null; this._haRegistryRelease?.(); this._haRegistryRelease = undefined; @@ -1471,6 +1482,9 @@ class HouseplanCard extends LitElement { clearTimeout(this._duplicateColumnTimer); for (const timer of this._glowFadeTimers.values()) clearTimeout(timer); for (const raf of this._glowEnterRafs.values()) cancelAnimationFrame(raf); + clearTimeout(this._glowFeatherResumeTimer); + this._glowFeatherResumeTimer = 0; + this._glowFeatherSuspendUntil = 0; this._glowFadeTimers.clear(); this._glowEnterRafs.clear(); this._glowEnteringSources.clear(); @@ -1505,6 +1519,8 @@ class HouseplanCard extends LitElement { // after the disconnect snapshot (which must still skip unstable geometry), // so a same-element reattach can start fresh instead of keeping the veil. this._resumeSettling = false; + this._continuity.dispose(); + this._continuityDisposed = true; super.disconnectedCallback(); } @@ -1797,7 +1813,10 @@ class HouseplanCard extends LitElement { this._serverCfg = c.config; this._cfgEpoch++; this._cfgRev = c.rev || 0; + this._cfgContentFingerprint = c.config_fingerprint || contentFingerprint(c.config); this._layout = c.layout || {}; + this._layoutRev = c.layout_rev || 0; + this._layoutContentFingerprint = c.layout_fingerprint || contentFingerprint(this._layout); this._serverStorage = true; const hs = this._hashSpace(); const nav = this._savedNav(); @@ -1855,15 +1874,17 @@ class HouseplanCard extends LitElement { // is waiting for us, and our live state is newer than anything in it const mine = list.find((s) => s.owner === this._warmGen); if (mine) { - this._warmLongReturn = !!mine.freed && Date.now() - mine.freed >= RESUME_LONG_HIDDEN_MS; + this._warmLongReturn = !!mine.freed && Date.now() - mine.freed >= CONTINUITY_LONG_HIDDEN_MS; clearTimeout(mine.evict); mine.evict = 0; mine.freed = 0; mine.live = true; this._warmSlot = mine; this._warmKey = key; + if (mine.frameFingerprint) this._continuity.adoptCompleteFrame(mine.frameFingerprint); + if (!this._devices.length && mine.devices?.length) this._devices = [...mine.devices]; return; } const { slot, sure } = warmMatch(list, this._warmGen, place, idx); if (!slot) return; - this._warmLongReturn = !!slot.freed && Date.now() - slot.freed >= RESUME_LONG_HIDDEN_MS; + this._warmLongReturn = !!slot.freed && Date.now() - slot.freed >= CONTINUITY_LONG_HIDDEN_MS; this._booting = false; this._bootFading = false; this._hdrH = slot.hdrH; @@ -1882,6 +1903,8 @@ class HouseplanCard extends LitElement { slot.live = true; this._warmSlot = slot; this._warmVp = slot.vp; // adopted below, once the model is in hand + if (slot.frameFingerprint) this._continuity.adoptCompleteFrame(slot.frameFingerprint); + if (!this._devices.length && slot.devices?.length) this._devices = [...slot.devices]; this._warmAdoptViewport(this._config!); } else { // Two identical cards on one view and no way to tell which slot is ours: @@ -1889,7 +1912,8 @@ class HouseplanCard extends LitElement { // viewport and the dialog belong to somebody and must not be guessed. this._warmSlot = { owner: this._warmGen, place: place ? new WeakRef(place) : null, idx, live: true, - hdrH: slot.hdrH, stageH: slot.stageH, vp: null, dlg: null, freed: 0, evict: 0, + hdrH: slot.hdrH, stageH: slot.stageH, vp: null, + frameFingerprint: '', devices: null, dlg: null, freed: 0, evict: 0, }; list.push(this._warmSlot); this._warmTrim(list); @@ -2007,7 +2031,8 @@ class HouseplanCard extends LitElement { this._warmKey = k; this._warmSlot = { owner: this._warmGen, place: place ? new WeakRef(place) : null, idx: this._warmIdx(place), live: true, - hdrH: this._hdrH, stageH: 0, vp: null, dlg: null, freed: 0, evict: 0, + hdrH: this._hdrH, stageH: 0, vp: null, + frameFingerprint: '', devices: null, dlg: null, freed: 0, evict: 0, }; const list = warmBoot.get(k) || []; list.push(this._warmSlot); @@ -2087,10 +2112,16 @@ class HouseplanCard extends LitElement { * next one must open at — and a dialog closed with Esc/Cancel/Save writes * `dlg: null` here on the very next render, so it can never come back. */ private _warmSnapshot(): void { - // The hidden resume frame still carries the pre-suspension viewport. Do - // not replace the warm memo with it; the settled frame writes the truth. - if (this._booting || this._resumeSettling || this._config?.kiosk) return; - const patch: Partial = { vp: this._warmViewportState() }; + // A candidate may already exist in fields while the DOM still presents the + // previous complete frame. Never pair those fields with the old frame + // fingerprint in the placement memo; the successful paint barrier writes + // the coherent snapshot from its completion callback. + if (this._booting || this._config?.kiosk || this._continuity.state !== 'steady') return; + const patch: Partial = { + vp: this._warmViewportState(), + frameFingerprint: this._continuity.frameFingerprint, + devices: this._devices, + }; // do not overwrite the snapshot we are about to revive FROM if (!this._warmRevivePending) patch.dlg = this._warmDialogState(); // AUD-159B1-01: the placement is re-measured from the live DOM, so a card @@ -2165,12 +2196,145 @@ class HouseplanCard extends LitElement { private _cacheSnapshot(): void { if (!this._serverCfg) return; try { - localStorage.setItem(LS_CFG, JSON.stringify({ config: this._serverCfg, rev: this._cfgRev, layout: this._layout })); + // Local edits may mutate nested config/layout before the debounced write; + // keep the accepted identity paired with exactly what is cached. + this._cfgContentFingerprint = contentFingerprint(this._serverCfg); + this._layoutContentFingerprint = contentFingerprint(this._layout); + localStorage.setItem(LS_CFG, JSON.stringify({ + config: this._serverCfg, + rev: this._cfgRev, + config_fingerprint: this._cfgContentFingerprint, + layout: this._layout, + layout_rev: this._layoutRev, + layout_fingerprint: this._layoutContentFingerprint, + })); } catch { /* ignore */ } } + private _beginContinuityCandidate( + reason: string, + dataReady: boolean, + recoveryReason: 'plan' | 'connection' | 'stage-size' | 'asset' = 'plan', + ): number { + // Cold boot remains under the existing boot veil; #73 governs a frame + // that has already been complete at least once. + if (this._booting && !this._continuity.hasCompleteFrame) return this._continuity.token; + this._continuityDataReady = dataReady; + this._continuityPaintToken = -1; + this._resumeSettling = true; + return this._continuity.beginCandidate(reason, recoveryReason); + } + + private _continuityStageValid(): boolean { + const stage = this._stageEl; + return !!stage && stage.clientWidth > 0 && stage.clientHeight > 0; + } + + private _continuityAssetsReady(): boolean { + const space = this._model.length ? this._spaceModel() : null; + return !space?.bg?.href || this._signer.isReady(this.hass, space.bg.href); + } + + private _candidateBackdrop(config: ServerConfig | null, spaceId = this._space): string { + const models = spaceModels(config); + const hashSpace = this._hashSpace(); + const savedSpace = this._savedNav()?.space || ''; + const preferred = !this._hashApplied && models.some((space) => space.id === hashSpace) ? hashSpace + : !this._hashApplied && !this._navApplied && models.some((space) => space.id === savedSpace) ? savedSpace + : models.some((space) => space.id === spaceId) ? spaceId : models[0]?.id; + return models.find((space) => space.id === preferred)?.bg?.href || ''; + } + + private _visualFrameFingerprint(): string { + const stage = this._stageEl; + const size = stage ? [stage.clientWidth, stage.clientHeight] : [0, 0]; + return visualFrameFingerprint([ + this._cfgRev, + this._cfgContentFingerprint || contentFingerprint(this._serverCfg), + this._layoutRev, + this._layoutContentFingerprint || contentFingerprint(this._layout), + this._space, + this._mode, + this._view, + size, + this._glowScreenBlend ? 'screen' : 'normal', + this._hassSequence, + this.hass?.themes?.darkMode ?? this.hass?.themes?.default_theme ?? '', + ]); + } + + /** Called from updated(): one token owns at most one paint barrier. */ + private _settleContinuityFrame(): void { + if (this._booting || !this._continuityStageValid()) return; + if (!this._continuity.hasCompleteFrame && this._continuity.state === 'steady') { + if (!this._continuityAssetsReady()) { + this._beginContinuityCandidate('asset-wait', true, 'asset'); + return; + } + this._renderSnapshotAt = Date.now(); + this._continuity.markCompleteFrame(this._visualFrameFingerprint()); + return; + } + if (!this._continuityDataReady || !this._continuityAssetsReady()) return; + if (!['holding', 'offline-stale', 'overlay-pending', 'overlay-visible', 'candidate-ready'] + .includes(this._continuity.state)) return; + const token = this._continuity.token; + if (this._continuityPaintToken === token) return; + this._continuityPaintToken = token; + if (!this._continuity.candidateReady(token)) return; + void this._continuity.commitAfterPaint(token, { + updateComplete: () => this.updateComplete, + stageValid: () => this.isConnected && this._continuityStageValid(), + assetsReady: () => this._continuityAssetsReady(), + frameFingerprint: () => this._visualFrameFingerprint(), + }).then((committed) => { + if (!committed || token !== this._continuity.token) { + if (token === this._continuity.token) this._continuityPaintToken = -1; + return; + } + this._resumeSettling = false; + this._renderSnapshotAt = Date.now(); + this._warmSnapshot(); + }); + } + + private _onBackdropLoaded(raw: string): void { + this._signer.markLoaded(this.hass, raw); + this._continuity.note('asset-ready'); + this._continuityPaintToken = -1; + if (this._continuity.state !== 'steady') this.requestUpdate(); + } + + private _retryContinuity = (): void => { + this._continuityDataReady = false; + this._continuityPaintToken = -1; + this._continuity.retry(this._continuity.recoveryReason || 'plan'); + if (!this._loading) void this._loadFromServer(); + }; + + private _renderRecoveryOverlay(): TemplateResult | typeof nothing { + if (!this._continuity.overlayVisible && this._continuity.state !== 'recovery-error') return nothing; + const connection = this._continuity.recoveryReason === 'connection'; + return html`
event.stopPropagation()} + @click=${(event: Event) => event.stopPropagation()} + @wheel=${(event: Event) => event.stopPropagation()}> + + ${this._t(connection ? 'continuity.restore_connection' : 'continuity.restore_plan')} + ${this._continuity.state === 'recovery-error' + ? html`` + : nothing} +
`; + } + + /** Redacted lifecycle diagnostics used by the deterministic sampler. */ + public houseplanContinuityTrace(): readonly import('./visual-continuity').ContinuityTraceEvent[] { + return this._continuity.trace; + } + public getCardSize(): number { return 12; } @@ -2360,6 +2524,9 @@ class HouseplanCard extends LitElement { if (changed.has('_serverCfg')) this._cfgEpoch++; this._skyPlan(); if (changed.has('hass') && this.hass) { + this._hassSequence++; + this._renderSnapshotAt = Date.now(); + this._continuity.note('hass-snapshot'); this._ensureHaRegistryAuthority(); this._planHassMemo = null; this._hookConnection(); @@ -2372,6 +2539,9 @@ class HouseplanCard extends LitElement { this._vacTick(); this._activityTick(); } + if (this._continuity.hasCompleteFrame && this._continuity.state === 'steady') { + this._continuity.refreshCompleteFrame(this._visualFrameFingerprint()); + } } protected updated(): void { @@ -2423,6 +2593,7 @@ class HouseplanCard extends LitElement { } if (stage && !this._view) this._refitView(); this._editorSecondary.afterRender(); + this._settleContinuityFrame(); // onboarding: on an empty server config, open the space dialog right away if ( this._serverStorage && @@ -2444,14 +2615,82 @@ class HouseplanCard extends LitElement { // ================= server: config + layout ================= + /** + * Adopt one structural WS candidate by revision + actual content identity. + * Equal payloads keep their authoritative object references and geometry + * epoch; equal revisions never hide changed content (#73 §9.4). + */ + private _adoptStructuralResponses( + cfgResp: any, + layResp?: any, + layoutOverride?: Record, + ): { configChanged: boolean; layoutChanged: boolean } { + const rawConfig = cfgResp?.config; + const nextConfig = rawConfig && Array.isArray(rawConfig.spaces) + ? rawConfig as ServerConfig : null; + const nextCfgFingerprint = contentFingerprint(nextConfig); + const configChanged = nextCfgFingerprint !== (this._cfgContentFingerprint + || contentFingerprint(this._serverCfg)); + if (configChanged) { + // A genuinely different baseline invalidates local geometry undo. A + // reconnect echo with identical content deliberately does not. + this._geometryHistory.clear(); + if (this._serverCfg) this._clearGeometryGesture(); + this._serverCfg = nextConfig; + this._cfgContentFingerprint = nextCfgFingerprint; + } + this._cfgRev = cfgResp?.rev ?? this._cfgRev; + + let layoutChanged = false; + if (layResp !== undefined || layoutOverride !== undefined) { + const nextLayout = layoutOverride ?? layResp?.layout ?? {}; + const nextLayoutFingerprint = contentFingerprint(nextLayout); + layoutChanged = nextLayoutFingerprint !== (this._layoutContentFingerprint + || contentFingerprint(this._layout)); + if (layoutChanged) { + this._layout = nextLayout; + this._layoutContentFingerprint = nextLayoutFingerprint; + } + this._layoutRev = layResp?.rev ?? this._layoutRev; + } + + this._canOptimizeUndo = !!(cfgResp?.can_optimize_undo || layResp?.can_optimize_undo); + if (typeof cfgResp?.can_write === 'boolean') this._serverCanWrite = cfgResp.can_write; + if (configChanged) this._continuity.note('config-candidate', { configRev: this._cfgRev }); + if (layoutChanged) this._continuity.note('layout-candidate', { layoutRev: this._layoutRev }); + return { configChanged, layoutChanged }; + } + private async _loadFromServer(): Promise { this._loading = true; this._loadTries++; + const visibleSpace = this._space; + const hadViewport = !!this._view; try { const [cfgResp, layResp] = await Promise.all([ this.hass.callWS({ type: 'houseplan/config/get' }), this.hass.callWS({ type: 'houseplan/layout/get' }), ]); + const candidateConfig = cfgResp?.config && Array.isArray(cfgResp.config.spaces) + ? cfgResp.config : null; + const structuralChanged = contentFingerprint(candidateConfig) + !== (this._cfgContentFingerprint || contentFingerprint(this._serverCfg)) + || contentFingerprint(layResp?.layout ?? {}) + !== (this._layoutContentFingerprint || contentFingerprint(this._layout)); + if (structuralChanged) { + const assetReady = await this._signer.prepareImage( + this.hass, this._candidateBackdrop(candidateConfig), + ); + if (!assetReady) { + this._continuity.note('asset-failed'); + this._scheduleLoadRetry(true); + return; + } + } + if (structuralChanged && this._continuity.hasCompleteFrame + && this._continuity.state === 'steady') { + this._beginContinuityCandidate('structural-response', true); + } this._loadOk = true; this._serverStorage = true; // absent can_write = older backend / demo stub → keep null (legacy admin fallback) @@ -2461,16 +2700,7 @@ class HouseplanCard extends LitElement { this._mode = this._pendingNavMode; this._pendingNavMode = null; } - const cfg = cfgResp?.config; - // A server reload is a new history baseline. Replaying snapshots made - // against an older revision would overwrite somebody else's geometry. - this._geometryHistory.clear(); - if (this._serverCfg) this._clearGeometryGesture(); - this._serverCfg = cfg && Array.isArray(cfg.spaces) ? cfg : null; - this._cfgEpoch++; - this._cfgRev = cfgResp?.rev || 0; - this._layout = layResp?.layout || {}; - this._layoutRev = layResp?.rev ?? 0; + this._adoptStructuralResponses(cfgResp, layResp); // live sync: the config was changed in another window → re-read it if (!this._unsubCfg) { this._unsubCfg = await this.hass.connection.subscribeEvents((ev: any) => { @@ -2524,7 +2754,7 @@ class HouseplanCard extends LitElement { // reported jerk. Only a genuine navigation (the hash/nav landed us on // another space) still needs it. if (this._warmVpArmed && this._space === this._warmVp?.space) this._warmVpArmed = false; - else this._restoreZoom(); + else if (!hadViewport || this._space !== visibleSpace) this._restoreZoom(); } catch (e) { if (this._serverCfg) { // DEV-B703-02: this instance already RENDERS a valid config (the LS @@ -2534,7 +2764,7 @@ class HouseplanCard extends LitElement { // revalidate: the last valid config stays on screen until a // successful reload replaces it, and revalidation keeps running on // our own clock (willUpdate stops driving loads after 8 tries). - this._scheduleLoadRetry(); + this._scheduleLoadRetry(true); } else if (this._loadTries >= 8) { // nothing was ever shown — genuine no-backend: local-only fallback this._serverStorage = false; @@ -2549,6 +2779,7 @@ class HouseplanCard extends LitElement { } finally { this._loading = false; } + this._continuityDataReady = true; this._regSignature = ''; this._maybeRebuildDevices(); this.requestUpdate(); @@ -2576,16 +2807,21 @@ class HouseplanCard extends LitElement { return; } } + this._beginContinuityCandidate('config-reload', false); try { const resp = await this.hass.callWS({ type: 'houseplan/config/get' }); - const cfg = resp?.config; - this._geometryHistory.clear(); - this._clearGeometryGesture(); - this._serverCfg = cfg && Array.isArray(cfg.spaces) ? cfg : null; - this._cfgEpoch++; - this._cfgRev = resp?.rev || 0; - this._canOptimizeUndo = !!resp?.can_optimize_undo; - if (typeof resp?.can_write === 'boolean') this._serverCanWrite = resp.can_write; + const candidateConfig = resp?.config && Array.isArray(resp.config.spaces) + ? resp.config as ServerConfig : null; + const configChanged = contentFingerprint(candidateConfig) + !== (this._cfgContentFingerprint || contentFingerprint(this._serverCfg)); + if (configChanged && !await this._signer.prepareImage( + this.hass, this._candidateBackdrop(candidateConfig), + )) { + this._continuity.note('asset-failed'); + this._scheduleLoadRetry(true); + return; + } + this._adoptStructuralResponses(resp); if (this._pendingNavMode && this._canEdit && !this._config?.kiosk) { this._mode = this._pendingNavMode; this._pendingNavMode = null; @@ -2598,6 +2834,9 @@ class HouseplanCard extends LitElement { // a failed reload leaves the card on its last known config; tell the user // rather than silently diverging from the server (audit L2 note) this._showToast(this._t('toast.cfg_reload_failed', { err: this._errText(e) })); + } finally { + this._continuityDataReady = true; + this.requestUpdate(); } } @@ -2608,12 +2847,12 @@ class HouseplanCard extends LitElement { * after 8 tries). Exponential backoff capped at 8 s; single timer; * cleared on disconnect and on a connection 'ready'. */ private _loadRetryTimer?: number; - private _scheduleLoadRetry(): void { + private _scheduleLoadRetry(force = false): void { if (this._loadRetryTimer !== undefined) return; const delay = Math.min(8000, 500 * 2 ** Math.min(4, Math.max(1, this._loadTries - 7))); this._loadRetryTimer = window.setTimeout(() => { this._loadRetryTimer = undefined; - if (!this._loadOk && !this._loading && this.hass) this._loadFromServer(); + if ((force || !this._loadOk) && !this._loading && this.hass) this._loadFromServer(); }, delay); } @@ -2751,17 +2990,29 @@ class HouseplanCard extends LitElement { clearTimeout(this._loadRetryTimer); this._loadRetryTimer = undefined; refreshHaRegistries(this.hass); + this._beginContinuityCandidate('connection-ready', false, 'connection'); if (this._loading) return; // a subscribe lost mid-load leaves _loadOk=true without _unsubCfg — the // full load path repairs both (every subscribe in it is guarded) - if (!this._loadOk || !this._unsubCfg) this._loadFromServer(); - else this._reloadConfigOnly(); + // Re-read config and layout as one candidate. `_loadFromServer` now adopts + // each side by revision+fingerprint and preserves equal references. + this._loadFromServer(); + }; + private _onConnLost = (): void => { + if (this._booting && !this._continuity.hasCompleteFrame) return; + this._continuityDataReady = false; + this._continuityPaintToken = -1; + this._continuity.connectionLost(); }; private _hookConnection(): void { const conn = (this.hass as any)?.connection; if (!conn || conn === this._connHooked) return; this._connHooked?.removeEventListener?.('ready', this._onConnReady); + this._connHooked?.removeEventListener?.('disconnected', this._onConnLost); + this._connHooked?.removeEventListener?.('reconnect-error', this._onConnLost); conn.addEventListener?.('ready', this._onConnReady); + conn.addEventListener?.('disconnected', this._onConnLost); + conn.addEventListener?.('reconnect-error', this._onConnLost); this._connHooked = conn; } /** @@ -2820,6 +3071,7 @@ class HouseplanCard extends LitElement { */ private async _reloadLayoutOnly(): Promise { if (!this._serverStorage || !this.hass?.callWS) return; + this._beginContinuityCandidate('layout-reload', false); // Snapshot BEFORE flushing. `flush()` runs the debounced writer // synchronously, and the first thing that does is empty `_dirtyPos` — so // reading the dirty set afterwards found nothing to protect and the server's @@ -2837,13 +3089,20 @@ class HouseplanCard extends LitElement { const remote = resp?.layout || {}; const merged: Record = { ...remote }; for (const [id, pos] of mine) merged[id] = pos; - this._layout = merged; + const fingerprint = contentFingerprint(merged); + if (fingerprint !== (this._layoutContentFingerprint || contentFingerprint(this._layout))) { + this._layout = merged; + this._layoutContentFingerprint = fingerprint; + } this._layoutRev = resp?.rev ?? this._layoutRev; this._canOptimizeUndo = !!resp?.can_optimize_undo; this._cacheSnapshot(); this.requestUpdate(); } catch { /* a failed refresh just leaves the positions we already had */ + } finally { + this._continuityDataReady = true; + this.requestUpdate(); } } @@ -3615,7 +3874,7 @@ class HouseplanCard extends LitElement { } /** Set the zoom (centered on vb point cx,cy, or on the center of the current view). */ - private _applyView(zoom: number, cx?: number, cy?: number): void { + private _applyView(zoom: number, cx?: number, cy?: number): boolean { const vb = this._baseVb(); const fit = fitView(vb, this._stageAspect()); const z = Math.min(HouseplanCard.ZOOM_MAX, Math.max(HouseplanCard.ZOOM_MIN, zoom)); @@ -3623,8 +3882,17 @@ class HouseplanCard extends LitElement { const cur = this._viewOr(vb); const ccx = cx ?? cur.x + cur.w / 2; const ccy = cy ?? cur.y + cur.h / 2; + const next = this._clampView({ x: ccx - w / 2, y: ccy - h / 2, w, h }, fit); + const same = this._view + && Math.abs(this._zoom - z) < 1e-9 + && Math.abs(this._view.x - next.x) < 1e-6 + && Math.abs(this._view.y - next.y) < 1e-6 + && Math.abs(this._view.w - next.w) < 1e-6 + && Math.abs(this._view.h - next.h) < 1e-6; + if (same) return false; this._zoom = z; - this._view = this._clampView({ x: ccx - w / 2, y: ccy - h / 2, w, h }, fit); + this._view = next; + return true; } /** @@ -3686,56 +3954,18 @@ class HouseplanCard extends LitElement { this._bootSoft = false; } - /** Hide only the unstable plan frames after a genuinely long tab sleep. - * The header remains usable and the existing stage background stays put; - * once its measured size has been quiet, viewport + reveal land in the - * same Lit update. */ + /** Hold the last complete frame after a long sleep; never hide the scene. */ private _beginResumeSettle(): void { - if (this._kiosk || this._mode !== 'view' || this._booting || this._resumeSettling) return; - this._resumeSettling = true; - this._resumeStarted = performance.now(); - this._resumeLastSize = ''; - this._resumeLastChange = this._resumeStarted; + if (this._booting || this._resumeSettling) return; this._viewportInvalidAt = 0; - this.requestUpdate(); - if (this._resumeRaf) cancelAnimationFrame(this._resumeRaf); - this._resumeRaf = requestAnimationFrame(() => this._resumeSettleTick()); - } - - private _resumeSettleTick(): void { - this._resumeRaf = 0; - if (!this._resumeSettling || !this.isConnected) return; - if (this._kiosk || this._mode !== 'view') { - this._resumeSettling = false; - this.requestUpdate(); - return; - } - const now = performance.now(); - const stage = this._stageEl; - const measurable = !!stage && stage.clientWidth > 0 && stage.clientHeight > 0; - if (measurable) { - const size = `${stage!.clientWidth}x${stage!.clientHeight}`; - if (size !== this._resumeLastSize) { - this._resumeLastSize = size; - this._resumeLastChange = now; - } - } - const elapsed = now - this._resumeStarted; - const settled = measurable && elapsed >= RESUME_MIN_MS && now - this._resumeLastChange >= RESUME_QUIET_MS; - if (settled || elapsed >= RESUME_MAX_MS) { - const cur = this._view; - this._resumeSettling = false; - if (measurable) { - this._applyView( - this._zoom, - cur ? cur.x + cur.w / 2 : undefined, - cur ? cur.y + cur.h / 2 : undefined, - ); - } - this.requestUpdate(); - return; - } - this._resumeRaf = requestAnimationFrame(() => this._resumeSettleTick()); + const token = this._beginContinuityCandidate('warm-resume', false); + // A card can be detached for the whole visibility event and reattached as + // the same already-loaded instance. In that case `willUpdate()` has no + // reason to reload, so the warm tombstone must start its own revalidation + // instead of immediately blessing the stale frame as current. + if (token !== this._continuity.token) return; + if (!this._loading) void this._loadFromServer(); + else this.requestUpdate(); } /** Recompute the view for a new scene size, preserving zoom and center. */ @@ -3748,20 +3978,51 @@ class HouseplanCard extends LitElement { if (!this._viewportInvalidAt) this._viewportInvalidAt = Date.now(); return; } - if (this._viewportInvalidAt) { - const invalidFor = Date.now() - this._viewportInvalidAt; - this._viewportInvalidAt = 0; - // Lovelace's own view tabs can hide a card without hiding `document`. - // A long zero-box interval is the equivalent suspension signal. - if (invalidFor >= RESUME_LONG_HIDDEN_MS && !this._resumeSettling) { - this._beginResumeSettle(); - if (this._resumeSettling) return; - } + const size: [number, number] = [stage.clientWidth, stage.clientHeight]; + const previous = this._lastValidStageSize; + const sameSize = !!previous + && Math.abs(previous[0] - size[0]) <= 0.5 + && Math.abs(previous[1] - size[1]) <= 0.5; + const invalidFor = this._viewportInvalidAt ? Date.now() - this._viewportInvalidAt : 0; + this._viewportInvalidAt = 0; + if (!previous) { + this._lastValidStageSize = size; + if (!this._view) this._applyView(this._zoom); + return; } - if (this._resumeSettling) return; - const cur = this._view; - this._applyView(this._zoom, cur ? cur.x + cur.w / 2 : undefined, cur ? cur.y + cur.h / 2 : undefined); - this.requestUpdate(); + // 0x0 -> the same positive size is explicitly a no-op. + if (sameSize) { + this._pendingRefitSize = null; + return; + } + this._pendingRefitSize = size; + if (this._refitRaf) return; + this._refitRaf = requestAnimationFrame(() => { + this._refitRaf = 0; + const target = this._pendingRefitSize; + this._pendingRefitSize = null; + const live = this._stageEl; + if (!target || !live || live.clientWidth <= 0 || live.clientHeight <= 0) return; + if (Math.abs(live.clientWidth - target[0]) > 0.5 + || Math.abs(live.clientHeight - target[1]) > 0.5) { + this._refitView(); + return; + } + const old = this._lastValidStageSize; + if (old && Math.abs(old[0] - target[0]) <= 0.5 && Math.abs(old[1] - target[1]) <= 0.5) return; + this._lastValidStageSize = target; + const cur = this._view; + if (invalidFor >= CONTINUITY_LONG_HIDDEN_MS) { + this._beginContinuityCandidate('stage-size-restored', true, 'stage-size'); + } else if (this._continuity.hasCompleteFrame) { + this._beginContinuityCandidate('stage-resize', true, 'stage-size'); + } + this._applyView( + this._zoom, + cur ? cur.x + cur.w / 2 : undefined, + cur ? cur.y + cur.h / 2 : undefined, + ); + }); } /** Change the zoom while keeping the point (sx,sy relative to the scene) in place. */ @@ -4067,6 +4328,7 @@ class HouseplanCard extends LitElement { this._decorMove = null; return; } + const viewportGestureEnded = !!this._pinchStart || !!this._panStart; this._pointers.delete(ev.pointerId); if (this._pointers.size < 2) this._pinchStart = null; if (this._pointers.size === 0) { @@ -4075,6 +4337,7 @@ class HouseplanCard extends LitElement { // reset click suppression on the next tick (so that a click right after a pan does not fire) setTimeout(() => (this._suppressClick = false), 0); } + if (viewportGestureEnded && this._pointers.size === 0) this.requestUpdate(); } private _clickRoom(r: RoomCfg): void { @@ -4756,12 +5019,14 @@ class HouseplanCard extends LitElement { return; } this._cancelBoundaryAnchor(); + const viewportGestureEnded = !!this._pinchStart || !!this._panStart; this._pointers.delete(ev.pointerId); if (this._pointers.size < 2) this._pinchStart = null; if (this._pointers.size === 0) { this._panStart = null; this._panLock = null; } + if (viewportGestureEnded && this._pointers.size === 0) this.requestUpdate(); } private _applyGeometryState(state: SpaceGeometryState): boolean { @@ -8826,15 +9091,15 @@ class HouseplanCard extends LitElement { * opposite (outward) wall face. Open spans stay dashed in their own layer; * doors, windows and gates remain gaps instead of being bridged by a solid accent. */ - private _renderRoomHover(space: SpaceModel): TemplateResult { + private _roomHoverPaths(space: SpaceModel): { fillD: string; outlineD: string } | null { const hover = this._hoverRoom; if (this._mode !== 'view' || !hover || hover.space !== space.id) { - return svg`` as unknown as TemplateResult; + return null; } const room = space.rooms.find((r) => r === hover.room || (!!r.id && r.id === hover.room.id)); - if (!room) return svg`` as unknown as TemplateResult; + if (!room) return null; const poly = roomPoly(room); - if (!poly) return svg`` as unknown as TemplateResult; + if (!poly) return null; // A parent room also owns the floor-facing side of walls around rooms // nested inside it. Keep the room together with its cached polygon so a @@ -8933,15 +9198,30 @@ class HouseplanCard extends LitElement { .map((sg) => `M ${sg[0]} ${sg[1]} L ${sg[2]} ${sg[3]}`) .join(' '); }).filter(Boolean).join(' '); - if (!d) return svg`` as unknown as TemplateResult; + if (!d) return null; const pathOf = (points: number[][]) => `M ${points.map((p) => `${p[0]} ${p[1]}`).join(' L ')} Z`; const cleanFloor = this._cleanFloor(room, floor, space).path || pathOf(floor); const fillD = [cleanFloor, ...islandPolys.map(pathOf)].join(' '); - return svg` - - - + return { fillD, outlineD: d }; + } + + private _renderRoomHoverFill( + paths: { fillD: string; outlineD: string } | null, + ): TemplateResult { + if (!paths) return svg`` as unknown as TemplateResult; + return svg` + + ` as unknown as TemplateResult; + } + + private _renderRoomHoverOutline( + paths: { fillD: string; outlineD: string } | null, + ): TemplateResult { + if (!paths) return svg`` as unknown as TemplateResult; + return svg` + + ` as unknown as TemplateResult; } @@ -11286,6 +11566,7 @@ class HouseplanCard extends LitElement { this._glowFadeTimers.delete(key); } if (domId == null) { + this._suspendGlowFeatherForTransition(); domId = ++this._glowSourceSeq; this._glowRenderedSources.set(key, domId); this._glowEnteringSources.add(key); @@ -11305,6 +11586,7 @@ class HouseplanCard extends LitElement { this._glowEnterRafs.delete(key); this._glowEnteringSources.delete(key); if (!this._glowFadeTimers.has(key)) { + this._suspendGlowFeatherForTransition(); const timer = window.setTimeout(() => { if (this._glowFadeTimers.get(key) !== timer) return; this._glowFadeTimers.delete(key); @@ -11317,6 +11599,31 @@ class HouseplanCard extends LitElement { return { domId, entering: false, leaving: true }; } + /** A whole-layer Gaussian blur is needlessly re-evaluated for every frame + * of a source opacity transition. The 1 px penumbra is imperceptible while + * the pool itself is moving between transparent and opaque, so bypass it + * for that bounded interval and restore it once at the settled frame. */ + private _suspendGlowFeatherForTransition(): void { + if (window.matchMedia?.('(prefers-reduced-motion: reduce)').matches) return; + this._glowFeatherSuspendUntil = Math.max( + this._glowFeatherSuspendUntil, Date.now() + GLOW_FADE_MS, + ); + clearTimeout(this._glowFeatherResumeTimer); + const delay = Math.max(0, this._glowFeatherSuspendUntil - Date.now()) + 17; + const resume = () => { + this._glowFeatherResumeTimer = 0; + if (Date.now() < this._glowFeatherSuspendUntil) { + this._glowFeatherResumeTimer = window.setTimeout( + resume, this._glowFeatherSuspendUntil - Date.now() + 17, + ); + return; + } + this._glowFeatherSuspendUntil = 0; + if (this.isConnected) this.requestUpdate(); + }; + this._glowFeatherResumeTimer = window.setTimeout(resume, delay); + } + private _forgetGlowSource(key: string): void { const timer = this._glowFadeTimers.get(key); if (timer != null) clearTimeout(timer); @@ -11342,7 +11649,7 @@ class HouseplanCard extends LitElement { * Opaque: the wall bodies exactly as the plan draws them — with their real * thickness — plus every independent body (partition, column, room draft), * plus the bare outline of any edge that carries no thickness at all. - * Transparent: doorways, gates and arches, cut out of the masonry so the + * Transparent: doorways and gates, cut out of the masonry so the * opening is a real gap between two jamb faces; and virtual (open) * boundaries, which are not walls to begin with. A window stays solid: an * indoor lamp must not wash the street, so the light's masonry is cut by @@ -11387,12 +11694,23 @@ class HouseplanCard extends LitElement { hash = Math.imul(hash, 0x01000193) >>> 0; }; const walls = this._spaceWalls; + mix(this._cellCm); + mix(this._gridPitch); + mix(this._wallKeyPitch); for (const { poly } of polys) { mix(poly.length); for (const p of poly) { mix(p[0]); mix(p[1]); } } for (const cut of cuts) for (const value of cut) mix(value); - for (const body of physical) { mix(body.length); mix(body[0]?.[0] ?? 0); mix(body[0]?.[1] ?? 0); } - for (const wall of walls) { mix(wall.cm); mix(wall.a?.[0] ?? 0); mix(wall.a?.[1] ?? 0); } + for (const body of physical) { + mix(body.length); + for (const point of body) { mix(point[0]); mix(point[1]); } + } + for (const wall of walls) { + mix(wall.cm); + mix(wall.a?.[0] ?? 0); mix(wall.a?.[1] ?? 0); + mix(wall.b?.[0] ?? 0); mix(wall.b?.[1] ?? 0); + } const fingerprint = hash.toString(36); - if (this._lightBarrierCache?.key === fingerprint) return this._lightBarrierCache.value; + const cacheKey = `${space.id}|${fingerprint}`; + if (this._lightBarrierCache?.key === cacheKey) return this._lightBarrierCache.value; const eps = this._gridPitch * 0.02; const occluders: LightSegment[] = []; // The masonry the plan draws, cut by passages only — real thickness, real @@ -11420,7 +11738,7 @@ class HouseplanCard extends LitElement { floor: polys.map((x) => x.poly), fingerprint, }; - this._lightBarrierCache = { key: fingerprint, value }; + this._lightBarrierCache = { key: cacheKey, value }; return value; } @@ -11428,7 +11746,6 @@ class HouseplanCard extends LitElement { private _renderGlowLayer(space: SpaceModel, disp: SpaceDisplay): TemplateResult { const colors = this._fillColors; const defaultR = (this._glowRadiusCm / this._cellCm) * this._gridPitch; - const g = this._gridPitch; const polys = space.rooms .map((r) => ({ r, poly: roomPoly(r) })) .filter((x): x is { r: RoomCfg; poly: number[][] } => !!x.poly); @@ -11478,11 +11795,13 @@ class HouseplanCard extends LitElement { const ownCm = Number(d.marker?.glow_radius_cm); const R = Number.isFinite(ownCm) && ownCm > 0 ? (ownCm / this._cellCm) * this._gridPitch : defaultR; const pos = this._pos(d); - // Invalid placement in masonry must remain dark. radialOccluders() also - // guards this case, but the partial shadow mask below intentionally - // never turns an otherwise valid room completely black. + // Invalid placement in an independent physical body must remain dark. + // The visibility sweep separately rejects a source on any opaque edge. if (physical.some((body) => pointInPhysicalBody([pos.x, pos.y], body))) { - this._glowTransition(key, false); + // This placement cannot produce a valid previous-frame fade: the old + // clip belongs to a different position. Remove its transition state as + // well, rather than leaving a timer for a DOM node no longer rendered. + this._forgetGlowSource(key); continue; } const transition = this._glowTransition(key, !!visibleGlow); @@ -11536,10 +11855,13 @@ class HouseplanCard extends LitElement { // above still crosses a disabled room, but no base/pool pixels are painted // there. For the common all-enabled case this extra clip is omitted. const walls = this._spaceWalls; + const openCuts = enabled.length === polys.length + ? [] + : this._openPairs().flatMap((pair) => pair.segs); const enabledClip = enabled.length === polys.length ? null : enabled.map(({ r, poly }) => { const floorPoly = walls.length && r.id ? (innerContourForRoom( - space.rooms, r.id, walls, this._openPairs().flatMap((p) => p.segs), + space.rooms, r.id, walls, openCuts, this._wallKeyPitch, this._cellCm, this._gridPitch, NORM_W, ) || poly) : poly; @@ -11558,7 +11880,12 @@ class HouseplanCard extends LitElement { const perUnit = this._stageEl?.clientWidth && view.w ? this._stageEl.clientWidth / view.w : 1; - const feather = GLOW_EDGE_FEATHER_PX / 2 / (perUnit > 0 ? perUnit : 1); + const nextFeather = GLOW_EDGE_FEATHER_PX / 2 / (perUnit > 0 ? perUnit : 1); + const featherEnabled = !this._pinchStart && !this._panStart + && Date.now() >= this._glowFeatherSuspendUntil; + if (this._glowFeatherUnits == null || featherEnabled) + this._glowFeatherUnits = nextFeather; + const feather = this._glowFeatherUnits ?? nextFeather; const pad = feather * 4; const featherBox = spots.reduce((box, sp) => ({ x: Math.min(box.x, sp.pos.x - sp.r - pad), @@ -11616,7 +11943,7 @@ class HouseplanCard extends LitElement { not become the pointer target: room hover and its tooltip still belong to the room underneath the light pool. */} + filter=${featherEnabled ? 'url(#hp-glowfeather)' : nothing}> @@ -12005,9 +12337,17 @@ class HouseplanCard extends LitElement { const bdLive = this._bdLive; const furnLive = this._furnLive; const editorChromeMode = this._mode === 'view' ? this._editorChromeMode : this._mode; + const roomHover = this._roomHoverPaths(space); + const backdropHref = space.bg ? this._display(space.bg.href) : ''; + const recoveryReason = (this._continuity.overlayVisible || this._continuity.state === 'recovery-error') + ? this._continuity.recoveryReason : null; return html` -
this._markupClick(e)} @wheel=${(e: WheelEvent) => this._onWheel(e)} @@ -12107,6 +12447,7 @@ class HouseplanCard extends LitElement { @pointercancel=${(e: PointerEvent) => this._stagePointerCancel(e)}> ${this._renderEditorSecondary()}