šŸ”¹ Card-mod - Super-charge your themes!

Yes that works, except for one thing. When switching theme from:

  card-mod-theme: night
  card-mod-card: |
    ha-card.top-level-card {
      border: solid 1px var(--secondary-text-color);
      background: url("/local/background/card_bg_Night.png");
    }

    ha-card.top-level-card div.card-header {
      padding-top: 8px;
      padding-bottom: 36px;
    }

to

  card-mod-theme: day
  card-mod-card: |
    ha-card.top-level-card {
      border: solid 1px var(--primary-text-color);
      background: url("/local/background/card_bg_Day.png");
    }
    ha-card.top-level-card div.card-header {
      padding-top: 8px;
      padding-bottom: 36px;
    }

The card background does not update unless I navigate to another view and back, or refresh the page.

Possibly related console errors:

card-mod.js:1:2954
Uncaught (in promise) TypeError: null has no properties
    c https://redacted.duckdns.org/hacsfiles/lovelace-card-mod/card-mod.js:1

Leads here:

!function(e){var t={};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.m=e,o.c=t,o.d=function(e,t,n){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,t){if(1&t&&(e=o(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)o.d(n,r,function(t){return e[t]}.bind(null,r));return n},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=1)}([function(e){e.exports=JSON.parse('{"name":"card-mod","private":true,"version":"2.0.0","description":"","scripts":{"build":"webpack","watch":"webpack --watch --mode=development","update-card-tools":"npm uninstall card-tools && npm install thomasloven/lovelace-card-tools"},"keywords":[],"author":"Thomas LovƩn","license":"MIT","devDependencies":{"webpack":"^4.43.0","webpack-cli":"^3.3.11"},"dependencies":{"card-tools":"github:thomasloven/lovelace-card-tools"}}')},function(e,t,o){"use strict";o.r(t);const n=customElements.get("home-assistant-main")?Object.getPrototypeOf(customElements.get("home-assistant-main")):Object.getPrototypeOf(customElements.get("hui-view")),r=n.prototype.html;n.prototype.css;function a(){return document.querySelector("hc-main")?document.querySelector("hc-main").hass:document.querySelector("home-assistant")?document.querySelector("home-assistant").hass:void 0}let s=function(){if(window.fully&&"function"==typeof fully.getDeviceId)return fully.getDeviceId();if(!localStorage["lovelace-player-device-id"]){const e=()=>Math.floor(1e5*(1+Math.random())).toString(16).substring(1);localStorage["lovelace-player-device-id"]=`${e()}${e()}-${e()}${e()}`}return localStorage["lovelace-player-device-id"]}();const i=async e=>(await(async()=>{if(customElements.get("developer-tools-event"))return;await customElements.whenDefined("partial-panel-resolver");const e=document.createElement("partial-panel-resolver");e.hass={panels:[{url_path:"tmp",component_name:"developer-tools"}]},e._updateRoutes(),await e.routerOptions.routes.tmp.load(),await customElements.whenDefined("developer-tools-router");const t=document.createElement("developer-tools-router");await t.routerOptions.routes.event.load()})(),document.createElement("developer-tools-event")._computeParsedEventData(e)),l={template:"",variables:{},entity_ids:[]},c=async(e,t,o,n,r,a=!0)=>{e.localName.includes("-")&&await customElements.whenDefined(e.localName),e.updateComplete&&await e.updateComplete,e._cardMod=e._cardMod||document.createElement("card-mod"),(a?e.shadowRoot:e).appendChild(e._cardMod),await e.updateComplete,e._cardMod.type=t,e._cardMod.template={template:o,variables:n,entity_ids:r}};class d extends n{static get properties(){return{_renderedStyles:{},_renderer:{}}}static get applyToElement(){return c}constructor(){super(),document.querySelector("home-assistant").addEventListener("settheme",()=>{this._setTemplate(this._data)})}connectedCallback(){super.connectedCallback(),this.template=this._data,this.setAttribute("slot","none")}async getTheme(){if(!this.type)return null;let e=this.parentElement?this.parentElement:this;const t=window.getComputedStyle(e).getPropertyValue("--card-mod-theme"),o=a().themes.themes;return o[t]?o[t][`card-mod-${this.type}-yaml`]?await i(o[t][`card-mod-${this.type}-yaml`]):o[t]["card-mod-"+this.type]?o[t]["card-mod-"+this.type]:null:null}set template(e){e&&(this._data=JSON.parse(JSON.stringify(e)),this._setTemplate(this._data))}async _setTemplate(e){this._parent||(e.theme_template=await this.getTheme(),"string"==typeof e.template&&(e.template={".":e.template}),"string"==typeof e.theme_template&&(e.theme_template={".":e.theme_template})),e.template&&JSON.stringify(e.template).includes("config.entity")&&!e.entity_ids&&e.variables.config&&e.variables.config.entity&&(e.entity_ids=[e.variables.config.entity]),await this.setStyle(e)}async unStyle(){this._styledChildren=this._styledChildren||new Set;for(const e of this._styledChildren)e.template=l}_mergeDeep(e,t){const o=e=>e&&"object"==typeof e&&!Array.isArray(e);if(o(e)&&o(t))for(const n in t)o(t[n])?(e[n]||Object.assign(e,{[n]:{}}),"string"==typeof e[n]&&(e[n]={".":e[n]}),this._mergeDeep(e[n],t[n])):e[n]?e[n]=t[n]+e[n]:e[n]=t[n];return e}async setStyle(e){let{template:t,theme_template:o,variables:n,entity_ids:r}=e;if(await this.unStyle(),t||(t={}),t=JSON.parse(JSON.stringify(t)),this._mergeDeep(t,o),"string"==typeof t){if(this._renderedStyles=t,this._renderer){try{await this._renderer()}catch(e){if(!e.code||"not_found"!==e.code)throw e}this._renderer=void 0}return i=t,void((String(i).includes("{%")||String(i).includes("{{"))&&(this._renderer=await function(e,t,o){e||(e=a().connection);let n={user:a().user.name,browser:s,hash:location.hash.substr(1)||" ",...o.variables},r=o.template,i=o.entity_ids;return e.subscribeMessage(e=>{let o=e.result;o=o.replace(/_\([^)]*\)/g,e=>a().localize(e.substring(2,e.length-1))||e),t(o)},{type:"render_template",template:r,variables:n,entity_ids:i})}(null,e=>{this._renderedStyles=e},{template:t,variables:n,entity_ids:r})))}var i;await this.updateComplete;const l=this.parentElement||this.parentNode;if(!l)return{template:"",variable:variable,entity_ids:r};l.updateComplete&&await l.updateComplete;for(const e of Object.keys(t)){let o=[];if("."!==e){if("$"===e?(l.localName,o=[l.shadowRoot]):o=l.querySelectorAll(e),o.length)for(const a of o){if(!a)continue;let o=a.querySelector(":scope > card-mod");o&&o._parent===this||(o=document.createElement("card-mod"),this._styledChildren.add(o),o._parent=this),o.template={template:t[e],variables:n,entity_ids:r},a.appendChild(o)}}else this.setStyle({template:t[e],variables:n,entity_ids:r})}}createRenderRoot(){return this}render(){return r`
      <style>
        ${this._renderedStyles}
      </style>
    `}}if(!customElements.get("card-mod")){customElements.define("card-mod",d);const e=o(0);console.info(`%cCARD-MOD ${e.version} IS INSTALLED`,"color: green; font-weight: bold","")}function u(e,t,o=null){if((e=new Event(e,{bubbles:!0,cancelable:!1,composed:!0})).detail=t||{},o)o.dispatchEvent(e);else{var n=function(){var e=document.querySelector("hc-main");return e=e?(e=(e=(e=e&&e.shadowRoot)&&e.querySelector("hc-lovelace"))&&e.shadowRoot)&&e.querySelector("hui-view")||e.querySelector("hui-panel-view"):(e=(e=(e=(e=(e=(e=(e=(e=(e=(e=(e=(e=document.querySelector("home-assistant"))&&e.shadowRoot)&&e.querySelector("home-assistant-main"))&&e.shadowRoot)&&e.querySelector("app-drawer-layout partial-panel-resolver"))&&e.shadowRoot||e)&&e.querySelector("ha-panel-lovelace"))&&e.shadowRoot)&&e.querySelector("hui-root"))&&e.shadowRoot)&&e.querySelector("ha-app-layout"))&&e.querySelector("#view"))&&e.firstElementChild}();n&&n.dispatchEvent(e)}}customElements.whenDefined("ha-card").then(()=>{const e=customElements.get("ha-card");if(e.prototype.cardmod_patched)return;e.prototype.cardmod_patched=!0;const t=function(e){return e.config?e.config:e._config?e._config:e.host?t(e.host):e.parentElement?t(e.parentElement):e.parentNode?t(e.parentNode):null};e.prototype.firstUpdated=function(){const e=this.shadowRoot.querySelector(".card-header");e&&this.insertBefore(e,this.children[0]);const o=t(this);if(!o)return;o.class&&this.classList.add(o.class),o.type&&this.classList.add("type-"+o.type.replace(":","-"));(()=>{c(this,"card",o.style,{config:o},o.entity_ids,!1)})()},u("ll-rebuild",{})}),customElements.whenDefined("hui-entities-card").then(()=>{const e=customElements.get("hui-entities-card");if(e.prototype.cardmod_patched)return;e.prototype.cardmod_patched=!0;const t=e.prototype.renderEntity;e.prototype.renderEntity=function(e){const o=t.bind(this)(e);if(!e)return o;if(!o||!o.values)return o;const n=o.values[0];if(!n)return o;e.entity_ids;const r=()=>c(n,"row",e.style,{config:e},e.entity_ids);return r(),o.values[0]&&o.values[0].addEventListener("ll-rebuild",r),o},u("ll-rebuild",{})}),customElements.whenDefined("hui-glance-card").then(()=>{const e=customElements.get("hui-glance-card");e.prototype.cardmod_patched||(e.prototype.cardmod_patched=!0,e.prototype.firstUpdated=function(){this.shadowRoot.querySelectorAll("ha-card div.entity").forEach(e=>{const t=e.attachShadow({mode:"open"});[...e.children].forEach(e=>t.appendChild(e));const o=document.createElement("style");t.appendChild(o),o.innerHTML="\n      :host {\n        box-sizing: border-box;\n        padding: 0 4px;\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n        cursor: pointer;\n        margin-bottom: 12px;\n        width: var(--glance-column-width, 20%);\n      }\n      div {\n        width: 100%;\n        text-align: center;\n        white-space: nowrap;\n        overflow: hidden;\n        text-overflow: ellipsis;\n      }\n      .name {\n        min-height: var(--paper-font-body1_-_line-height, 20px);\n      }\n      state-badge {\n        margin: 8px 0;\n      }\n      ";const n=e.config||e.entityConf;if(!n)return;n.entity_ids;c(e,"glance",n.style,{config:n},n.entity_ids)})},u("ll-rebuild",{}))}),customElements.whenDefined("hui-state-label-badge").then(()=>{const e=customElements.get("hui-state-label-badge");e.prototype.cardmod_patched||(e.prototype.cardmod_patched=!0,e.prototype.firstUpdated=function(){const e=this._config;if(!e)return;e.entity_ids;(()=>{c(this,"badge",e.style,{config:e},e.entity_ids)})()},u("ll-rebuild",{}))}),customElements.whenDefined("hui-view").then(()=>{const e=customElements.get("hui-view");e.prototype.cardmod_patched||(e.prototype.cardmod_patched=!0,e.prototype.firstUpdated=function(){(()=>{c(this,"view","",{},[])})()},u("ll-rebuild",{}))}),customElements.whenDefined("hui-root").then(()=>{const e=customElements.get("hui-root");if(e.prototype.cardmod_patched)return;e.prototype.cardmod_patched=!0,e.prototype.firstUpdated=async function(){(()=>{c(this,"root","",{},[])})()},u("ll-rebuild",{});let t=document.querySelector("home-assistant");t=t&&t.shadowRoot,t=t&&t.querySelector("home-assistant-main"),t=t&&t.shadowRoot,t=t&&t.querySelector("app-drawer-layout partial-panel-resolver"),t=t&&t.querySelector("ha-panel-lovelace"),t=t&&t.shadowRoot,t=t&&t.querySelector("hui-root"),t&&t.firstUpdated()}),customElements.whenDefined("ha-more-info-dialog").then(()=>{const e=customElements.get("ha-more-info-dialog");if(e.prototype.cardmod_patched)return;e.prototype.cardmod_patched=!0;const t=e.prototype.showDialog;e.prototype.showDialog=function(e){const o=()=>{c(this.shadowRoot.querySelector("ha-dialog"),"more-info","",{config:e},[e.entityId],!1)};t.bind(this)(e),this.requestUpdate().then(async()=>{await this.shadowRoot.querySelector("ha-dialog").updateComplete,o()})};let o=document.querySelector("home-assistant");o=o&&o.shadowRoot,o=o&&o.querySelector("ha-more-info-dialog"),o&&(o.showDialog=e.prototype.showDialog.bind(o),o.showDialog({entityId:o.entityId}))});let p=window.cardHelpers;const h=new Promise(async(e,t)=>{p&&e();const o=async()=>{p=await window.loadCardHelpers(),window.cardHelpers=p,e()};window.loadCardHelpers?o():window.addEventListener("load",async()=>{!function(){if(customElements.get("hui-view"))return!0;const e=document.createElement("partial-panel-resolver");if(e.hass=a(),!e.hass||!e.hass.panels)return!1;e.route={path:"/lovelace/"},e._updateRoutes();try{document.querySelector("home-assistant").appendChild(e)}catch(e){}finally{document.querySelector("home-assistant").removeChild(e)}customElements.get("hui-view")}(),window.loadCardHelpers&&o()})});function m(e,t){const o={type:"error",error:e,origConfig:t},n=document.createElement("hui-error-card");return customElements.whenDefined("hui-error-card").then(()=>{const e=document.createElement("hui-error-card");e.setConfig(o),n.parentElement&&n.parentElement.replaceChild(e,n)}),h.then(()=>{u("ll-rebuild",{},n)}),n}function y(e,t){if(!t||"object"!=typeof t||!t.type)return m(`No ${e} type configured`,t);let o=t.type;if(o=o.startsWith("custom:")?o.substr("custom:".length):`hui-${o}-${e}`,customElements.get(o))return function(e,t){let o=document.createElement(e);try{o.setConfig(JSON.parse(JSON.stringify(t)))}catch(e){o=m(e,t)}return h.then(()=>{u("ll-rebuild",{},o)}),o}(o,t);const n=m(`Custom element doesn't exist: ${o}.`,t);n.style.display="None";const r=setTimeout(()=>{n.style.display=""},2e3);return customElements.whenDefined(o).then(()=>{clearTimeout(r),u("ll-rebuild",{},n)}),n}const f="\nha-card {\n  background: none;\n  box-shadow: none;\n}";customElements.define("mod-card",class extends n{static get properties(){return{hass:{}}}setConfig(e){this._config=JSON.parse(JSON.stringify(e)),void 0===e.style?this._config.style=f:"string"==typeof e.style?this._config.style=f+e.style:e.style["."]?this._config.style["."]=f+e.style["."]:this._config.style["."]=f,this.card=function(e){return p?p.createCardElement(e):y("card",e)}(this._config.card),this.card.hass=a()}render(){return r`
          <ha-card modcard>
          ${this.card}
          </ha-card>
        `}set hass(e){this.card&&(this.card.hass=e)}getCardSize(){if(this._config.report_size)return this._config.report_size;let e=this.shadowRoot;return e&&(e=e.querySelector("ha-card card-maker")),e&&(e=e.getCardSize),e&&(e=e()),e||1}})}]);

