${t}
`:""}\n `,i.hasListener||(i.addEventListener("click",(t=>{t.stopPropagation(),popUpOpen=location.hash+!0;localStorage.getItem("isManuallyClosed_"+e);popUpOpen!==e+!0?(navigate("",e),popUpOpen=e+!0):(history.replaceState(null,null,location.href.split("#")[0]),popUpOpen=e+!1)}),{passive:!0}),i.hasListener=!0),i};if(!this.buttonsAdded){const tt=document.createElement("div");tt.setAttribute("class","horizontal-buttons-stack-container"),this.content.appendChild(tt),this.buttonsContainer=tt}const l=(t,e)=>{if(hass.states[e].attributes.rgb_color){const n=hass.states[e].attributes.rgb_color,i=isColorCloseToWhite(n)?"rgba(255,220,200, 0.5)":`rgba(${n}, 0.5)`;t.style.backgroundColor=i,t.style.border="1px solid rgba(0,0,0,0)"}else hass.states[e].attributes.rgb_color||"on"!=hass.states[e].state?(t.style.backgroundColor="rgba(0,0,0,0)",t.style.border="1px solid var(--primary-text-color)"):(t.style.backgroundColor="rgba(255,255,255,0.5)",t.style.border="1px solid rgba(0,0,0,0)")};let c=[],d=1;for(;this.config[d+"_link"];){const et=d+"_",nt=this.config[et+"name"]||"",it=this.config[et+"pir_sensor"];icon=this.config[et+"icon"]||"";const ot=this.config[et+"link"],at=this.config[et+"entity"];c.push({button:nt,pirSensor:it,icon:icon,link:ot,lightEntity:at}),d++}if(this.config.auto_order&&c.sort(((t,e)=>{if(t.pirSensor&&e.pirSensor){if("on"===hass.states[t.pirSensor].state&&"on"===hass.states[e.pirSensor].state){return hass.states[t.pirSensor].last_updated${name}
\n ${m?`${formatedState}
`:""}\n `,this.buttonAdded=!0}function U(t){let e=t.querySelector(".feedback-element");e||(e=document.createElement("div"),e.setAttribute("class","feedback-element"),t.appendChild(e)),e.style.animation="tap-feedback .5s",setTimeout((()=>{e.style.animation="none",t.removeChild(e)}),500)}function H(t){x=t.pageX||(t.touches?t.touches[0].pageX:0),k=t.pageY||(t.touches?t.touches[0].pageY:0),C=L.value,t.target!==I&&t.target!==I.querySelector("ha-icon")&&(v=!0,document.addEventListener("mouseup",V,{passive:!0}),document.addEventListener("touchend",V,{passive:!0}),document.addEventListener("mousemove",D,{passive:!0}),document.addEventListener("touchmove",D,{passive:!0}),S=setTimeout((()=>{Y(t.pageX||t.touches[0].pageX),z(),S=null}),200))}function D(t){const e=t.pageX||(t.touches?t.touches[0].pageX:0),n=t.pageY||(t.touches?t.touches[0].pageY:0);Math.abs(n-k)>Math.abs(e-x)?(clearTimeout(S),V()):(document.removeEventListener("mousemove",D),document.removeEventListener("touchmove",D),document.addEventListener("mousemove",B,{passive:!0}),document.addEventListener("touchmove",B,{passive:!0}))}function V(){v=!1,$=!1,z(),document.removeEventListener("mouseup",V),document.removeEventListener("touchend",V),document.removeEventListener("mousemove",B),document.removeEventListener("touchmove",B)}function z(){entityId.startsWith("light.")?(b=_,hass.callService("light","turn_on",{entity_id:entityId,brightness:b})):entityId.startsWith("media_player.")&&(y=w,hass.callService("media_player","volume_set",{entity_id:entityId,volume_level:y}))}function B(t){const e=t.pageX||(t.touches?t.touches[0].pageX:0),n=t.pageY||(t.touches?t.touches[0].pageY:0);v&&Math.abs(e-x)>10?Y(e):v&&Math.abs(n-k)>10&&(v=!1,L.value=C)}function Y(t){const e=L.getBoundingClientRect(),n=Math.min(Math.max(t-e.left,0),e.width)/e.width;entityId.startsWith("light.")?_=Math.round(255*n):entityId.startsWith("media_player.")&&(w=n),T.style.transition="none",T.style.transform=`translateX(${100*n}%)`}if(m&&formatedState&&(this.content.querySelector(".state").textContent=formatedState),this.eventAdded||"switch"!==f?this.eventAdded||"slider"!==f?this.eventAdded||"custom"!==f||(A.addEventListener("click",(()=>U(this.switchButton)),{passive:!0}),addActions(this,this.switchButton),this.eventAdded=!0):(L.addEventListener("mousedown",H,{passive:!0}),L.addEventListener("touchstart",H,{passive:!0}),addActions(this,this.iconContainer),this.eventAdded=!0):(A.addEventListener("click",(()=>U(this.switchButton)),{passive:!0}),A.addEventListener("click",(function(t){t.target!==I&&t.target!==I.querySelector("ha-icon")&&toggleEntity(entityId)}),{passive:!0}),addActions(this,this.iconContainer),this.eventAdded=!0),this.isDragging||"slider"!==f||(this.rangeFill.style.transition="all .3s",entityId.startsWith("light.")?this.rangeFill.style.transform=`translateX(${b/255*100}%)`:entityId.startsWith("media_player.")&&(this.rangeFill.style.transform=`translateX(${100*y}%)`)),"slider"===f&&(!this.colorAdded||E||this.wasEditing)){if(entityId.startsWith("light.")){const lt=hass.states[entityId].attributes.rgb_color;this.rgbColorOpacity=lt?isColorCloseToWhite(lt)?"rgba(255,220,200,0.5)":`rgba(${lt}, 0.5)`:stateOn?"rgba(255,220,200, 0.5)":"rgba(255, 255, 255, 0.5)",this.rgbColor=lt?isColorCloseToWhite(lt)?"rgb(255,220,200)":`rgb(${lt})`:stateOn?"rgba(255,220,200, 1)":"rgba(255, 255, 255, 1)",this.iconFilter=lt?isColorCloseToWhite(lt)?"none":"brightness(1.1)":"none"}else this.rgbColorOpacity="var(--accent-color)",this.iconFilter="brightness(1.1)";this.colorAdded=!0,this.wasEditing=!1}const M=`\n ha-card {\n margin-top: 0 !important;\n background: none !important;\n opacity: ${"unavailable"!==state?"1":"0.5"};\n }\n \n .button-container {\n position: relative;\n width: 100%;\n height: 50px;\n z-index: 0;\n background-color: var(--background-color-2,var(--secondary-background-color));\n border-radius: 25px;\n mask-image: radial-gradient(white, black);\n -webkit-mask-image: radial-gradient(white, black);\n -webkit-backface-visibility: hidden;\n -moz-backface-visibility: hidden;\n -webkit-transform: translateZ(0);\n overflow: hidden;\n }\n \n .switch-button,\n .range-slider {\n display: inline-flex;\n position: absolute;\n height: 100%;\n width: 100%;\n transition: background-color 1.5s;\n background-color: ${stateOn&&["switch","custom"].includes(f)?"var(--accent-color)":"rgba(0,0,0,0)"};\n }\n \n .range-fill {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n background-color: ${this.rgbColorOpacity};\n }\n \n .switch-button {\n cursor: pointer !important;\n }\n \n .range-slider {\n cursor: ew-resize;\n }\n \n .range-fill {\n z-index: 0;\n width: 100%;\n left: -100%;\n }\n \n .icon-container {\n position: absolute;\n display: flex;\n z-index: 1;\n width: 38px;\n height: 38px;\n margin: 6px;\n border-radius: 50%;\n cursor: pointer !important;\n background-color: var(--card-background-color,var(--ha-card-background));\n }\n \n .icon-container::after {\n content: '';\n position: absolute;\n display: block;\n opacity: ${entityId.startsWith("light.")?"0.2":"0"};\n width: 100%;\n height: 100%;\n transition: all 1s;\n border-radius: 50%;\n background-color: ${stateOn?this.rgbColor?this.rgbColor:"var(--accent-color)":"var(--card-background-color,var(--ha-card-background))"};\n }\n \n ha-icon {\n display: flex;\n position: absolute;\n margin: inherit;\n padding: 1px 2px;\n width: 22px; \n height: 22px;\n color: ${stateOn?this.rgbColor?this.rgbColor:"var(--accent-color)":"inherit"};\n opacity: ${stateOn?"1":"0.6"};\n filter: ${stateOn?this.rgbColor?this.iconFilter:"brightness(1.1)":"inherit"};\n }\n \n .entity-picture {\n display: flex;\n height: 38px;\n width: 38px;\n border-radius: 100%;\n }\n \n .name-container {\n position: relative;\n display: ${m?"block":"inline-flex"};\n margin-left: 58px;\n z-index: 1;\n font-weight: 600;\n align-items: center;\n line-height: ${m?"4px":"16px"};\n padding-right: 16px;\n }\n \n .state {\n font-size: 12px;\n opacity: 0.7;\n }\n \n .feedback-element {\n position: absolute;\n top: 0;\n left: 0;\n opacity: 0;\n width: 100%;\n height: 100%;\n background-color: rgb(0,0,0);\n }\n \n @keyframes tap-feedback {\n 0% {transform: translateX(-100%); opacity: 0;}\n 64% {transform: translateX(0); opacity: 0.1;}\n 100% {transform: translateX(100%); opacity: 0;}\n }\n `;addStyles(this,M,customStyles,state,entityId,E);break;case"separator":if(!this.separatorAdded||editor){if(editor&&this.separatorContainer)for(;this.separatorContainer.firstChild;)this.separatorContainer.removeChild(this.separatorContainer.firstChild);this.separatorAdded||(this.separatorContainer=document.createElement("div"),this.separatorContainer.setAttribute("class","separator-container")),this.separatorContainer.innerHTML=`\n${name}
\n${formatedState}
\nAlmost everything is available in the GUI editor, but in the YAML editor you can add your own custom styles, create custom buttons or modify the tap actions of all cards. You can find more details on my GitHub page.
And if you like my project and want to support me, please consider making a donation. Any amount is welcome and very much appreciated! 🍻
${this.makeVersion()}${e}
`:""}\n `,r.hasListener||(r.addEventListener("click",(t=>{t.stopPropagation(),o("light"),v=location.hash+!0,localStorage.getItem("isManuallyClosed_"+n),v!==n+!0?(i(0,n),v=n+!0):(history.replaceState(null,null,location.href.split("#")[0]),v=n+!1)}),{passive:!0}),window.addEventListener("urlChanged",(function(){t.config.highlightCurrentview&&(location.pathname===n||location.hash===n?r.classList.add("highlight"):r.classList.remove("highlight"))}),{passive:!0}),r.hasListener=!0),r})(n.button,n.link,n.icon);e[n.link]=a,t.buttonsContainer.appendChild(a)})),t.buttonsAdded=!0,t.buttons=e}let z=0;!async function(t){if(t.buttonsUpdated)return;let e=[];for(let n of V)t.buttons[n.link]&&(e.push(localStorage.getItem(`buttonWidth-${n.link}`)),e.push(localStorage.getItem(`buttonContent-${n.link}`)));let n=await Promise.all(e),o=0;for(let e of V){let i=t.buttons[e.link];if(i){let r=n[o],s=n[o+1];o+=2,r&&"0"!==r&&s===i.innerHTML&&!a||(r=i.offsetWidth,await localStorage.setItem(`buttonWidth-${e.link}`,r),await localStorage.setItem(`buttonContent-${e.link}`,i.innerHTML),t.previousConfig=t.config),i.style.transform=`translateX(${z}px)`,z+=parseInt(r)+12}e.lightEntity&&T(i,e.lightEntity,e.link)}t.buttonsAdded=!0}(t);const M=`\n ha-card {\n border-radius: 0;\n }\n .horizontal-buttons-stack {\n width: 100%;\n margin-top: 0 !important;\n background: none !important;\n position: fixed;\n height: 51px;\n bottom: 16px;\n left: ${_};\n z-index: 1 !important; /* Higher value hide the more-info panel */\n }\n @keyframes from-bottom {\n 0% {transform: translateY(200px);}\n 20% {transform: translateY(200px);}\n 46% {transform: translateY(-8px);}\n 56% {transform: translateY(1px);}\n 62% {transform: translateY(-2px);}\n 70% {transform: translateY(0);}\n 100% {transform: translateY(0);}\n }\n .horizontal-buttons-stack-container {\n width: max-content;\n position: relative;\n height: 51px;\n }\n .button {\n display: inline-flex;\n position: absolute;\n box-sizing: border-box !important;\n border: 1px solid var(--primary-text-color);\n align-items: center;\n height: 50px;\n line-height: 16px;\n white-space: nowrap;\n width: auto;\n border-radius: 25px;\n z-index: 1;\n padding: 0 16px;\n background: none;\n transition: background-color 1s, border 1s, transform 1s;\n color: var(--primary-text-color);\n }\n .highlight {\n animation: pulse 1.4s infinite alternate;\n }\n @keyframes pulse {\n 0% {\n filter: brightness(0.7);\n }\n 100% {\n filter: brightness(1.3);\n }\n }\n .icon {\n height: 24px;\n }\n .card-content {\n width: calc(100% + 18px);\n box-sizing: border-box !important;\n margin: 0 -36px !important;\n padding: 0 36px !important;\n overflow: scroll !important;\n -ms-overflow-style: none;\n scrollbar-width: none;\n -webkit-mask-image: linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, 1) calc(0% + 28px), rgba(0, 0, 0, 1) calc(100% - 28px), transparent 100%);\n /* mask-image: linear-gradient(90deg, transparent 2%, rgba(0, 0, 0, 1) 6%, rgba(0, 0, 0, 1) 96%, transparent 100%); */\n /* -webkit-mask-image: linear-gradient(90deg, transparent 2%, rgba(0, 0, 0, 1) 6%, rgba(0, 0, 0, 1) 96%, transparent 100%); */\n }\n .horizontal-buttons-stack::before {\n content: '';\n position: absolute;\n top: -32px;\n left: -100%;\n display: block;\n background: linear-gradient(0deg, var(--background-color, var(--primary-background-color)) 50%, rgba(79, 69, 87, 0));\n width: 200%;\n height: 100px;\n }\n .card-content::-webkit-scrollbar {\n display: none;\n }\n @media only screen and (min-width: 600px) {\n .card-content {\n position: fixed;\n width: ${d} !important;\n left: calc(50% - ${h[1]/2}${h[2]});\n margin-left: -13px !important;\n padding: 0 26px !important;\n }\n }\n @media only screen and (min-width: 870px) {\n .card-content {\n position: fixed;\n width: calc(${d}${"%"!==h[2]||u?"":" - var(--mdc-drawer-width)"}) !important;\n left: calc(50% - ${h[1]/2}${h[2]} + ${!0===u?"0px":"var(--mdc-drawer-width) "+("%"===h[2]?"":"/ 2")});\n margin-left: -13px !important;\n padding: 0 26px !important;\n }\n }\n .horizontal-buttons-stack.editor {\n position: relative;\n bottom: 0;\n left: 0;\n overflow: hidden;\n }\n .horizontal-buttons-stack.editor::before {\n top: -32px;\n left: -100%;\n background: none;\n width: 100%;\n height: 0;\n }\n .horizontal-buttons-stack-container.editor > .button {\n transition: background-color 0s, border 0s, transform 0s;\n }\n .horizontal-buttons-stack-container.editor {\n margin-left: 1px;\n }\n .horizontal-buttons-stack.editor > .card-content {\n position: relative;\n width: calc(100% + 26px) !important;\n left: -26px;\n margin: 0 !important;\n padding: 0;\n }\n `;!window.hasAnimated&&f&&(t.content.style.animation="from-bottom 1.3s forwards",window.hasAnimated=!0,setTimeout((()=>{t.content.style.animation="none"}),1500)),(0,e.L2)(n,t,M,r),a?(t.buttonsContainer.classList.add("editor"),t.card.classList.add("editor")):(t.buttonsContainer.classList.remove("editor"),t.card.classList.remove("editor"))}(this);break;case"button":!function(t){const n=t._hass,i=t.editor;let{customStyles:r,entityId:l,icon:c,name:d,widthDesktop:h,widthDesktopDivided:u,isSidebarHidden:g,state:m,stateChanged:b,stateOn:f,formatedState:_,riseAnimation:v,marginCenter:y,popUpOpen:w,rgbaColor:x,rgbColor:k,bgOpacity:C,shadowOpacity:$,bgBlur:S,iconColorOpacity:E,iconColor:O,iconFilter:L,iconStyles:A,haStyle:I,themeBgColor:T,color:V}=p(t,t.config,n);_=b||i?n.formatEntityState(n.states[l]):_||"";const D=t.config.button_type||"switch",z=!!t.config.show_state&&t.config.show_state;let M=l?n.states[l].attributes.brightness||0:"",B=l?n.states[l].attributes.volume_level||0:"",q=!1,U=M,H=B,Y=0,F=0,R=0,W=!1,j=null;if(!t.buttonAdded){const e=document.createElement("div");e.setAttribute("class","button-container"),t.content.appendChild(e)}const P=document.createElement("div");P.setAttribute("class","icon-container"),t.iconContainer=P;const X=document.createElement("div");X.setAttribute("class","name-container");const N=document.createElement("div");N.setAttribute("class","switch-button");const G=document.createElement("div");G.setAttribute("class","range-slider");const K=document.createElement("div");if(K.setAttribute("class","range-fill"),!t.buttonContainer||i){if(i&&t.buttonContainer){for(;t.buttonContainer.firstChild;)t.buttonContainer.removeChild(t.buttonContainer.firstChild);t.eventAdded=!1,t.wasEditing=!0}t.buttonContainer=t.content.querySelector(".button-container"),"slider"!==D||t.buttonAdded&&!i?("switch"===D||"custom"===D||i)&&(t.buttonContainer.appendChild(N),N.appendChild(P),N.appendChild(X),t.switchButton=t.content.querySelector(".switch-button")):(t.buttonContainer.appendChild(G),G.appendChild(P),G.appendChild(X),G.appendChild(K),t.rangeFill=t.content.querySelector(".range-fill")),(0,e.IU)(t,n,l,c,P,i),X.innerHTML=`\n${d}
\n ${z?`${_}
`:""}\n `,t.buttonAdded=!0}function Z(t){o("success");let e=t.querySelector(".feedback-element");e||(e=document.createElement("div"),e.setAttribute("class","feedback-element"),t.appendChild(e)),e.style.animation="tap-feedback .5s",setTimeout((()=>{e.style.animation="none",t.removeChild(e)}),500)}function J(t){Y=t.pageX||(t.touches?t.touches[0].pageX:0),F=t.pageY||(t.touches?t.touches[0].pageY:0),R=G.value,t.target!==P&&t.target!==P.querySelector("ha-icon")&&(q=!0,document.addEventListener("mouseup",tt,{passive:!0}),document.addEventListener("touchend",tt,{passive:!0}),document.addEventListener("mousemove",Q,{passive:!0}),document.addEventListener("touchmove",Q,{passive:!0}),j=setTimeout((()=>{ot(t.pageX||t.touches[0].pageX),et(),j=null}),200))}function Q(t){const e=t.pageX||(t.touches?t.touches[0].pageX:0),n=t.pageY||(t.touches?t.touches[0].pageY:0);Math.abs(n-F)>Math.abs(e-Y)?(clearTimeout(j),tt()):(document.removeEventListener("mousemove",Q),document.removeEventListener("touchmove",Q),document.addEventListener("mousemove",nt,{passive:!0}),document.addEventListener("touchmove",nt,{passive:!0}))}function tt(){q=!1,W=!1,et(),document.removeEventListener("mouseup",tt),document.removeEventListener("touchend",tt),document.removeEventListener("mousemove",nt),document.removeEventListener("touchmove",nt)}function et(){l.startsWith("light.")?(M=U,n.callService("light","turn_on",{entity_id:l,brightness:M})):l.startsWith("media_player.")&&(B=H,n.callService("media_player","volume_set",{entity_id:l,volume_level:B}))}function nt(t){const e=t.pageX||(t.touches?t.touches[0].pageX:0),n=t.pageY||(t.touches?t.touches[0].pageY:0);q&&Math.abs(e-Y)>10?(o("light"),ot(e)):q&&Math.abs(n-F)>10&&(q=!1,G.value=R)}function ot(t){const e=G.getBoundingClientRect(),n=Math.min(Math.max(t-e.left,0),e.width)/e.width;l.startsWith("light.")?U=Math.round(255*n):l.startsWith("media_player.")&&(H=n),K.style.transition="none",K.style.transform=`translateX(${100*n}%)`}z&&_&&(t.content.querySelector(".state").textContent=_),t.eventAdded||"switch"!==D?t.eventAdded||"slider"!==D?t.eventAdded||"custom"!==D||(N.addEventListener("click",(()=>Z(t.switchButton)),{passive:!0}),s(P,t.config,0,o),t.eventAdded=!0):(G.addEventListener("mousedown",J,{passive:!0}),G.addEventListener("touchstart",J,{passive:!0}),s(P,t.config,0,o),t.eventAdded=!0):(N.addEventListener("click",(()=>Z(t.switchButton)),{passive:!0}),N.addEventListener("click",(function(t){t.target!==P&&t.target!==P.querySelector("ha-icon")&&a(n,l)}),{passive:!0}),s(P,t.config,0,o),t.eventAdded=!0),t.isDragging||"slider"!==D||(t.rangeFill.style.transition="all .3s",l.startsWith("light.")?t.rangeFill.style.transform=`translateX(${M/255*100}%)`:l.startsWith("media_player.")&&(t.rangeFill.style.transform=`translateX(${100*B}%)`));const it=`\n ha-card {\n margin-top: 0 !important;\n background: none !important;\n opacity: ${"unavailable"!==m?"1":"0.5"};\n }\n \n .button-container {\n position: relative;\n width: 100%;\n height: 50px;\n z-index: 0;\n background-color: var(--background-color-2,var(--secondary-background-color));\n border-radius: 25px;\n mask-image: radial-gradient(white, black);\n -webkit-mask-image: radial-gradient(white, black);\n -webkit-backface-visibility: hidden;\n -moz-backface-visibility: hidden;\n -webkit-transform: translateZ(0);\n overflow: hidden;\n }\n \n .switch-button,\n .range-slider {\n display: inline-flex;\n position: absolute;\n height: 100%;\n width: 100%;\n transition: background-color 1.5s;\n background-color: ${f&&["switch","custom"].includes(D)?"var(--accent-color)":"rgba(0,0,0,0)"};\n }\n\n .range-fill {\n z-index: -1;\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n background-color: ${E};\n width: 100%;\n left: -100%;\n }\n \n .switch-button {\n cursor: pointer !important;\n }\n \n .range-slider {\n cursor: ew-resize;\n }\n \n .name-container {\n position: relative;\n display: ${z?"block":"inline-flex"};\n margin-left: 4px;\n z-index: 1;\n font-weight: 600;\n align-items: center;\n line-height: ${z?"4px":"16px"};\n padding-right: 16px;\n }\n \n .state {\n font-size: 12px;\n opacity: 0.7;\n }\n \n .feedback-element {\n position: absolute;\n top: 0;\n left: 0;\n opacity: 0;\n width: 100%;\n height: 100%;\n background-color: rgb(0,0,0);\n }\n \n @keyframes tap-feedback {\n 0% {transform: translateX(-100%); opacity: 0;}\n 64% {transform: translateX(0); opacity: 0.1;}\n 100% {transform: translateX(100%); opacity: 0;}\n }\n\n ${A}\n `;(0,e.L2)(n,t,it,r,m,l,b)}(this);break;case"separator":!function(t){const n=t._hass,o=t.editor,i=t.config;let{customStyles:a,entityId:r,icon:s,name:l,widthDesktop:c,widthDesktopDivided:d,isSidebarHidden:h,state:u,stateChanged:g,stateOn:m,formatedState:b,riseAnimation:f,marginCenter:_,popUpOpen:v,rgbaColor:y,rgbColor:w,bgOpacity:x,shadowOpacity:k,bgBlur:C,iconColorOpacity:$,iconColor:S,iconFilter:E,iconStyles:O,haStyle:L,themeBgColor:A,color:I}=p(t,i,n);if(!t.separatorAdded||o){if(o&&t.separatorContainer)for(;t.separatorContainer.firstChild;)t.separatorContainer.removeChild(t.separatorContainer.firstChild);t.separatorAdded||(t.separatorContainer=document.createElement("div"),t.separatorContainer.setAttribute("class","separator-container")),t.separatorContainer.innerHTML=`\n${d}
\n \nAlmost everything is available in the GUI editor, but in the YAML editor you can add your own custom styles, create custom buttons or modify the tap actions of all cards. You can find more details on my GitHub page.
+ +And if you like my project and want to support me, please consider making a donation. Any amount is welcome and very much appreciated! 🍻
+ + ${this.makeVersion()} +${name}
+ ${!showState ? '' : `${formatedState}
`} + `; + + context.buttonAdded = true; + } + + if (showState && formatedState) { + context.content.querySelector(".state").textContent = formatedState; + } + + function tapFeedback(content) { + forwardHaptic("success"); + let feedbackElement = content.querySelector('.feedback-element'); + if (!feedbackElement) { + feedbackElement = document.createElement('div'); + feedbackElement.setAttribute('class', 'feedback-element'); + content.appendChild(feedbackElement); + } + + feedbackElement.style.animation = 'tap-feedback .5s'; + setTimeout(() => { + feedbackElement.style.animation = 'none'; + content.removeChild(feedbackElement); + }, 500); + } + + function handleStart(e) { + startX = e.pageX || (e.touches ? e.touches[0].pageX : 0); + startY = e.pageY || (e.touches ? e.touches[0].pageY : 0); + startValue = rangeSlider.value; + if (e.target !== iconContainer && e.target !== iconContainer.querySelector('ha-icon')) { + isDragging = true; + document.addEventListener('mouseup', handleEnd, { passive: true }); + document.addEventListener('touchend', handleEnd, { passive: true }); + document.addEventListener('mousemove', checkVerticalScroll, { passive: true }); + document.addEventListener('touchmove', checkVerticalScroll, { passive: true }); + + // Add a delay before activating the slider + timeoutId = setTimeout(() => { + updateRange(e.pageX || e.touches[0].pageX); + updateEntity(); + timeoutId = null; // Reset timeoutId once the delay has elapsed + }, 200); + } + } + + function checkVerticalScroll(e) { + const x = e.pageX || (e.touches ? e.touches[0].pageX : 0); + const y = e.pageY || (e.touches ? e.touches[0].pageY : 0); + if (Math.abs(y - startY) > Math.abs(x - startX)) { + clearTimeout(timeoutId); // Cancel the activation of the slider if vertical scrolling is detected + handleEnd(); + } else { + document.removeEventListener('mousemove', checkVerticalScroll); + document.removeEventListener('touchmove', checkVerticalScroll); + document.addEventListener('mousemove', handleMove, { passive: true }); + document.addEventListener('touchmove', handleMove, { passive: true }); + } + } + + function handleEnd() { + isDragging = false; + movingVertically = false; + updateEntity(); + document.removeEventListener('mouseup', handleEnd); + document.removeEventListener('touchend', handleEnd); + document.removeEventListener('mousemove', handleMove); + document.removeEventListener('touchmove', handleMove); + } + + function updateEntity() { + if (entityId.startsWith("light.")) { + currentBrightness = brightness; + hass.callService('light', 'turn_on', { + entity_id: entityId, + brightness: currentBrightness + }); + } else if (entityId.startsWith("media_player.")) { // && currentVolume !== volume + currentVolume = volume; + hass.callService('media_player', 'volume_set', { + entity_id: entityId, + volume_level: currentVolume + }); + } + } + + function handleMove(e) { + const x = e.pageX || (e.touches ? e.touches[0].pageX : 0); + const y = e.pageY || (e.touches ? e.touches[0].pageY : 0); + // Check if the movement is large enough to be considered a slide + if (isDragging && Math.abs(x - startX) > 10) { + forwardHaptic("light"); + updateRange(x); + } else if (isDragging && Math.abs(y - startY) > 10) { // If the movement is primarily vertical + isDragging = false; // Stop the slide + rangeSlider.value = startValue; // Reset to initial value + } + } + + if (!context.eventAdded && buttonType === 'switch') { + switchButton.addEventListener('click', () => tapFeedback(context.switchButton), { passive: true }); + switchButton.addEventListener('click', function(e) { + if (e.target !== iconContainer && e.target !== iconContainer.querySelector('ha-icon')) { + toggleEntity(hass, entityId); + } + }, { passive: true }); + addActions(iconContainer, context.config, hass, forwardHaptic); + context.eventAdded = true; + } else if (!context.eventAdded && buttonType === 'slider') { + rangeSlider.addEventListener('mousedown', handleStart, { passive: true }); + rangeSlider.addEventListener('touchstart', handleStart, { passive: true }); + addActions(iconContainer, context.config, hass, forwardHaptic); + context.eventAdded = true; + } else if (!context.eventAdded && buttonType === 'custom') { + switchButton.addEventListener('click', () => tapFeedback(context.switchButton), { passive: true }); + addActions(iconContainer, context.config, hass, forwardHaptic); + context.eventAdded = true; + } + + if (!context.isDragging && buttonType === 'slider') { + context.rangeFill.style.transition = 'all .3s'; + if (entityId.startsWith("light.")) { + context.rangeFill.style.transform = `translateX(${(currentBrightness / 255) * 100}%)`; + } else if (entityId.startsWith("media_player.")) { + context.rangeFill.style.transform = `translateX(${currentVolume * 100}%)`; + } + } + + function updateRange(x) { + const rect = rangeSlider.getBoundingClientRect(); + const position = Math.min(Math.max(x - rect.left, 0), rect.width); + const percentage = position / rect.width; + if (entityId.startsWith("light.")) { + brightness = Math.round(percentage * 255); + } else if (entityId.startsWith("media_player.")) { + volume = percentage; + } + + rangeFill.style.transition = 'none'; + rangeFill.style.transform = `translateX(${percentage * 100}%)`; + } + + const buttonStyles = ` + ha-card { + margin-top: 0 !important; + background: none !important; + opacity: ${state !== 'unavailable' ? '1' : '0.5'}; + } + + .button-container { + position: relative; + width: 100%; + height: 50px; + z-index: 0; + background-color: var(--background-color-2,var(--secondary-background-color)); + border-radius: 25px; + mask-image: radial-gradient(white, black); + -webkit-mask-image: radial-gradient(white, black); + -webkit-backface-visibility: hidden; + -moz-backface-visibility: hidden; + -webkit-transform: translateZ(0); + overflow: hidden; + } + + .switch-button, + .range-slider { + display: inline-flex; + position: absolute; + height: 100%; + width: 100%; + transition: background-color 1.5s; + background-color: ${stateOn && ['switch', 'custom'].includes(buttonType) ? 'var(--accent-color)' : 'rgba(0,0,0,0)'}; + } + + .range-fill { + z-index: -1; + position: absolute; + top: 0; + bottom: 0; + left: 0; + background-color: ${iconColorOpacity}; + width: 100%; + left: -100%; + } + + .switch-button { + cursor: pointer !important; + } + + .range-slider { + cursor: ew-resize; + } + + .name-container { + position: relative; + display: ${!showState ? 'inline-flex' : 'block'}; + margin-left: 4px; + z-index: 1; + font-weight: 600; + align-items: center; + line-height: ${!showState ? '16px' : '4px'}; + padding-right: 16px; + } + + .state { + font-size: 12px; + opacity: 0.7; + } + + .feedback-element { + position: absolute; + top: 0; + left: 0; + opacity: 0; + width: 100%; + height: 100%; + background-color: rgb(0,0,0); + } + + @keyframes tap-feedback { + 0% {transform: translateX(-100%); opacity: 0;} + 64% {transform: translateX(0); opacity: 0.1;} + 100% {transform: translateX(100%); opacity: 0;} + } + + ${iconStyles} + `; + + addStyles(hass, context, buttonStyles, customStyles, state, entityId, stateChanged); +} \ No newline at end of file diff --git a/src/cards/cover.ts b/src/cards/cover.ts new file mode 100644 index 00000000..854c4a19 --- /dev/null +++ b/src/cards/cover.ts @@ -0,0 +1,207 @@ +import { + addStyles, + createIcon, + updateIcon, + isColorCloseToWhite, + convertToRGBA, + getIconColor, + getIconStyles +} from '../tools/style.ts'; +import { + initializeContent, + checkEditor, + checkResources +} from '../tools/init.ts'; +import { + fireEvent, + forwardHaptic, + navigate, + toggleEntity, + hasStateChanged +} from '../tools/utils.ts'; +import { addActions } from '../tools/tap-actions.ts'; +import { getVariables } from '../var/cards.ts'; + +export function handleCover(context) { + + const hass = context._hass; + const editor = context.editor; + const config = context.config; + + let { + customStyles, + entityId, + icon, + name, + widthDesktop, + widthDesktopDivided, + isSidebarHidden, + state, + stateChanged, + stateOn, + formatedState, + riseAnimation, + marginCenter, + popUpOpen, + rgbaColor, + rgbColor, + bgOpacity, + shadowOpacity, + bgBlur, + iconColorOpacity, + iconColor, + iconFilter, + iconStyles, + haStyle, + themeBgColor, + color, + } = getVariables(context, config, hass, editor); + + const iconOpen = config.icon_open ? config.icon_open : 'mdi:window-shutter-open'; + const iconClosed = config.icon_close ? config.icon_close : 'mdi:window-shutter' + const openCover = !config.open_service ? 'cover.open_cover' : config.open_service; + const closeCover = !config.close_service ? 'cover.close_cover' : config.close_service; + const stopCover = !config.stop_service ? 'cover.stop_cover' : config.stop_service; + const iconUp = config.icon_up ? config.icon_up : "mdi:arrow-up"; + const iconDown = config.icon_down ? config.icon_down : "mdi:arrow-down"; + const showState = !context.config.show_state ? false : context.config.show_state; + icon = hass.states[config.entity].state === 'open' ? iconOpen : iconClosed; + formatedState = stateChanged ? hass.formatEntityState(hass.states[entityId]) : formatedState || ''; + + if (!context.coverAdded || editor) { + // Fix for editor mode + if (editor && context.coverContainer) { + while (context.coverContainer.firstChild) { + context.coverContainer.removeChild(context.coverContainer.firstChild); + } + } + // End of fix + + context.coverContainer = document.createElement("div"); + + context.coverContainer.setAttribute("class", "cover-container"); + context.coverContainer.innerHTML = ` +${name}
+ +${button}
` : ''} + `; + + if (!buttonElement.hasListener) { + buttonElement.addEventListener('click', (event) => { + event.stopPropagation(); + forwardHaptic("light"); + popUpOpen = location.hash + true; + const manuallyClosed = localStorage.getItem('isManuallyClosed_' + link) === 'true'; + if (popUpOpen !== link + true) { + navigate('', link); + popUpOpen = link + true; + } else { + history.replaceState(null, null, location.href.split('#')[0]); + popUpOpen = link + false; + } + }, { passive: true }); + + window.addEventListener('urlChanged', highlightButton, { passive: true }); + + buttonElement.hasListener = true; + } + + function highlightButton() { + if (context.config.highlightCurrentview) { + const isShown = location.pathname === link || location.hash === link; + if (isShown) { + buttonElement.classList.add("highlight"); + } else { + buttonElement.classList.remove("highlight"); + } + } + } + + return buttonElement; + }; + + if (!context.buttonsAdded) { + const buttonsContainer = document.createElement("div"); + buttonsContainer.classList.add("horizontal-buttons-stack-container"); + context.content.appendChild(buttonsContainer); + context.buttonsContainer = buttonsContainer; + } + + const updateButtonStyle = (buttonElement, lightEntity, buttonLink) => { + if (hass.states[lightEntity].attributes.rgb_color) { + const rgbColor = hass.states[lightEntity].attributes.rgb_color; + const rgbColorOpacity = (!isColorCloseToWhite(rgbColor) ? `rgba(${rgbColor}, 0.5)` : 'rgba(255,220,200, 0.5)') + buttonElement.style.backgroundColor = rgbColorOpacity; + buttonElement.style.border = '1px solid rgba(0,0,0,0)'; + } else if (!hass.states[lightEntity].attributes.rgb_color && hass.states[lightEntity].state == 'on') { + buttonElement.style.backgroundColor = 'rgba(255,255,255,0.5)'; + buttonElement.style.border = '1px solid rgba(0,0,0,0)'; + } else { + buttonElement.style.backgroundColor = 'rgba(0,0,0,0)'; + buttonElement.style.border = '1px solid var(--primary-text-color)'; + } + }; + + let buttonsList = []; + let i = 1; + while (context.config[i + '_link']) { + const prefix = i + '_'; + const button = context.config[prefix + 'name'] || ''; + const pirSensor = context.config[prefix + 'pir_sensor']; + icon = context.config[prefix + 'icon'] || ''; + const link = context.config[prefix + 'link']; + const lightEntity = context.config[prefix + 'entity']; + buttonsList.push({ + button, + pirSensor, + icon, + link, + lightEntity + }); + i++; + } + + if (context.config.auto_order) { + buttonsList.sort((a, b) => { + // Check if both PIR sensors are defined + if (a.pirSensor && b.pirSensor) { + // Check if the PIR sensor state is "on" for both buttons + if (hass.states[a.pirSensor].state === "on" && hass.states[b.pirSensor].state === "on") { + let aTime = hass.states[a.pirSensor].last_updated; + let bTime = hass.states[b.pirSensor].last_updated; + return aTime < bTime ? 1 : -1; + } + // If only a.pirSensor is "on", place a before b + else if (hass.states[a.pirSensor].state === "on") { + return -1; + } + // If only b.pirSensor is "on", place b before a + else if (hass.states[b.pirSensor].state === "on") { + return 1; + } + // If neither PIR sensor is "on", arrangement based only on the state of last updated even if off + let aTime = hass.states[a.pirSensor].last_updated; + let bTime = hass.states[b.pirSensor].last_updated; + return aTime < bTime ? 1 : -1; + } + // If a.pirSensor is not defined, place a after b + else if (!a.pirSensor) { + return 1; + } + // If b.pirSensor is not defined, place b after a + else if (!b.pirSensor) { + return -1; + } + }); + } + + if (!context.buttonsAdded || editor) { + context.card.classList.add('horizontal-buttons-stack'); + + // Fix for editor mode + if (editor && context.buttonsContainer) { + while (context.buttonsContainer.firstChild) { + context.buttonsContainer.removeChild(context.buttonsContainer.firstChild); + } + localStorage.setItem('editorMode', true); + } else { + localStorage.setItem('editorMode', false); + } + // End of fix + + const buttons = {}; + buttonsList.forEach(button => { + const buttonElement = createButton(button.button, button.link, button.icon); + // Store the button element using its link as key + buttons[button.link] = buttonElement; + context.buttonsContainer.appendChild(buttonElement); + }); + context.buttonsAdded = true; + context.buttons = buttons; + } + + let currentPosition = 0; + let buttonMargin = 12; + + async function updateButtons(context) { + if (context.buttonsUpdated) { + return; + } + + let promises = []; + for (let button of buttonsList) { + let buttonElement = context.buttons[button.link]; + if (buttonElement) { + promises.push(localStorage.getItem(`buttonWidth-${button.link}`)); + promises.push(localStorage.getItem(`buttonContent-${button.link}`)); + } + } + let results = await Promise.all(promises); + let index = 0; + for (let button of buttonsList) { + let buttonElement = context.buttons[button.link]; + if (buttonElement) { + let buttonWidth = results[index]; + let buttonContent = results[index + 1]; + index += 2; + if (!buttonWidth || buttonWidth === '0' || buttonContent !== buttonElement.innerHTML || editor) { + buttonWidth = buttonElement.offsetWidth; + await localStorage.setItem(`buttonWidth-${button.link}`, buttonWidth); + await localStorage.setItem(`buttonContent-${button.link}`, buttonElement.innerHTML); + context.previousConfig = context.config; + } + buttonElement.style.transform = `translateX(${currentPosition}px)`; + currentPosition += parseInt(buttonWidth) + buttonMargin; + } + if (button.lightEntity) { + updateButtonStyle(buttonElement, button.lightEntity, button.link); + } + } + + context.buttonsAdded = true; + } + + updateButtons(context); + + const horizontalButtonsStackStyles = ` + ha-card { + border-radius: 0; + } + .horizontal-buttons-stack { + width: 100%; + margin-top: 0 !important; + background: none !important; + position: fixed; + height: 51px; + bottom: 16px; + left: ${marginCenter}; + z-index: 1 !important; /* Higher value hide the more-info panel */ + } + @keyframes from-bottom { + 0% {transform: translateY(200px);} + 20% {transform: translateY(200px);} + 46% {transform: translateY(-8px);} + 56% {transform: translateY(1px);} + 62% {transform: translateY(-2px);} + 70% {transform: translateY(0);} + 100% {transform: translateY(0);} + } + .horizontal-buttons-stack-container { + width: max-content; + position: relative; + height: 51px; + } + .button { + display: inline-flex; + position: absolute; + box-sizing: border-box !important; + border: 1px solid var(--primary-text-color); + align-items: center; + height: 50px; + line-height: 16px; + white-space: nowrap; + width: auto; + border-radius: 25px; + z-index: 1; + padding: 0 16px; + background: none; + transition: background-color 1s, border 1s, transform 1s; + color: var(--primary-text-color); + } + .highlight { + animation: pulse 1.4s infinite alternate; + } + @keyframes pulse { + 0% { + filter: brightness(0.7); + } + 100% { + filter: brightness(1.3); + } + } + .icon { + height: 24px; + } + .card-content { + width: calc(100% + 18px); + box-sizing: border-box !important; + margin: 0 -36px !important; + padding: 0 36px !important; + overflow: scroll !important; + -ms-overflow-style: none; + scrollbar-width: none; + -webkit-mask-image: linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, 1) calc(0% + 28px), rgba(0, 0, 0, 1) calc(100% - 28px), transparent 100%); + /* mask-image: linear-gradient(90deg, transparent 2%, rgba(0, 0, 0, 1) 6%, rgba(0, 0, 0, 1) 96%, transparent 100%); */ + /* -webkit-mask-image: linear-gradient(90deg, transparent 2%, rgba(0, 0, 0, 1) 6%, rgba(0, 0, 0, 1) 96%, transparent 100%); */ + } + .horizontal-buttons-stack::before { + content: ''; + position: absolute; + top: -32px; + left: -100%; + display: block; + background: linear-gradient(0deg, var(--background-color, var(--primary-background-color)) 50%, rgba(79, 69, 87, 0)); + width: 200%; + height: 100px; + } + .card-content::-webkit-scrollbar { + display: none; + } + @media only screen and (min-width: 600px) { + .card-content { + position: fixed; + width: ${widthDesktop} !important; + left: calc(50% - ${widthDesktopDivided[1] / 2}${widthDesktopDivided[2]}); + margin-left: -13px !important; + padding: 0 26px !important; + } + } + @media only screen and (min-width: 870px) { + .card-content { + position: fixed; + width: calc(${widthDesktop}${widthDesktopDivided[2] === '%' && !isSidebarHidden ? ' - var(--mdc-drawer-width)' : ''}) !important; + left: calc(50% - ${widthDesktopDivided[1] / 2}${widthDesktopDivided[2]} + ${isSidebarHidden === true ? '0px' : `var(--mdc-drawer-width) ${widthDesktopDivided[2] === '%' ? '' : '/ 2'}`}); + margin-left: -13px !important; + padding: 0 26px !important; + } + } + .horizontal-buttons-stack.editor { + position: relative; + bottom: 0; + left: 0; + overflow: hidden; + } + .horizontal-buttons-stack.editor::before { + top: -32px; + left: -100%; + background: none; + width: 100%; + height: 0; + } + .horizontal-buttons-stack-container.editor > .button { + transition: background-color 0s, border 0s, transform 0s; + } + .horizontal-buttons-stack-container.editor { + margin-left: 1px; + } + .horizontal-buttons-stack.editor > .card-content { + position: relative; + width: calc(100% + 26px) !important; + left: -26px; + margin: 0 !important; + padding: 0; + } + `; + + if (!window.hasAnimated && riseAnimation) { + context.content.style.animation = 'from-bottom 1.3s forwards'; + window.hasAnimated = true; + setTimeout(() => { + context.content.style.animation = 'none'; + }, 1500); + } + + addStyles(hass, context, horizontalButtonsStackStyles, customStyles); + + if (editor) { + context.buttonsContainer.classList.add('editor'); + context.card.classList.add('editor'); + } else { + context.buttonsContainer.classList.remove('editor'); + context.card.classList.remove('editor'); + } +} \ No newline at end of file diff --git a/src/cards/pop-up.ts b/src/cards/pop-up.ts new file mode 100644 index 00000000..8a6ad006 --- /dev/null +++ b/src/cards/pop-up.ts @@ -0,0 +1,647 @@ +import { + addStyles, + createIcon, + updateIcon, + isColorCloseToWhite, + convertToRGBA, + getIconColor, + getIconStyles +} from '../tools/style.ts'; +import { + initializeContent, + checkEditor, + checkResources +} from '../tools/init.ts'; +import { + fireEvent, + forwardHaptic, + navigate, + toggleEntity, + hasStateChanged +} from '../tools/utils.ts'; +import { addActions } from '../tools/tap-actions.ts'; +import { getVariables } from '../var/cards.ts'; + +export function handlePopUp(context) { + + const hass = context._hass; + const editor = context.editor; + const config = context.config; + + if (!hass) { + return; + } + + let { + customStyles, + entityId, + icon, + name, + widthDesktop, + widthDesktopDivided, + isSidebarHidden, + state, + stateChanged, + stateOn, + formatedState, + riseAnimation, + marginCenter, + popUpOpen, + rgbaColor, + rgbColor, + bgOpacity, + shadowOpacity, + bgBlur, + iconColorOpacity, + iconColor, + iconFilter, + iconStyles, + haStyle, + themeBgColor, + color, + } = getVariables(context, config, hass, editor); + + let autoClose = config.auto_close || false; + let popUpHash = config.hash; + let triggerEntity = config.trigger_entity ? config.trigger_entity : ''; + let triggerState = config.trigger_state ? config.trigger_state : ''; + let triggerClose = config.trigger_close ? config.trigger_close : false; + let startTouchY; + let lastTouchY; + + if (context.errorTriggered) { + return; + } + + if (!context.initStyleAdded && !context.host && !editor) { + // Hide vertical stack content before initialization + context.card.style.marginTop = '4000px'; + context.initStyleAdded = true; + } + + const createPopUp = () => { + if (!context.host) { + context.host = context.getRootNode().host; + } else { + if (!context.popUp) { + context.verticalStack = context.getRootNode(); + context.popUp = context.verticalStack.querySelector('#root'); + + if (!window.popUpInitialized && context.popUp) { + const backOpen = config.back_open || false; + backOpen ? localStorage.setItem('backOpen', true) : localStorage.setItem('backOpen', false); + const backOpenState = localStorage.getItem('backOpen') === 'true'; + + if (backOpenState) { + window.backOpen = true; + const event = new Event('popUpInitialized'); + setTimeout(() => { + window.dispatchEvent(event); + }, 0); + } else { + window.backOpen = false; + popUpOpen = popUpHash + false; + history.replaceState(null, null, location.href.split('#')[0]); + } + + window.popUpInitialized = true; + } + } + + const popUp = context.popUp; + const verticalStack = context.verticalStack; + const text = config.text || ''; + const stateEntityId = config.state; + formatedState = stateEntityId ? hass.formatEntityState(hass.states[stateEntityId]) : formatedState || ''; + const marginTopMobile = config.margin_top_mobile + ? (config.margin_top_mobile !== '0' ? config.margin_top_mobile : '0px') + : '0px'; + const marginTopDesktop = config.margin_top_desktop + ? (config.margin_top_desktop !== '0' ? config.margin_top_desktop : '0px') + : '0px'; + const displayPowerButton = config.entity ? 'flex' : 'none'; + state = stateEntityId ? hass.states[stateEntityId].state : ''; + let closeTimeout; + let rgbaBgColor; + + if (!context.headerAdded) { + const headerContainer = document.createElement("div"); + headerContainer.setAttribute("id", "header-container"); + + const div = document.createElement("div"); + headerContainer.appendChild(div); + + const iconContainer = document.createElement("div"); + iconContainer.setAttribute("class", "icon-container"); + div.appendChild(iconContainer); + + createIcon(context, hass, entityId, icon, iconContainer, editor); + addActions(iconContainer, config, hass, forwardHaptic); + + const h2 = document.createElement("h2"); + h2.textContent = name; + div.appendChild(h2); + + const p = document.createElement("p"); + p.textContent = formatedState; + div.appendChild(p); + + const haIcon2 = document.createElement("ha-icon"); + haIcon2.setAttribute("class", "power-button"); + haIcon2.setAttribute("icon", "mdi:power"); + haIcon2.setAttribute("style", `display: ${displayPowerButton};`); + div.appendChild(haIcon2); + + const button = document.createElement("button"); + button.setAttribute("class", "close-pop-up"); + button.onclick = function() { history.replaceState(null, null, location.href.split('#')[0]); localStorage.setItem('isManuallyClosed_' + popUpHash, true); }; + headerContainer.appendChild(button); + + const haIcon3 = document.createElement("ha-icon"); + haIcon3.setAttribute("icon", "mdi:close"); + button.appendChild(haIcon3); + + context.content.appendChild(headerContainer); + context.header = div; + + context.headerAdded = true; + } else if (entityId) { + const iconContainer = context.content.querySelector("#header-container .icon-container"); + const h2 = context.content.querySelector("#header-container h2"); + const p = context.content.querySelector("#header-container p"); + const haIcon2 = context.content.querySelector("#header-container .power-button"); + + iconContainer.innerHTML = ''; // Clear the container + createIcon(context, hass, entityId, icon, iconContainer, editor); + + h2.textContent = name; + p.textContent = formatedState; + haIcon2.setAttribute("style", `display: ${displayPowerButton};`); + } + + if (!context.eventAdded && !editor) { + window['checkHashRef_' + popUpHash] = checkHash; + window.addEventListener('urlChanged', window['checkHashRef_' + popUpHash], { passive: true }); + window.addEventListener('click', function(e) { + // Reset auto close + location.hash === popUpHash && resetAutoClose(); + + if (!window.justOpened) { + return; + } + + const target = e.composedPath(); + + if (target && + !target.some(el => el.nodeName === 'HA-MORE-INFO-DIALOG') && + !target.some(el => el.id === 'root' && !el.classList.contains('close-pop-up')) && + popUpOpen === popUpHash + true) { + setTimeout(function() { + if (location.hash === popUpHash) { + popUpOpen = popUpHash + false; + history.replaceState(null, null, location.href.split('#')[0]); + localStorage.setItem('isManuallyClosed_' + popUpHash, true); + } + }, 2); + } + }, { passive: true }); + + context.eventAdded = true; + } + + function powerButtonClickHandler() { + toggleEntity(hass, entityId); + } + + function windowClickHandler(e) { + // Reset auto close + if (window.hash === popUpHash) { + resetAutoClose(); + } + + if (!window.justOpened) { + return; + } + + const target = e.composedPath(); + + if (target && + !target.some(el => el.nodeName === 'HA-MORE-INFO-DIALOG') && + !target.some(el => el.id === 'root' && !el.classList.contains('close-pop-up')) && + popUpOpen === popUpHash + true) { + popUpOpen = popUpHash + false; + history.replaceState(null, null, location.href.split('#')[0]); + localStorage.setItem('isManuallyClosed_' + popUpHash, true) + } + } + + function windowKeydownHandler(e) { + if (e.key === 'Escape') { + popUpOpen = popUpHash + false; + history.replaceState(null, null, location.href.split('#')[0]); + localStorage.setItem('isManuallyClosed_' + popUpHash, true) + } + } + + function popUpTouchstartHandler(event) { + // Reset auto close + if (window.hash === popUpHash) { + resetAutoClose(); + } + + // Record the Y position of the finger at the start of the touch + startTouchY = event.touches[0].clientY; + lastTouchY = startTouchY; + } + + function popUpTouchmoveHandler(event) { + // Calculate the distance the finger has traveled + let touchMoveDistance = event.touches[0].clientY - startTouchY; + + // If the distance is positive (i.e., the finger is moving downward) and exceeds a certain threshold, close the pop-up + if (touchMoveDistance > 300 && event.touches[0].clientY > lastTouchY) { + popUpOpen = popUpHash + false; + history.replaceState(null, null, location.href.split('#')[0]); + popUpOpen = popUpHash + false; + localStorage.setItem('isManuallyClosed_' + popUpHash, true) + } + + // Update the Y position of the last touch + lastTouchY = event.touches[0].clientY; + } + + if (entityId) { + const rgbColor = hass.states[entityId].attributes.rgb_color; + context.rgbColor = rgbColor + ? (!isColorCloseToWhite(rgbColor) ? `rgb(${rgbColor})` : 'rgb(255,220,200)') + : (stateOn + ? (entityId.startsWith("light.") ? 'rgba(255,220,200, 0.5)' : 'var(--accent-color)') + : 'rgba(255, 255, 255, 1'); + + context.rgbColorOpacity = rgbColor + ? (!isColorCloseToWhite(rgbColor) ? `rgba(${rgbColor}, 0.5)` : 'rgba(255,220,200, 0.5)') + : (entityId && stateOn + ? (entityId.startsWith("light.") ? 'rgba(255,220,200, 0.5)' : 'var(--accent-color)') + : 'var(--background-color,var(--secondary-background-color))'); + + rgbaBgColor = convertToRGBA(color, 0); + + context.iconFilter = rgbColor ? + (!isColorCloseToWhite(rgbColor) ? 'brightness(1.1)' : 'none') : + 'none'; + } else { + rgbaBgColor = convertToRGBA(color, 0); + } + + function checkHash() { + if (!editor) { + window.hash = location.hash.split('?')[0]; + + // Open on hash change + if (window.hash === popUpHash) { + openPopUp(); + // Close on back button from browser + } else if (popUp.classList.contains('open-pop-up')) { + closePopUp(); + } + } + }; + + let content = context.content; + + function pauseVideos(root, pause) { + var videos = root.querySelectorAll('video'); + for (var i=0; iAlmost everything is available in the GUI editor, but in the YAML editor you can add your own custom styles, create custom buttons or modify the tap actions of all cards. You can find more details on my GitHub page.
+ +And if you like my project and want to support me, please consider making a donation. Any amount is welcome and very much appreciated! 🍻
+ + ${this.makeVersion()} +