diff --git a/button_card_templates.yaml b/button_card_templates.yaml index 832dd0b4..63391765 100644 --- a/button_card_templates.yaml +++ b/button_card_templates.yaml @@ -28,6 +28,8 @@ base: template: - settings + - tilt + - extra_styles variables: state_on: > [[[ return ['on', 'home', 'cool', 'fan_only', 'playing'].indexOf(entity === undefined || entity.state) !== -1; ]]] @@ -43,6 +45,24 @@ [[[ return entity === undefined || entity.attributes.entity_picture; ]]] timeout: > [[[ return entity === undefined || Date.now() - Date.parse(entity.last_changed); ]]] + tilt_options: > + [[[ + let options = { + max: 5, + scale: 1.06, + glare: true, + 'max-glare': 0.15, + perspective: 800, + speed: 800, + parallax: '25px' + } + if (this._config.template.includes('conditional_media')) { + options.scale = options.scale % parseInt(options.scale) / 2 + parseInt(options.scale); + options.perspective = options.perspective * 2; + return options; + } + return options; + ]]] aspect_ratio: 1/1 show_state: true show_icon: false @@ -69,15 +89,6 @@ }); } ]]] - animation_card: | - [[[ - let time = 900; - this.shadowRoot.getElementById('card').style.animation = - `card_bounce ${time}ms cubic-bezier(0.22, 1, 0.36, 1)`, - window.setTimeout(() => { - this.shadowRoot.getElementById('card').style.animation = 'none' - }, time); - ]]] action: toggle haptic: medium hold_action: @@ -102,7 +113,7 @@ - border-radius: var(--custom-button-card-border-radius) - -webkit-tap-highlight-color: rgba(0,0,0,0) - transition: none - - padding: 10% 9% 9% 10% + - padding: 11.5% 10.5% 10.5% 11.5% - --mdc-ripple-color: > [[[ return variables.state_on @@ -121,7 +132,9 @@ ? 'rgba(255, 255, 255, 0.8)' : 'rgba(115, 115, 115, 0.2)'; ]]] - extra_styles: | + + extra_styles: + extra_styles: > [[[ if (entity) { let hs = entity.attributes.hs_color === undefined, @@ -134,6 +147,7 @@ ? `hsl(204, 58%, ${l_calc}%);` : `hsl(${h}, ${s}%, ${l_calc}%);`; } + return ` svg { --light-color: @@ -154,60 +168,150 @@ #state::first-letter { text-transform: uppercase; } - #blur, #overlayx { - top: calc((100% - var(--media-blur-height)) + 0.5%); - height: var(--media-blur-height); - } /* portrait */ @media screen and (max-width: 1200px) { #name, #state { font-size: 2vw; - letter-spacing: 0.05vw; - } - #blur, #overlayx { - top: calc((100% - (var(--media-blur-height) + 1.5%)) + 0.5%); - height: calc(var(--media-blur-height) + 1.5%); } } /* phone */ @media screen and (max-width: 800px) { #name, #state { - font-size: 3.1vw; - letter-spacing: 0.12vw; - } - #blur, #overlayx { - top: calc((100% - (var(--media-blur-height) + 3%)) + 0.5%); - height: calc(var(--media-blur-height) + 3%); + font-size: 3vw; } } - @keyframes card_bounce { - 0% { - transform: scale(1); + + /* tilt */ + #ripple, .js-tilt-glare { + clip-path: inset(0 round var(--custom-button-card-border-radius)); + overflow: hidden; + } + .js-tilt-glare { + z-index: 1; + } + .js-tilt-glare-inner { + background-color: rgba(0,0,0,0.9); + } + #container { + transform: translateZ(${variables.tilt_options.parallax}); + } + #card { + transform-style: preserve-3d; + overflow: visible; + } + + ${this._config.template.includes('conditional_media') ? ` + :host { + --blur-intensity: blur(4.5px) brightness(0.8); } - 15% { - transform: scale(0.9); + /* phone */ + @media screen and (max-width: 800px) { + :host { + --blur-intensity: blur(2.5px) brightness(0.8); + } } - 25% { - transform: scale(1); + #ripple, .js-tilt-glare { + clip-path: inset(0 round calc(var(--custom-button-card-border-radius) / 2)); } - 30% { - transform: scale(0.98); + #container { + overflow: hidden; } - 100% { - transform: scale(1); + .marquee { + animation: marquee 20s linear infinite; } - } - .marquee { - animation: marquee 20s linear infinite; - } - @keyframes marquee { - from { - transform: translateX(0%); + @keyframes marquee { + from { + transform: translateX(0%); + } + to { + transform: translateX(-50%); + } } - to { - transform: translateX(-50%); + `:''} + + ${this._config.template.includes('footer') ? ` + :host { + --name-font-size: 1.22vw; + --name-icon-size: 1.5vw; + --notify-font-size: 0.9vw; + --notify-box-size: 1.8vw; + --name-padding-v: 0.7vw; + --name-padding-h: 1.1vw; + --card-border-radius: 0.6vw; } - } + #ripple, .js-tilt-glare { + border-radius: calc(var(--card-border-radius) - 0.1vw); + clip-path: inset(0 round calc( var(--custom-button-card-border-radius) - 0.1vw )); + } + #name { + font-size: var(--name-font-size); + padding: var(--name-padding-v) var(--name-padding-h); + letter-spacing: 0.012vw; + } + ha-icon { + width: var(--name-icon-size); + vertical-align: 7%; + padding-right: 0.1vw; + opacity: 0.4; + } + #card { + border-radius: var(--card-border-radius); + background: rgba(115, 115, 115, 0.04); + } + #notify { + font-size: var(--notify-font-size); + width: var(--notify-box-size); + height: var(--notify-box-size); + line-height: var(--notify-box-size); + padding-right: 0.5px; + padding-top: 0.5px; + } + /* portrait */ + @media screen and (max-width: 1200px) { + #name { + font-size: calc(var(--name-font-size) * 1.4); + padding: calc(var(--name-padding-v) * 1.4) calc(var(--name-padding-h) * 1.4); + } + ha-icon { + width: calc(var(--name-icon-size) * 1.4); + } + #card { + border-radius: calc(var(--card-border-radius) * 1.4); + margin: 0 0.5vw; + } + #notify { + font-size: calc(var(--notify-font-size) * 1.4); + width: calc(var(--notify-box-size) * 1.4); + height: calc(var(--notify-box-size) * 1.4); + line-height: calc(var(--notify-box-size) * 1.4); + } + } + /* phone */ + @media screen and (max-width: 800px) { + #name { + font-size: calc(var(--name-font-size) * 2.7); + padding: calc(var(--name-padding-v) * 2.7) calc(var(--name-padding-h) * 2.7); + letter-spacing: 0.12vw; + } + ha-icon { + width: calc(var(--name-icon-size) * 2.7); + } + #card { + border-radius: calc(var(--card-border-radius) * 2.7); + background: rgba(115, 115, 115, 0.08); + margin: 0 0.5vw; + } + #notify { + font-size: calc(var(--notify-font-size) * 2.7); + width: calc(var(--notify-box-size) * 2.7); + height: calc(var(--notify-box-size) * 2.7); + line-height: calc(var(--notify-box-size) * 2.7); + padding: 0; + } + } + `:''} + + ` ]]] @@ -308,11 +412,17 @@ ################################################# footer: + template: + - settings + - tilt + - extra_styles variables: notify: > [[[ return false; ]]] + tilt_options: > + [[[ return { max: 5, scale: 1.1, glare: true, 'max-glare': 0.07, perspective: 1000, speed: 800, parallax: '10px' }; ]]] size: 2vw color: '#9da0a2' custom_fields: @@ -321,7 +431,7 @@ if (Number.isInteger(variables.notify)) { return variables.notify; } else if (variables.notify) { - return '!'; + return `!`; } ]]] styles: @@ -360,76 +470,8 @@ hold_action: action: none tap_action: - load: none - extra_styles: | - :host { - --name-font-size: 1.2vw; - --name-icon-size: 1.5vw; - --notify-font-size: 0.9vw; - --notify-box-size: 1.8vw; - --name-padding-v: 0.7vw; - --name-padding-h: 1.1vw; - --card-border-radius: 0.6vw; - } - #name { - font-size: var(--name-font-size); - padding: var(--name-padding-v) var(--name-padding-h); - letter-spacing: 0.07vw; - } - ha-icon { - width: var(--name-icon-size); - vertical-align: 7%; - padding-right: 0.1vw; - filter: brightness(50%); - } - #card { - border-radius: var(--card-border-radius); - } - #notify { - font-size: var(--notify-font-size); - width: var(--notify-box-size); - height: var(--notify-box-size); - line-height: var(--notify-box-size); - } - /* portrait */ - @media screen and (max-width: 1200px) { - #name { - font-size: calc(var(--name-font-size) * 1.4); - padding: calc(var(--name-padding-v) * 1.4) calc(var(--name-padding-h) * 1.4); - } - ha-icon { - width: calc(var(--name-icon-size) * 1.4); - } - #card { - border-radius: calc(var(--card-border-radius) * 1.4); - } - #notify { - font-size: calc(var(--notify-font-size) * 1.4); - width: calc(var(--notify-box-size) * 1.4); - height: calc(var(--notify-box-size) * 1.4); - line-height: calc(var(--notify-box-size) * 1.4); - } - } - /* phone */ - @media screen and (max-width: 800px) { - #name { - font-size: calc(var(--name-font-size) * 2.5); - padding: calc(var(--name-padding-v) * 2.5) calc(var(--name-padding-h) * 2.5); - letter-spacing: 0.12vw; - } - ha-icon { - width: calc(var(--name-icon-size) * 2.5); - } - #card { - border-radius: calc(var(--card-border-radius) * 2.5); - } - #notify { - font-size: calc(var(--notify-font-size) * 2.5); - width: calc(var(--notify-box-size) * 2.5); - height: calc(var(--notify-box-size) * 2.5); - line-height: calc(var(--notify-box-size) * 2.5); - } - } + haptic: medium + ################################################# # # @@ -491,7 +533,7 @@ loader = (id, style, timeout) => { elt.getElementById(id) && (elt.getElementById(id).style.display = style, window.setTimeout(() => { - elt.getElementById(id).style.display = timeout + elt.getElementById('loader').style.display = 'none' }, 20000)) }; loader('circle', 'none', 'initial'), @@ -502,14 +544,35 @@ custom_fields: loader: - display: none - - top: 3% - - left: 60% - - width: 37% + - top: -5.8% + - right: -5.3% + - width: 50% - position: absolute - opacity: 0.5 - filter: > [[[ return variables.state === 'off' ? 'invert(1)' : 'none'; ]]] + ################################################# + # # + # TILT # + # # + ################################################# + + tilt: + custom_fields: + tilt: > + [[[ + setTimeout(() => { + let elt = this.shadowRoot, + card = elt.getElementById('card'), + ignore = window.navigator.userAgent; + if (elt && card && VanillaTilt.init !== undefined + && !ignore.match(/iPhone/i) && !ignore.match(/Android/i)) { + VanillaTilt.init(card, variables.tilt_options); + } + }, 0); + ]]] + ################################################# # # # CLIMATE # diff --git a/configuration.yaml b/configuration.yaml index 202e53a0..8dc97b0d 100755 --- a/configuration.yaml +++ b/configuration.yaml @@ -31,6 +31,7 @@ lovelace: { url: '/local/calendar-card.js?v=3.109.1', type: module }, { url: '/local/custom_icons.js?v=28082021', type: module }, { url: '/local/marked.min.js?v=4.0.12', type: module }, + { url: '/local/vanilla-tilt.min.js?v=1.7.2', type: module }, { url: /local/font.css, type: css } ] logger: diff --git a/include/themes.yaml b/include/themes.yaml index 1e80f2ab..96c9ef12 100644 --- a/include/themes.yaml +++ b/include/themes.yaml @@ -126,10 +126,14 @@ frontend: div { height: 100%; } + .swiper-container { + margin: -1.5vw !important; + padding: 1.5vw !important; + } hui-horizontal-stack-card$: .: | hui-conditional-card { - margin: 1px !important; + margin: 0 0.1vw 0 0 !important; } ################################################# diff --git a/www/vanilla-tilt.min.js b/www/vanilla-tilt.min.js new file mode 100644 index 00000000..9ba2f01f --- /dev/null +++ b/www/vanilla-tilt.min.js @@ -0,0 +1 @@ +var VanillaTilt=function(){"use strict";class t{constructor(e,i={}){if(!(e instanceof Node))throw"Can't initialize VanillaTilt because "+e+" is not a Node.";this.width=null,this.height=null,this.clientWidth=null,this.clientHeight=null,this.left=null,this.top=null,this.gammazero=null,this.betazero=null,this.lastgammazero=null,this.lastbetazero=null,this.transitionTimeout=null,this.updateCall=null,this.event=null,this.updateBind=this.update.bind(this),this.resetBind=this.reset.bind(this),this.element=e,this.settings=this.extendSettings(i),this.reverse=this.settings.reverse?-1:1,this.glare=t.isSettingTrue(this.settings.glare),this.glarePrerender=t.isSettingTrue(this.settings["glare-prerender"]),this.fullPageListening=t.isSettingTrue(this.settings["full-page-listening"]),this.gyroscope=t.isSettingTrue(this.settings.gyroscope),this.gyroscopeSamples=this.settings.gyroscopeSamples,this.elementListener=this.getElementListener(),this.glare&&this.prepareGlare(),this.fullPageListening&&this.updateClientSize(),this.addEventListeners(),this.reset(),this.updateInitialPosition()}static isSettingTrue(t){return""===t||!0===t||1===t}getElementListener(){if(this.fullPageListening)return window.document;if("string"==typeof this.settings["mouse-event-element"]){const t=document.querySelector(this.settings["mouse-event-element"]);if(t)return t}return this.settings["mouse-event-element"]instanceof Node?this.settings["mouse-event-element"]:this.element}addEventListeners(){this.onMouseEnterBind=this.onMouseEnter.bind(this),this.onMouseMoveBind=this.onMouseMove.bind(this),this.onMouseLeaveBind=this.onMouseLeave.bind(this),this.onWindowResizeBind=this.onWindowResize.bind(this),this.onDeviceOrientationBind=this.onDeviceOrientation.bind(this),this.elementListener.addEventListener("mouseenter",this.onMouseEnterBind),this.elementListener.addEventListener("mouseleave",this.onMouseLeaveBind),this.elementListener.addEventListener("mousemove",this.onMouseMoveBind),(this.glare||this.fullPageListening)&&window.addEventListener("resize",this.onWindowResizeBind),this.gyroscope&&window.addEventListener("deviceorientation",this.onDeviceOrientationBind)}removeEventListeners(){this.elementListener.removeEventListener("mouseenter",this.onMouseEnterBind),this.elementListener.removeEventListener("mouseleave",this.onMouseLeaveBind),this.elementListener.removeEventListener("mousemove",this.onMouseMoveBind),this.gyroscope&&window.removeEventListener("deviceorientation",this.onDeviceOrientationBind),(this.glare||this.fullPageListening)&&window.removeEventListener("resize",this.onWindowResizeBind)}destroy(){clearTimeout(this.transitionTimeout),null!==this.updateCall&&cancelAnimationFrame(this.updateCall),this.reset(),this.removeEventListeners(),this.element.vanillaTilt=null,delete this.element.vanillaTilt,this.element=null}onDeviceOrientation(t){if(null===t.gamma||null===t.beta)return;this.updateElementPosition(),this.gyroscopeSamples>0&&(this.lastgammazero=this.gammazero,this.lastbetazero=this.betazero,null===this.gammazero?(this.gammazero=t.gamma,this.betazero=t.beta):(this.gammazero=(t.gamma+this.lastgammazero)/2,this.betazero=(t.beta+this.lastbetazero)/2),this.gyroscopeSamples-=1);const e=this.settings.gyroscopeMaxAngleX-this.settings.gyroscopeMinAngleX,i=this.settings.gyroscopeMaxAngleY-this.settings.gyroscopeMinAngleY,s=e/this.width,n=i/this.height,l=(t.gamma-(this.settings.gyroscopeMinAngleX+this.gammazero))/s,a=(t.beta-(this.settings.gyroscopeMinAngleY+this.betazero))/n;null!==this.updateCall&&cancelAnimationFrame(this.updateCall),this.event={clientX:l+this.left,clientY:a+this.top},this.updateCall=requestAnimationFrame(this.updateBind)}onMouseEnter(){this.updateElementPosition(),this.element.style.willChange="transform",this.setTransition()}onMouseMove(t){null!==this.updateCall&&cancelAnimationFrame(this.updateCall),this.event=t,this.updateCall=requestAnimationFrame(this.updateBind)}onMouseLeave(){this.setTransition(),this.settings.reset&&requestAnimationFrame(this.resetBind)}reset(){this.event={clientX:this.left+this.width/2,clientY:this.top+this.height/2},this.element&&this.element.style&&(this.element.style.transform=`perspective(${this.settings.perspective}px) `+"rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)"),this.resetGlare()}resetGlare(){this.glare&&(this.glareElement.style.transform="rotate(180deg) translate(-50%, -50%)",this.glareElement.style.opacity="0")}updateInitialPosition(){if(0===this.settings.startX&&0===this.settings.startY)return;this.onMouseEnter(),this.fullPageListening?this.event={clientX:(this.settings.startX+this.settings.max)/(2*this.settings.max)*this.clientWidth,clientY:(this.settings.startY+this.settings.max)/(2*this.settings.max)*this.clientHeight}:this.event={clientX:this.left+(this.settings.startX+this.settings.max)/(2*this.settings.max)*this.width,clientY:this.top+(this.settings.startY+this.settings.max)/(2*this.settings.max)*this.height};let t=this.settings.scale;this.settings.scale=1,this.update(),this.settings.scale=t,this.resetGlare()}getValues(){let t,e;return this.fullPageListening?(t=this.event.clientX/this.clientWidth,e=this.event.clientY/this.clientHeight):(t=(this.event.clientX-this.left)/this.width,e=(this.event.clientY-this.top)/this.height),t=Math.min(Math.max(t,0),1),e=Math.min(Math.max(e,0),1),{tiltX:(this.reverse*(this.settings.max-t*this.settings.max*2)).toFixed(2),tiltY:(this.reverse*(e*this.settings.max*2-this.settings.max)).toFixed(2),percentageX:100*t,percentageY:100*e,angle:Math.atan2(this.event.clientX-(this.left+this.width/2),-(this.event.clientY-(this.top+this.height/2)))*(180/Math.PI)}}updateElementPosition(){let t=this.element.getBoundingClientRect();this.width=this.element.offsetWidth,this.height=this.element.offsetHeight,this.left=t.left,this.top=t.top}update(){let t=this.getValues();this.element.style.transform="perspective("+this.settings.perspective+"px) rotateX("+("x"===this.settings.axis?0:t.tiltY)+"deg) rotateY("+("y"===this.settings.axis?0:t.tiltX)+"deg) scale3d("+this.settings.scale+", "+this.settings.scale+", "+this.settings.scale+")",this.glare&&(this.glareElement.style.transform=`rotate(${t.angle}deg) translate(-50%, -50%)`,this.glareElement.style.opacity=`${t.percentageY*this.settings["max-glare"]/100}`),this.element.dispatchEvent(new CustomEvent("tiltChange",{detail:t})),this.updateCall=null}prepareGlare(){if(!this.glarePrerender){const t=document.createElement("div");t.classList.add("js-tilt-glare");const e=document.createElement("div");e.classList.add("js-tilt-glare-inner"),t.appendChild(e),this.element.appendChild(t)}this.glareElementWrapper=this.element.querySelector(".js-tilt-glare"),this.glareElement=this.element.querySelector(".js-tilt-glare-inner"),this.glarePrerender||(Object.assign(this.glareElementWrapper.style,{position:"absolute",top:"0",left:"0",width:"100%",height:"100%",overflow:"hidden","pointer-events":"none"}),Object.assign(this.glareElement.style,{position:"absolute",top:"50%",left:"50%","pointer-events":"none","background-image":"linear-gradient(0deg, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 100%)",transform:"rotate(180deg) translate(-50%, -50%)","transform-origin":"0% 0%",opacity:"0"}),this.updateGlareSize())}updateGlareSize(){if(this.glare){const t=2*(this.element.offsetWidth>this.element.offsetHeight?this.element.offsetWidth:this.element.offsetHeight);Object.assign(this.glareElement.style,{width:`${t}px`,height:`${t}px`})}}updateClientSize(){this.clientWidth=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,this.clientHeight=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight}onWindowResize(){this.updateGlareSize(),this.updateClientSize()}setTransition(){clearTimeout(this.transitionTimeout),this.element.style.transition=this.settings.speed+"ms "+this.settings.easing,this.glare&&(this.glareElement.style.transition=`opacity ${this.settings.speed}ms ${this.settings.easing}`),this.transitionTimeout=setTimeout(()=>{this.element.style.transition="",this.glare&&(this.glareElement.style.transition="")},this.settings.speed)}extendSettings(t){let e={reverse:!1,max:15,startX:0,startY:0,perspective:1e3,easing:"cubic-bezier(.03,.98,.52,.99)",scale:1,speed:300,transition:!0,axis:null,glare:!1,"max-glare":1,"glare-prerender":!1,"full-page-listening":!1,"mouse-event-element":null,reset:!0,gyroscope:!0,gyroscopeMinAngleX:-45,gyroscopeMaxAngleX:45,gyroscopeMinAngleY:-45,gyroscopeMaxAngleY:45,gyroscopeSamples:10},i={};for(var s in e)if(s in t)i[s]=t[s];else if(this.element.hasAttribute("data-tilt-"+s)){let t=this.element.getAttribute("data-tilt-"+s);try{i[s]=JSON.parse(t)}catch(e){i[s]=t}}else i[s]=e[s];return i}static init(e,i){e instanceof Node&&(e=[e]),e instanceof NodeList&&(e=[].slice.call(e)),e instanceof Array&&e.forEach(e=>{"vanillaTilt"in e||(e.vanillaTilt=new t(e,i))})}}return"undefined"!=typeof document&&(window.VanillaTilt=t,t.init(document.querySelectorAll("[data-tilt]"))),t}(); \ No newline at end of file