It is very fast though and I like that I am no longer messing with system cards like the configuration / integration page (the background image did not fit well).

Opened an issue:

1 Like

One final thing and my supercharged theme will be complete.

I can not get this style:

    style:
      .: |
        ha-card {
          border: solid 1px var(--primary-color);
        }
      mmp-shortcuts:
        $: |
          mmp-button {
            box-shadow: none;
            background: none;
            border: solid 1px var(--primary-color);
            border-radius: 10px;
          } 

To work as class: media-player . This is what I have:

    ha-card.media-player {
      border: solid 1px var(--secondary-text-color);
    }

    mmp-shortcuts.media-player {
      $: |
        mmp-button {
          box-shadow: none;
          background: none;
          border: solid 1px var(--secondary-text-color);
          border-radius: 10px;
        }
    }

But it only shows the border. The button stying does not occur. I have tried with and without the pipe symbol after the shadow root.

The ha-card element is the one that gets the class added, and the mmp-shortcuts element is a child of that, so ha-card.media-player mmp-shortcuts { may work.

I have a weird issue. I finally decided to bite the bullet to 113 and updated many card-mod and browser-mod related stuff. All works great now (a few minor glitches aside). One thing I do notice though: the more-info styling doesnā€™t work properly on iOS. I used to have a backdrop filter with a blurred effect. That pluging doesnā€™t work anymore, but card-mod 2.0 supports that as well. I use the exact same theme across all my devices (iOS, Android and Chrome browser on Win10) and the following style (under theme yaml) works excellent on desktop/Android:

  card-mod-theme: hohm-one-black
  card-mod-more-info-yaml: |
    $: |
      .mdc-dialog {
        backdrop-filter: blur(17px);
        background: rgba(0,0,0,0.5);
      }
      .mdc-dialog .mdc-dialog__container .mdc-dialog__surface {
        background: none !important;
        box-shadow: none;
        border-radius: 20px;
      }
    ha-header-bar:
      $: |
        .mdc-top-app-bar {
          background: none !important;
        }   

It gives me a nice blurred darkened background. On iOS however, it does not apply the blur. It does apply the darkened effect. Is this an iOS related issue? I havenā€™t seen a post yet, so thatā€™s why I wanted to ask around. I cleared cache withing HA app (also withing iOS settings for Safari) and tried with the HA app and Safari on iOS.

I also notice the header bar is white on iOS, while it is transparent as it should be on desktop. It seems to take the primary-color (or background-color, those are same color on my theme). When I use my night theme, the status bar is black on iOS (while transparent on desktop).

This is on Edge (Chromium) on Win10.

This is on iOS

To be fully informative, I use light-popup-slider card with the following styling:

      $: |
        .mdc-dialog .mdc-dialog__container {
          width: 100%;
        }
        .mdc-dialog .mdc-dialog__container .mdc-dialog__surface {
          width:100%;
          box-shadow:none;
        }
      .: |
        :host {
          --mdc-theme-surface: rgba(0,0,0,0);
          --secondary-background-color: rgba(0,0,0,0);
          # --ha-card-background: rgba(0,0,0,8);
          # --mdc-dialog-scrim-color: rgba(0,0,0,0.8);
          --mdc-dialog-min-height: 100%;
          --mdc-dialog-min-width: 100%;
          --mdc-dialog-max-width: 100%;
        }
        mwc-icon-button {
          color: var(--text-color);
        }

Perhaps these stylings are working against each other?

Any help if you have the time is greatly appreciated.

1 Like

Can you please tell me about the ā€œclassā€ section you have under the Lovelace card setup? I didnā€™t see any of this in the example theme, unless Iā€™m missing a file. Does that need to be on every card? Can you define multiple themes for the same Lovelace card type? Inheritance? Overrides?

Thomas did a quick explanation here:

HI Thomas, might I ask in this thread about an issue I have since updating (either to HA 113, or card-mod, I cant say honestly) and point here to prevent crossposting too much?

Having an inspector error, while HA seems to do alright in the frontend.
Hope you ca have a look, thanks.

Thanks Thomas, but unfortunately no, this does not replace the button style either.

  card-mod-theme: night
  card-mod-card: |

    ha-card.media-player {
      border: solid 1px var(--secondary-text-color);
    }

    ha-card.media-player mmp-shortcuts {
      $: |
        mmp-button {
          box-shadow: none;
          background: none;
          border: solid 1px var(--secondary-text-color);
          border-radius: 10px;
        }
    }

https://caniuse.com/#feat=css-backdrop-filter
Try with the -webkit- prefix.

2 Likes

That did the trick! I added that as an extra line so I have it on both iOS devices and Android/Chromium.

I think the status bar not becoming transparant on iOS has something to with the popup-light-card Iā€™m using. Standard ā€˜more infoā€™ views are transparent. Gonna dig deeper into that.

Thanks a lot for your time and help, once again :slight_smile:

edit:

I did some more digging around, and found out that the popup bar changes to background-color depending on the size. When I resize it untill it goes white on desktop, I see the following line:

@media (max-width: 450px), (max-height: 500px)
app-toolbar {
    background-color: #FFFFFF;
    color: #404040;
}

The weird thing is, I donā€™t know why this happens with popup cards only. More info dialogs donā€™t have that but opening any other popup card does.

I would like to be able to specify the theme in the Dashboard/View and have that apply to the system more-info dialogue. Best I can tell right now is that I have to change the theme in user preferences for this to apply. Is that possible? Am I just doing it wrong? Thanks for any help.

BTW, love this, extremely powerful yet can manage things centrally without having to go through my entire set of YAML dashboard file every time i change something. Only challenge is that my CSS skills are next to non-existence, it is a bit like trying to read Egyptian Hieroglyphs; but I am getting there.

Well letā€™s just say that you are not aware that it can be done. There are theme options for just the more info dialogue box. e.g.

mdc-theme-surface: '#041D42'   # Pop-up background colour

And the current theme can be automated to be selected from the front end:

Screenshot_2020-08-07 Administration - Home Assistant

- id: select_theme
  alias: 'Select Theme'
  trigger:
  - platform: state
    entity_id: input_select.select_theme
  action:
    service: frontend.set_theme
    data_template:
      name: >
        {% if is_state('input_select.select_theme', 'Night') %}
          night
        {% else %}
          day
        {% endif %}

Just set your profile theme to ā€œback-end selectedā€ and this will work.

None of this needs card mod.

Perfect, and easy to boot. Thanks

Is it possible to apply CSS to ha-panel-lovelace instead of hui-root?

Why would you need that? To style the error screen?

Mainly to apply stuff to other panels and some editors.

1 Like

Any updates on how to do this? For my theme the integrations page looks bad:


UPDATE: I was able to fix with ha-card-border-color.

Just heads up, thank you so much @thomasloven for your contributions to the Lovelace stuff. Half of the custom modules Iā€™m using are yours including card-mod. I recently made Windows 10 inspired themes and I used card-mod to supercharge them. Hereā€™s a repo for anyone interested.

2 Likes

I canā€™t get header theming to work in 0.115, anyone else?
Forgot to set --card-mod-theme correctly.

I canā€™t get backend-selected themes to work now with 0.115. If I set the theme manually, then it works. Any fixes?