Ui_template HDMI_TV_control - fixed css flashing

I am struggling as others have with this flow 'HDMI_TV_control'. It is an excellent concept and at one point button 'blinking' seemed to work.

Closer inspection of the css showed . There is no id of that name in the flow so I changed it to "any-name" and there was no visible difference. I then changed the styling for .red to display blue and to my surprise the button went blue.
Should that be the case ?

.remote-button.red{
    background-color: blue !important;
    color: var(--remote-button-foreground) !important;
}

Here is the Flow 'HDMI_TV_control'.

[{"id":"1dc2aa68.579f46","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"67e2f110.35bbe","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"CSS only ","order":7,"width":0,"height":0,"format":"<style id=\"any-name\">\n    :root {\n      --dashboard-unit-width: 48px;\n      --dashboard-unit-height: 48px;\n      --remote-button-background: black;\n      --remote-button-foreground: #cccccc;\n    }\n    .nr-dashboard-template {\n        padding: 0px;\n    }\n    \n    .remote-button.disabled{\n        cursor: not-allowed;\n        pointer-events: none;\n        color: #aaaaaa !important;\n    }\n    \n    .remote-button:not([disabled]):hover{\n         background-color: #232323 !important;\n    }\n\n    /*   This is the normal button definition  */\n    .remote-button{\n        background-color: var(--remote-button-background) !important;\n        color: var(--remote-button-foreground) !important;\n        height: var(--dashboard-unit-height);\n        width: 100%;\n        border-radius: 10px;\n        font-size:1.0em;\n        font-weight:normal;\n        margin: 0;\n        min-height: 36px;\n        min-width: unset;\n        line-height: unset;\n    }\n    /*  This is a sub-set which is invoked by */\n    /*  <md-button class=\"md-button remote-button bigger\"> */\n    /*  note the (space) \"bigger\" at the end.  */\n    .remote-button.bigger{\n        font-weight:bold;\n        font-size:1.5em;\n    }\n    /*  This is for buttons with a lot of text.  `font-size:0.7em` */\n    /*  makes the font 70% normal size  */\n    .remote-button.small{\n        font-size:0.7em;\n    }\n    /*  This is for buttons with just icons, to upsize the size */\n    /*  of the icon with the line: */\n    /*  <i class=\"fa fa-fw fa-plus remote-icon\"> in the other node  */\n    .remote-icon{\n        font-size:2.0em;\n    }\n    /*  This is the same as the other one, but it makes the icon smaller  */\n    .remote-iconS{\n        font-size:0.5em;\n    }\n\n    .remote-button.black{\n        background-color: var(--remote-button-background) !important;\n        color: var(--remote-button-foreground) !important;\n    }\n\n    .remote-button.red{\n        background-color: blue !important;\n        color: var(--remote-button-foreground) !important;\n    }\n    .remote-button.red:not([disabled]):hover{\n         background-color: orange !important;\n    }\n\n    .remote-button.blinking{\n    \tanimation:blinkingAnim1 0.8s infinite;\n    }\n    @keyframes blinkingAnim1{\n    \t0%{\t\tbackground-color: var(--remote-button-background); color:var(--remote-button-foreground);}\n    \t50%{\tbackground-color: var(--remote-button-foreground); color:var(--remote-button-background);}\n    \t100%{\tbackground-color: var(--remote-button-background);\tcolor:var(--remote-button-foreground);}\n    }    \n     \n    .remote-button.red.blinking{\n    \tanimation:blinkingAnim2 0.8s infinite;\n    }\n    @keyframes blinkingAnim2{\n    \t0%{\t\tbackground-color: red; color:#cccccc;}\n    \t50%{\tbackground-color: #cccccc; color:red;}\n    \t100%{\tbackground-color: red;\tcolor:#cccccc;}\n    }     \n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"global","x":180,"y":40,"wires":[[]]},{"id":"5e48976.84df868","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"NetFlix","order":20,"width":"1","height":"1","format":"<div>\n    <md-button \n        class=\"md-button remote-button\"\n        data-topic=\"netflix\"\n        data-payload=\"netflix\"\n        aria-label=\"Netflix\"\n    >\n        <img\n            class=\"remote-icon\"\n            style=\"width: 36px; padding-top: 2px\"\n            src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAE2klEQVR4Xu3bW4hVVRzH8c8ZE/GCtywry5qcarrhpbKLmoVl9FZ0s2zS0DIJrIceSl+EwIgIDCQCp4t2L4guBJUgTmnlXYtUirIpU9EZxyayqWbmxPKcARFn1hKDaW/7v64fM/v3Pf/1X2v/91oFx3kUjnP//gdwLBnQxMB2Hk75G81sreTNFO0CbioyKkX7Nwvnsz9FeyTNMWXAbip78kPKP29n++uMmkNzTL+AlzAtpgvjBSof48cU7b8O4Ckqp7G8B2elPMB2pl7KazFtZgA8wVkTWHI+V8dMhfE/eec0bolpMwWgNxtq6IeeMWM4sIzRU/i2K22mABTZPoUvB3BFAgD7mHcOC3IFoJKNkxmdAqCNTc9y2XxaO9NnLgPCUjKTnys4IwXCNm4Yxye5ARCMXEvduUxMAdBC7TDuyxWAPjTUMBAnJEBo/Jjqu2g4kjZzU6DDxJ2s7s/lCQA0cX8Vi3MFoIoNkxiTAqCV5UOZlCsABYoz2VnBsAQI7VsYM4HNh2szOwWCkUnUVSUWwwM8eQaP5gpAP/ZMZQgqYllQpP4Dqu+l5VBtpjMgGJnK2n5cFgMQxhu4+TzezRWAatZOTATQyttDuT1XACpon8meAqckZEFLPSPGsLNDm/kpEIxcT93ZicXwLx45ladzBWAAu6aUMiDadWpn8xBGh2U0QMhFBgQjd7OuL5cmTIOwMxxfxapcATifNVczNgVAO8+dxOxcAaigbSaNBU5OgNC0lTPH81tupkAwfSN1w9OL4bRTWZorAAPYMYXTEzIgVMDlQ5iUKwDB+D1s7J3WMise4LyXmZeJ7wKhLR6aorFf9yK+HJfYNG3n8cUMzxWAHrTO4NcCJ8Zg4ada6tqoSdB275eh1AwoF8MVw7kmxVQdn2xjcoq2Wz+NHQ2Awbx9G7em7Ax/5Ys3uDJXALBkVqlTdF2Csb+WcuCPUpO1y8hMBgQAD7CsyCsxU2H8az77nAkxbaYA1DC7D79gUMxYK989zzkxXaYAzGV6I89gTsxYGH+fbbuo7kqbOQD7uKh4MMPjsZvP3otMg8wBCLYbS6+9V8URaK6lVxu9OtNmEkAD0wu8mADAWj7f0AWsTALYSZ9epR7ggBiEFjYvYWSuMiCY2ceiIg/GAITxt9jR1MkbZSYzIJhqYmQ7m1IA/Ejdx530FDILoFwMV0tomRXZvZihxSM0WLMOYAZqU7JgOeu/45LDtZkGsId+PdildMqsy2hm9etHOHuQaQDlafAcZsUAoHUpzX8w+FBt5gHsZUwF6xMA+IZPVx52KDPzAMpL4vpiwsmSNr6vZUSuMqAMYFaRMBWi8SFbdnBBhzAXGdBIf6WdYd8Ygb2sfIfxuQJQLoZhOQzLYpdR5PcXqGildxDmIgOCkQbGFggbo2isY9V6xuUKQNjl7WOjLl58Osi08PUSLs4VgHIWPFhgUTQFSi9I9U2cmZspEEyX7yCFYnhwfncV9dR9xMRcASgXw9AomR4DUGRPLUOKjOi2O0NH82EktMVDUzRmrLHU/Tl4UiQWK1j7LbfnCkC5GIam6YUxAL+z5lXuyBWAcjGcUyi1z2PR9hVV13bXtbn5DOyZeHGywKa5h50I7czdfga18VDMfRivYOGg7ro4mfKA/3VN9Azff93AsT7fcQ/gH8HaMl8q26yhAAAAAElFTkSuQmCC\">\n        />\n    </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":170,"y":340,"wires":[[]]},{"id":"f2c72b21.8ffd88","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"Home","order":21,"width":"1","height":"1","format":"<div>\n   <md-button class=\"md-button remote-button\" \n        data-topic=\"home\"\n        data-payload=\"home\" \n        aria-label=\"home\"\n    >\n    <i class=\"fa fa-home remote-icon\"> </i>\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":290,"y":340,"wires":[[]]},{"id":"bb820299.8834c","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"Vid","order":22,"width":"1","height":"1","format":"<div>\n   <md-button class=\"md-button remote-button small\" \n        data-topic=\"video\"\n        data-payload=\"video\" \n    >Video\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":410,"y":340,"wires":[[]]},{"id":"f280f977.996788","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"1","order":2,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button bigger\"\n    data-topic=\"1\"\n    data-payload=\"1\">1\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":170,"y":100,"wires":[[]]},{"id":"869dbb9f.b82bd8","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"2","order":3,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button bigger\"\n    data-topic=\"2\"\n    data-payload=\"2\">2\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":290,"y":100,"wires":[[]]},{"id":"1c76eed1.bab531","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"3","order":4,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button bigger\"\n    data-topic=\"3\"\n    data-payload=\"3\">3\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":410,"y":100,"wires":[[]]},{"id":"abf27862.86ce38","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"4","order":5,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button bigger\"\n    data-topic=\"4\"\n    data-payload=\"4\">4\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":170,"y":140,"wires":[[]]},{"id":"f77a2282.06659","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"5","order":6,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button bigger\"\n    data-topic=\"5\"\n    data-payload=\"5\">5\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":290,"y":140,"wires":[[]]},{"id":"be9d59c4.6bbd58","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"6","order":7,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button bigger\"\n    data-topic=\"6\"\n    data-payload=\"6\">6\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":410,"y":140,"wires":[[]]},{"id":"60685053.bdb74","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"7","order":8,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button bigger\"\n    data-topic=\"7\"\n    data-payload=\"7\">7\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":170,"y":180,"wires":[[]]},{"id":"58cf330d.1bdcbc","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"8","order":9,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button bigger\"\n    data-topic=\"8\"\n    data-payload=\"8\">8\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":290,"y":180,"wires":[[]]},{"id":"c05985f2.3d9c48","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"9","order":10,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button bigger\"\n    data-topic=\"9\"\n    data-payload=\"9\">9\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":410,"y":180,"wires":[[]]},{"id":"3f1148d6.c26098","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"0","order":12,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button bigger\"\n    data-topic=\"0\"\n    data-payload=\"0\">0\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":290,"y":220,"wires":[[]]},{"id":"851c00ee.dcbf2","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"info","order":11,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button\"\n    data-topic=\"info\"\n    data-payload=\"info\"\n    aria-label=\"info\"\n   >\n      <i class=\"fa fa-info-circle remote-icon\"></i>\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":170,"y":220,"wires":[[]]},{"id":"7d4a478f.115ad8","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"prev","order":13,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button\"\n    data-topic=\"prev\"\n    data-payload=\"prev\"\n    aria-label=\"previous\"\n   >\n      <i class=\"fa fa-rotate-left remote-icon\"></i>\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":410,"y":220,"wires":[[]]},{"id":"3e11598d.50b456","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"Ch +","order":16,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button\" \n        data-topic=\"channel/up\"\n        data-payload=\"up\" \n        aria-label=\"channel up\"\n    >\n    <i class=\"fa fa-chevron-up remote-icon\"></i>\n   </md-button>\n</div>\n\n\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":410,"y":260,"wires":[[]]},{"id":"2c644cdf.5282a4","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"mute","order":15,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button\" \n              data-payload=\"1\" \n              data-buttontype=\"toggle\"\n              data-topic=\"mute\"\n              data-icon0=\"volume_off\"\n              data-icon1=\"volume_mute\"\n              aria-label=\"volume mute\"\n              >\n      <i class=\"material-icons md-48\">volume_mute</i>\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":290,"y":260,"wires":[[]],"info":"  class=\"material-icons\"> volume_off"},{"id":"51e49c87.d11d24","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"vol +  *","order":14,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button\" \n        data-buttontype=\"repeater\"\n        data-interval=\"200\"\n        data-topic=\"volume/plus\"\n        data-payload=\"volume/plus\"\n        aria-label=\"volume plus\"\n    >\n    <span class=\"fa fa-plus remote-icon\"> </span>\n   </md-button>\n</div>\n\n\n","storeOutMessages":true,"fwdInMessages":false,"templateScope":"local","x":170,"y":260,"wires":[[]],"info":"<div id=\"regular_plus\">\n   <md-button class=\"md-button remote-button\">\n      <i class=\"fa fa-plus remote-icon\"></i>\n   </md-button>\n</div>"},{"id":"943b6dd3.e2cb4","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"vol -  *","order":17,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button\"\n        data-buttontype=\"repeater\"\n        data-interval=\"200\"\n        data-topic=\"volume/minus\"\n        data-payload=\"volume/minus\"\n        aria-label=\"volume minus\"\n    >\n    <span style=\"color:{{msg.colour}}\" class=\"fa fa-minus remote-icon\"> </span>\n   </md-button>\n</div>\n\n","storeOutMessages":true,"fwdInMessages":false,"templateScope":"local","x":170,"y":300,"wires":[[]],"info":"<div id=\"regular_plus\">\n   <md-button class=\"md-button remote-button\">\n      <i class=\"fa fa-minus remote-icon\"></i>\n   </md-button>\n</div>\n"},{"id":"d8f35a3a.809fe8","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"ch list","order":18,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button\"\n    data-topic=\"channel/list\"\n    data-payload=\"list\"\n    aria-label=\"channel list\"\n   >\n      <i class=\"fa fa-list-alt remote-icon\"></i>\n   </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":290,"y":300,"wires":[[]]},{"id":"2cc41ec0.eb5ad2","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"Ch -","order":19,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button\" \n        data-topic=\"channel/down\"\n        data-payload=\"down\" \n        aria-label=\"channel down\"\n    >\n    <i class=\"fa fa-chevron-down remote-icon\"></i>\n   </md-button>\n</div>\n\n\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":410,"y":300,"wires":[[]]},{"id":"5f5a8157.7c24f","type":"inject","z":"1dc2aa68.579f46","name":"","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"mute","payload":"1","payloadType":"num","x":130,"y":400,"wires":[["c3cd9eec.3dd3"]]},{"id":"ea979532.27cf58","type":"inject","z":"1dc2aa68.579f46","name":"","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"mute","payload":"0","payloadType":"num","x":130,"y":440,"wires":[["c3cd9eec.3dd3"]]},{"id":"c3cd9eec.3dd3","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"script for all buttons with class remote-button","order":27,"width":"1","height":"1","format":"<div>\n<!--diliberately emtpy - only need the script below -->\n</div>\n\n<script>\n\n(function($scope) {\n//debugger\n\n    //cause a small delay while things load \n    //ideally this would be an init event or on all parts of document loaded\n    //(may not be necessary!)\n    setTimeout(function() {\n        //debugger\n        $scope.init();\n    },100);\n    \n    var BUTTON_CLASS = \".remote-button\";\n\n    /** \n     * Initialise all buttons with class BUTTON_CLASS\n    */\n    $scope.init = function () {\n        //debugger\n        console.log(\"$scope.init called. Adding event handlers to all buttons with class '\" + BUTTON_CLASS + \"'.\");\n        var clickButtons = $(BUTTON_CLASS + \":not([data-buttontype='repeater'])\") \n        clickButtons.click(function(e){\n            //debugger\n            var btn = $(this)\n            var type = btn.data(\"buttontype\");//get the button type from attribute data-buttontype=\"xxxxx\"\n            var topic = btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n            var payload = btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n            \n            if(type == \"toggle\"){\n                var newPayload = payload == 1 ? 0 : 1;\n                setButtonState(btn,newPayload)\n                $scope.send({\"topic\":topic,\"payload\":newPayload, \"event\": \"click\"})\n            } else {\n                $scope.send({\"topic\":topic,\"payload\":payload})\n            }\n        }); \n        \n\n        var repeatButtons = $(BUTTON_CLASS + \"[data-buttontype='repeater']\") \n        repeatButtons.on('mousedown touchstart',function(e) {\n            e.preventDefault();\n            e.stopPropagation();\n            console.log(e.type);//remove me after debugging\n            var btn = this;\n            var $btn = $(btn);\n            if(btn._intervalId) return; //already in operation\n            btn._topic = $btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n            btn._payload = $btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n            btn._interval = $btn.data(\"interval\");//get the desired repeat duration\n            if(isNumeric(btn._interval) == false || btn._interval < 1) btn._interval = 300;//prevent zero & non numeric timeout value\n            var max = $btn.data(\"max\");//get the max repeat count\n            if(isNumeric(max) && max > 0) \n                btn._max = parseInt(max);\n            else\n                btn._max = undefined;\n            btn._count = 1;\n            $scope.send({\"topic\":btn._topic,\"payload\":btn.payload,\"event\":\"down\", \"count\": btn._count})\n            btn._lastevent = \"down\";\n            //remove any existing timer\n            if(btn._intervalId){\n                clearInterval(btn._intervalId);\n                btn._intervalId = null;\n            }\n            //start the timer if conditions are ok\n            if(!btn._max || (btn._max && btn._count < btn._max)){\n                btn._intervalId = setInterval(function() {\n                    if(btn._max && btn._count >= btn._max){\n                        clearInterval(btn._intervalId);\n                        btn._intervalId = null;\n                        return;\n                    }                    \n                    btn._count = btn._count + 1;\n                    $scope.send({\"topic\":btn._topic,\"payload\":btn.payload,\"event\":\"down\", \"count\": btn._count})\n                },btn._interval); \n            }\n        }).on('mouseup blur focusout mouseout touchend',function(e) {\n            e.preventDefault();\n            e.stopPropagation();\n            console.log(e.type);//remove me after debugging\n            var btn = this;\n            var $btn = $(btn);\n            \n            //next, if data-fireonup == true, then fire up event\n            var sendUpEvent = $btn.data(\"fireonup\") == true;\n            if(sendUpEvent && btn._lastevent === \"down\"){\n                btn._lastevent = \"up\"\n                $scope.send({\"topic\":btn._topic,\"payload\":btn._payload,\"event\":\"up\"})\n            }\n            \n            //finally, if the timer id is still valid, clear it (stop repeater)\n            if(btn._intervalId){\n                clearInterval(btn._intervalId);//stop timer\n                btn._intervalId = null;//clear timer id\n            }\n        });\n           \n    };\n    \n    //watch for node-red msgs\n    $scope.$watch('msg', function(msg) {\n        //debugger\n        if(!msg){ //if no msg \n            console.log(\"$scope.$watch('msg', ...) - msg is empty\");\n            return;\n        }\n        if(!msg.topic){ //if no topic set found\n            console.log(\"msg.topic is empty - cannot match this to any button\")\n            return; //stop processing!\n        }\n\n        var buttonSelector =  BUTTON_CLASS + \"[data-topic='\" + msg.topic + \"']\" \n        var $btn = $(buttonSelector);//get the button\n        \n        if(!$btn.length){ //if no button found\n            console.log(buttonSelector + \" not found - cannot set state\")\n            return; //stop processing!\n        }\n        \n        if($btn.length > 1){ //if MORE than one button found\n            console.log(buttonSelector + \" found more than 1 button - is this intended? Do you have the same data-topic set on multiple buttons?\")\n        }\n        \n        //see if this is a command - if so, process the command\n        if(typeof msg.payload === \"object\" && msg.payload.command){\n            processCommand($btn, msg.payload)\n            return;\n        }        \n        \n        if($btn.data(\"buttontype\") === \"toggle\"){\n            if(msg.payload == \"1\"){\n                setButtonState($btn, 1);\n            } else if(msg.payload == \"0\"){\n                setButtonState($btn, 0);\n            } else {\n               console.log(\"Invalid toggle value in msg.payload, cannot set \" + buttonSelector + \". Ensure msg.payload is either 0 or 1\") \n            }\n        }\n\n    }); \n    \n    /** \n    * helper function to set the correct icon & update the \"data-payload\" memory \n    */\n    function setButtonState($btn, state){\n        \n        $btn.data(\"payload\", state);//set data-payload to new state value (used as a memory)\n        \n        //determine the opposite state\n        var oppositeState;\n        if(state == \"1\" || state === 1){\n            state = 1; //normalise to a number\n            oppositeState = 0;\n        } else {\n            state = 0; //normalise to a number\n            oppositeState = 1;\n        }\n        \n        var $icon = $btn.find(\"i\"); //get the <i> element\n        if(!$icon.length){\n            $icon = $btn.find(\"span\"); //get the <span> element instead!\n        }\n        if(!$icon.length){\n            console.log(\"<i> or <span> not found inside button - cant toggle the icon!\")\n            return;//exit this function - nothing to toggle!\n        }\n        \n        //get the old icon and new icon names\n        var oldIcon = $btn.data(\"icon\" + oppositeState); //get icon1 or icon2 depending on oppositeState\n        var newIcon = $btn.data(\"icon\" + state); //get icon1 or icon2 depending on newPayload\n       \n        //if we have newIcon and an actual DOM element ($icon) - update it...\n        if(newIcon && $icon.length){\n            if(newIcon.includes(\"fa-\")){ \n                $icon.removeClass(oldIcon).addClass(newIcon);  // fontawesome\n            } else { \n                $icon.text(newIcon); // MDI\n            }\n        }\n    }\n        \n    function processCommand($btn, payload){\n        //first check payload is correct format...\n        if(!payload || !payload.command || !payload.value){\n            console.log(\"Cannot process command. Expected a payload object with .command and .value. \")\n        }\n        var cmd = payload.command.trim();\n        switch(cmd){\n            case \"addClass\":\n                $btn.addClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n                break;\n            case \"toggleClass\":\n                $btn.toggleClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n                break;\n            case \"removeClass\":\n                $btn.removeClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n                break;\n            default:\n                console.log(\"command '\" + payload.command + \"' is not supported\")\n        }\n    }        \n        \n    /** \n    * helper function to determine a value is REALLY a number \n    */\n    function isNumeric(n){\n        if(n === \"\") return false;\n        if(n === true || n === false) return false;\n        return !isNaN(parseFloat(n)) && isFinite(n);\n    }\n   \n\n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":290,"y":500,"wires":[["8fc2df4d.a1bd3"]]},{"id":"8fc2df4d.a1bd3","type":"debug","z":"1dc2aa68.579f46","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":350,"y":440,"wires":[]},{"id":"f9050d6a.1bd0c","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"lock","order":23,"width":"1","height":"1","format":"<div>\n   <md-button class=\"md-button remote-button bigger\" \n              data-payload=\"0\" \n              data-buttontype=\"toggle\"\n              data-topic=\"lock\"\n              data-icon0=\"fa-unlock\"\n              data-icon1=\"fa-lock\"\n              aria-label=\"lock\"\n              >\n      <span class=\"fa fa-unlock\" aria-hidden=\"true\"> </span>\n   </md-button>\n</div>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":290,"y":380,"wires":[[]]},{"id":"3fe73c93.01c494","type":"inject","z":"1dc2aa68.579f46","name":"addClass blinking, to lock","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"lock","payload":"{\"command\":\"addClass\",\"value\":\"blinking\"}","payloadType":"json","x":170,"y":560,"wires":[["c3cd9eec.3dd3"]]},{"id":"1fe1abb2.e810b4","type":"inject","z":"1dc2aa68.579f46","name":"removeClass blinking, from lock","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"lock","payload":"{\"command\":\"removeClass\",\"value\":\"blinking\"}","payloadType":"json","x":190,"y":600,"wires":[["c3cd9eec.3dd3"]]},{"id":"f8c597b1.8d5b88","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass blinking, on lock","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"lock","payload":"{\"command\":\"toggleClass\",\"value\":\"blinking\"}","payloadType":"json","x":180,"y":640,"wires":[["c3cd9eec.3dd3"]]},{"id":"69e77f1e.1c79e","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"lock2 (red)","order":24,"width":"1","height":"1","format":"<div>\n   <md-button class=\"md-button remote-button bigger red\" \n              data-payload=\"0\" \n              data-buttontype=\"toggle\"\n              data-topic=\"lock2\"\n              data-icon0=\"fa-unlock\"\n              data-icon1=\"fa-lock\"\n              aria-label=\"lock\"\n              >\n      <span class=\"fa fa-unlock\" aria-hidden=\"true\"> </span>\n   </md-button>\n</div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":430,"y":380,"wires":[[]]},{"id":"85eae62a.0aa128","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass blinking, on lock2","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"lock2","payload":"{\"command\":\"toggleClass\",\"value\":\"blinking\"}","payloadType":"json","x":180,"y":700,"wires":[["c3cd9eec.3dd3"]]},{"id":"2a76818d.cc66ae","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"limited to 3 & fires on up :  vol +  *","order":25,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button\" \n        data-buttontype=\"repeater\"\n        data-interval=\"200\"\n        data-max=\"3\"\n        data-fireonup=\"true\"\n        data-topic=\"volume/plus2\"\n        data-payload=\"volume/plus2\"\n        aria-label=\"volume plus\"\n    >\n    <span class=\"fa fa-plus remote-icon\"> </span>\n   </md-button>\n</div>\n\n\n","storeOutMessages":true,"fwdInMessages":false,"templateScope":"local","x":650,"y":380,"wires":[[]],"info":"<div id=\"regular_plus\">\n   <md-button class=\"md-button remote-button\">\n      <i class=\"fa fa-plus remote-icon\"></i>\n   </md-button>\n</div>"},{"id":"f33090b9.66112","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"limited to 1 & fires on up : vol -  *","order":26,"width":1,"height":1,"format":"<div>\n   <md-button class=\"md-button remote-button\"\n        data-buttontype=\"repeater\"\n        data-interval=\"200\"\n        data-max=\"1\"\n        data-fireonup=\"true\"\n        data-topic=\"volume/minus2\"\n        data-payload=\"volume/minus2\"\n        aria-label=\"volume minus\"\n    >\n    <span style=\"color:{{msg.colour}}\" class=\"fa fa-minus remote-icon\"> </span>\n   </md-button>\n</div>\n\n","storeOutMessages":true,"fwdInMessages":false,"templateScope":"local","x":650,"y":420,"wires":[[]],"info":"<div id=\"regular_plus\">\n   <md-button class=\"md-button remote-button\">\n      <i class=\"fa fa-minus remote-icon\"></i>\n   </md-button>\n</div>\n"},{"id":"6fbb0b6e.8b3274","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass disabled, on home","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"home","payload":"{\"command\":\"toggleClass\",\"value\":\"disabled\"}","payloadType":"json","x":190,"y":760,"wires":[["c3cd9eec.3dd3"]]},{"id":"1df0f0ce.3ed5cf","type":"ui_group","z":"","name":"Full_Remote2","tab":"d8c345fb.e094c8","order":3,"disp":false,"width":"3","collapse":false},{"id":"d8c345fb.e094c8","type":"ui_tab","z":"","name":"HDMI_TV_control","icon":"dashboard","order":7,"disabled":false,"hidden":false}]

p.s. there are no other flows in this instance of nr.

CSS basics. This selector finds elements which has classes remote-button and red

<div class="remote-button  red"></div>

It does not look for element id

<div id="remote-button red"></div>

Hi thank you for your response. Can you tell me what the purpose of style id="remote-buttons" ?

Also I cannot find how to format the flow like below:

Press that button first, then paste it to the indicated place
image

Thanks however I get this: Body is limited to 32000 characters; you entered 32377.

That will be definitely too much. It helps a lot if you can make your flow as small as possible to leave in it only those things which are really needed. In most cases the whole flow is not needed. The problem can be described and shown with couple of nodes only.

Thanks. Can you see why class 'blinking' does not work ?

    .remote-button.blinking{
    	animation:blinkingAnim1 0.8s infinite;
    }
    @keyframes blinkingAnim1{
    	0%{		background-color: var(--remote-button-background); color:var(--remote-button-foreground);}
    	50%{	background-color: var(--remote-button-foreground); color:var(--remote-button-background);}
    	100%{	background-color: var(--remote-button-background);	color:var(--remote-button-foreground);}
    }    

Another strange thing is that my instance of node red shows only one flow and if I export all flows it is too big to paste but if I export the current flow it fits ?

You have 3 options for export.


By using the 'selected nodes' option, you can trim down the flow to minimum. But still it should contain all what is needed to show your problem. And that may be tricky sometimes ...

Yes it certainly can. This flow seems to be a little unpredictable and I cannot pin down what is causing the problem.

Does the css look ok to you ?

<style>
    :root {
      --dashboard-unit-width: 48px;
      --dashboard-unit-height: 48px;
      --remote-button-background: black;
      --remote-button-foreground: #cccccc;
    }
    .nr-dashboard-template {
        padding: 0px;
    }
    
    .remote-button.disabled{
        cursor: not-allowed;
        pointer-events: none;
        color: #aaaaaa !important;
    }
    
    .remote-button:not([disabled]):hover{
         background-color: #232323 !important;
    }

    /*   This is the normal button definition  */
    .remote-button{
        background-color: var(--remote-button-background) !important;
        color: var(--remote-button-foreground) !important;
        height: var(--dashboard-unit-height);
        width: 100%;
        border-radius: 10px;
        font-size:1.0em;
        font-weight:normal;
        margin: 0;
        min-height: 36px;
        min-width: unset;
        line-height: unset;
    }
    /*  This is a sub-set which is invoked by */
    /*  <md-button class="md-button remote-button bigger"> */
    /*  note the (space) "bigger" at the end.  */
    .remote-button.bigger{
        font-weight:bold;
        font-size:1.5em;
    }
    /*  This is for buttons with a lot of text.  `font-size:0.7em` */
    /*  makes the font 70% normal size  */
    .remote-button.small{
        font-size:0.7em;
    }
    /*  This is for buttons with just icons, to upsize the size */
    /*  of the icon with the line: */
    /*  <i class="fa fa-fw fa-plus remote-icon"> in the other node  */
    .remote-icon{
        font-size:2.0em;
    }
    /*  This is the same as the other one, but it makes the icon smaller  */
    .remote-iconS{
        font-size:0.5em;
    }

    .remote-button.black{
        background-color: var(--remote-button-background) !important;
        color: var(--remote-button-foreground) !important;
    }

    .remote-button.red{
        background-color: blue !important;
        color: var(--remote-button-foreground) !important;
    }
    .remote-button.red:not([disabled]):hover{
         background-color: orange !important;
    }

    .remote-button.blinking{
    	animation:blinkingAnim1 0.8s infinite;
    }
    @keyframes blinkingAnim1{
    	0%{		background-color: var(--remote-button-background); color:var(--remote-button-foreground);}
    	50%{	background-color: var(--remote-button-foreground); color:var(--remote-button-background);}
    	100%{	background-color: var(--remote-button-background);	color:var(--remote-button-foreground);}
    }    
     
    .remote-button.red.blinking{
    	animation:blinkingAnim2 0.8s infinite;
    }
    @keyframes blinkingAnim2{
    	0%{		background-color: red; color:#cccccc;}
    	50%{	background-color: #cccccc; color:red;}
    	100%{	background-color: red;	color:#cccccc;}
    }     
</style>

CSS is fine. You can test the blinking with single template node.

<style>
    :root{
        --remote-button-background:red;
        --remote-button-foreground:blue;
    }
    .remote-button.blinking{
    	animation:blinkingAnim1 0.8s infinite;
    }
    @keyframes blinkingAnim1{
    	0%{		background-color: var(--remote-button-background); color:var(--remote-button-foreground);}
    	50%{	background-color: var(--remote-button-foreground); color:var(--remote-button-background);}
    	100%{	background-color: var(--remote-button-background);	color:var(--remote-button-foreground);}
    }
</style>
<div class="remote-button blinking">BUTTON</div>

Reduced it to 5 nodes but still no blinking blink !

[{"id":"1dc2aa68.579f46","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"67e2f110.35bbe","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"CSS only ","order":7,"width":0,"height":0,"format":"<style>\n    :root {\n      --dashboard-unit-width: 48px;\n      --dashboard-unit-height: 48px;\n      --remote-button-background: black;\n      --remote-button-foreground: #cccccc;\n    }\n    .nr-dashboard-template {\n        padding: 0px;\n    }\n    \n    .remote-button.disabled{\n        cursor: not-allowed;\n        pointer-events: none;\n        color: #aaaaaa !important;\n    }\n    \n    .remote-button:not([disabled]):hover{\n         background-color: #232323 !important;\n    }\n\n    /*   This is the normal button definition  */\n    .remote-button{\n        background-color: var(--remote-button-background) !important;\n        color: var(--remote-button-foreground) !important;\n        height: var(--dashboard-unit-height);\n        width: 100%;\n        border-radius: 10px;\n        font-size:1.0em;\n        font-weight:normal;\n        margin: 0;\n        min-height: 36px;\n        min-width: unset;\n        line-height: unset;\n    }\n    /*  This is a sub-set which is invoked by */\n    /*  <md-button class=\"md-button remote-button bigger\"> */\n    /*  note the (space) \"bigger\" at the end.  */\n    .remote-button.bigger{\n        font-weight:bold;\n        font-size:1.5em;\n    }\n    /*  This is for buttons with a lot of text.  `font-size:0.7em` */\n    /*  makes the font 70% normal size  */\n    .remote-button.small{\n        font-size:0.7em;\n    }\n    /*  This is for buttons with just icons, to upsize the size */\n    /*  of the icon with the line: */\n    /*  <i class=\"fa fa-fw fa-plus remote-icon\"> in the other node  */\n    .remote-icon{\n        font-size:2.0em;\n    }\n    /*  This is the same as the other one, but it makes the icon smaller  */\n    .remote-iconS{\n        font-size:0.5em;\n    }\n\n    .remote-button.black{\n        background-color: var(--remote-button-background) !important;\n        color: var(--remote-button-foreground) !important;\n    }\n\n    .remote-button.red{\n        background-color: blue !important;\n        color: var(--remote-button-foreground) !important;\n    }\n    .remote-button.red:not([disabled]):hover{\n         background-color: orange !important;\n    }\n\n    .remote-button.blinking{\n    \tanimation:blinkingAnim1 0.8s infinite;\n    }\n    @keyframes blinkingAnim1{\n    \t0%{\t\tbackground-color: var(--remote-button-background); color:var(--remote-button-foreground);}\n    \t50%{\tbackground-color: var(--remote-button-foreground); color:var(--remote-button-background);}\n    \t100%{\tbackground-color: var(--remote-button-background);\tcolor:var(--remote-button-foreground);}\n    }    \n     \n    .remote-button.red.blinking{\n    \tanimation:blinkingAnim2 0.8s infinite;\n    }\n    @keyframes blinkingAnim2{\n    \t0%{\t\tbackground-color: red; color:#cccccc;}\n    \t50%{\tbackground-color: #cccccc; color:red;}\n    \t100%{\tbackground-color: red;\tcolor:#cccccc;}\n    }     \n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"global","x":180,"y":40,"wires":[[]]},{"id":"c3cd9eec.3dd3","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"script for all buttons with class remote-button","order":27,"width":"1","height":"1","format":"<div>\n<!--diliberately emtpy - only need the script below -->\n</div>\n\n<script>\n\n(function($scope) {\n//debugger\n\n    //cause a small delay while things load \n    //ideally this would be an init event or on all parts of document loaded\n    //(may not be necessary!)\n    setTimeout(function() {\n        //debugger\n        $scope.init();\n    },100);\n    \n    var BUTTON_CLASS = \".remote-button\";\n\n    /** \n     * Initialise all buttons with class BUTTON_CLASS\n    */\n    $scope.init = function () {\n        //debugger\n        console.log(\"$scope.init called. Adding event handlers to all buttons with class '\" + BUTTON_CLASS + \"'.\");\n        var clickButtons = $(BUTTON_CLASS + \":not([data-buttontype='repeater'])\") \n        clickButtons.click(function(e){\n            //debugger\n            var btn = $(this)\n            var type = btn.data(\"buttontype\");//get the button type from attribute data-buttontype=\"xxxxx\"\n            var topic = btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n            var payload = btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n            \n            if(type == \"toggle\"){\n                var newPayload = payload == 1 ? 0 : 1;\n                setButtonState(btn,newPayload)\n                $scope.send({\"topic\":topic,\"payload\":newPayload, \"event\": \"click\"})\n            } else {\n                $scope.send({\"topic\":topic,\"payload\":payload})\n            }\n        }); \n        \n\n        var repeatButtons = $(BUTTON_CLASS + \"[data-buttontype='repeater']\") \n        repeatButtons.on('mousedown touchstart',function(e) {\n            e.preventDefault();\n            e.stopPropagation();\n            console.log(e.type);//remove me after debugging\n            var btn = this;\n            var $btn = $(btn);\n            if(btn._intervalId) return; //already in operation\n            btn._topic = $btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n            btn._payload = $btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n            btn._interval = $btn.data(\"interval\");//get the desired repeat duration\n            if(isNumeric(btn._interval) == false || btn._interval < 1) btn._interval = 300;//prevent zero & non numeric timeout value\n            var max = $btn.data(\"max\");//get the max repeat count\n            if(isNumeric(max) && max > 0) \n                btn._max = parseInt(max);\n            else\n                btn._max = undefined;\n            btn._count = 1;\n            $scope.send({\"topic\":btn._topic,\"payload\":btn.payload,\"event\":\"down\", \"count\": btn._count})\n            btn._lastevent = \"down\";\n            //remove any existing timer\n            if(btn._intervalId){\n                clearInterval(btn._intervalId);\n                btn._intervalId = null;\n            }\n            //start the timer if conditions are ok\n            if(!btn._max || (btn._max && btn._count < btn._max)){\n                btn._intervalId = setInterval(function() {\n                    if(btn._max && btn._count >= btn._max){\n                        clearInterval(btn._intervalId);\n                        btn._intervalId = null;\n                        return;\n                    }                    \n                    btn._count = btn._count + 1;\n                    $scope.send({\"topic\":btn._topic,\"payload\":btn.payload,\"event\":\"down\", \"count\": btn._count})\n                },btn._interval); \n            }\n        }).on('mouseup blur focusout mouseout touchend',function(e) {\n            e.preventDefault();\n            e.stopPropagation();\n            console.log(e.type);//remove me after debugging\n            var btn = this;\n            var $btn = $(btn);\n            \n            //next, if data-fireonup == true, then fire up event\n            var sendUpEvent = $btn.data(\"fireonup\") == true;\n            if(sendUpEvent && btn._lastevent === \"down\"){\n                btn._lastevent = \"up\"\n                $scope.send({\"topic\":btn._topic,\"payload\":btn._payload,\"event\":\"up\"})\n            }\n            \n            //finally, if the timer id is still valid, clear it (stop repeater)\n            if(btn._intervalId){\n                clearInterval(btn._intervalId);//stop timer\n                btn._intervalId = null;//clear timer id\n            }\n        });\n           \n    };\n    \n    //watch for node-red msgs\n    $scope.$watch('msg', function(msg) {\n        //debugger\n        if(!msg){ //if no msg \n            console.log(\"$scope.$watch('msg', ...) - msg is empty\");\n            return;\n        }\n        if(!msg.topic){ //if no topic set found\n            console.log(\"msg.topic is empty - cannot match this to any button\")\n            return; //stop processing!\n        }\n\n        var buttonSelector =  BUTTON_CLASS + \"[data-topic='\" + msg.topic + \"']\" \n        var $btn = $(buttonSelector);//get the button\n        \n        if(!$btn.length){ //if no button found\n            console.log(buttonSelector + \" not found - cannot set state\")\n            return; //stop processing!\n        }\n        \n        if($btn.length > 1){ //if MORE than one button found\n            console.log(buttonSelector + \" found more than 1 button - is this intended? Do you have the same data-topic set on multiple buttons?\")\n        }\n        \n        //see if this is a command - if so, process the command\n        if(typeof msg.payload === \"object\" && msg.payload.command){\n            processCommand($btn, msg.payload)\n            return;\n        }        \n        \n        if($btn.data(\"buttontype\") === \"toggle\"){\n            if(msg.payload == \"1\"){\n                setButtonState($btn, 1);\n            } else if(msg.payload == \"0\"){\n                setButtonState($btn, 0);\n            } else {\n               console.log(\"Invalid toggle value in msg.payload, cannot set \" + buttonSelector + \". Ensure msg.payload is either 0 or 1\") \n            }\n        }\n\n    }); \n    \n    /** \n    * helper function to set the correct icon & update the \"data-payload\" memory \n    */\n    function setButtonState($btn, state){\n        \n        $btn.data(\"payload\", state);//set data-payload to new state value (used as a memory)\n        \n        //determine the opposite state\n        var oppositeState;\n        if(state == \"1\" || state === 1){\n            state = 1; //normalise to a number\n            oppositeState = 0;\n        } else {\n            state = 0; //normalise to a number\n            oppositeState = 1;\n        }\n        \n        var $icon = $btn.find(\"i\"); //get the <i> element\n        if(!$icon.length){\n            $icon = $btn.find(\"span\"); //get the <span> element instead!\n        }\n        if(!$icon.length){\n            console.log(\"<i> or <span> not found inside button - cant toggle the icon!\")\n            return;//exit this function - nothing to toggle!\n        }\n        \n        //get the old icon and new icon names\n        var oldIcon = $btn.data(\"icon\" + oppositeState); //get icon1 or icon2 depending on oppositeState\n        var newIcon = $btn.data(\"icon\" + state); //get icon1 or icon2 depending on newPayload\n       \n        //if we have newIcon and an actual DOM element ($icon) - update it...\n        if(newIcon && $icon.length){\n            if(newIcon.includes(\"fa-\")){ \n                $icon.removeClass(oldIcon).addClass(newIcon);  // fontawesome\n            } else { \n                $icon.text(newIcon); // MDI\n            }\n        }\n    }\n        \n    function processCommand($btn, payload){\n        //first check payload is correct format...\n        if(!payload || !payload.command || !payload.value){\n            console.log(\"Cannot process command. Expected a payload object with .command and .value. \")\n        }\n        var cmd = payload.command.trim();\n        switch(cmd){\n            case \"addClass\":\n                $btn.addClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n                break;\n            case \"toggleClass\":\n                $btn.toggleClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n                break;\n            case \"removeClass\":\n                $btn.removeClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n                break;\n            default:\n                console.log(\"command '\" + payload.command + \"' is not supported\")\n        }\n    }        \n        \n    /** \n    * helper function to determine a value is REALLY a number \n    */\n    function isNumeric(n){\n        if(n === \"\") return false;\n        if(n === true || n === false) return false;\n        return !isNaN(parseFloat(n)) && isFinite(n);\n    }\n   \n\n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":290,"y":500,"wires":[["8fc2df4d.a1bd3"]]},{"id":"8fc2df4d.a1bd3","type":"debug","z":"1dc2aa68.579f46","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":350,"y":440,"wires":[]},{"id":"f9050d6a.1bd0c","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"lock","order":23,"width":"1","height":"1","format":"<div>\n   <md-button class=\"md-button remote-button bigger\" \n              data-payload=\"0\" \n              data-buttontype=\"toggle\"\n              data-topic=\"lock\"\n              data-icon0=\"fa-unlock\"\n              data-icon1=\"fa-lock\"\n              aria-label=\"lock\"\n              >\n      <span class=\"fa fa-unlock\" aria-hidden=\"true\"> </span>\n   </md-button>\n</div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":290,"y":380,"wires":[[]]},{"id":"f8c597b1.8d5b88","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass blinking, on lock","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"lock","payload":"{\"command\":\"toggleClass\",\"value\":\"blinking\"}","payloadType":"json","x":180,"y":640,"wires":[["c3cd9eec.3dd3"]]},{"id":"1df0f0ce.3ed5cf","type":"ui_group","z":"","name":"Full_Remote2","tab":"d8c345fb.e094c8","order":3,"disp":false,"width":"3","collapse":false},{"id":"d8c345fb.e094c8","type":"ui_tab","z":"","name":"HDMI_TV_control","icon":"dashboard","order":7,"disabled":false,"hidden":false}]

Added a couple of inject nodes.

[{"id":"1dc2aa68.579f46","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"f9af9a3d.4400d8","type":"ui_group","z":"","name":"HOME","tab":"","order":1,"disp":true,"width":3,"collapse":false},{"id":"1df0f0ce.3ed5cf","type":"ui_group","z":"","name":"Full_Remote2","tab":"d8c345fb.e094c8","order":3,"disp":false,"width":"3","collapse":false},{"id":"d8c345fb.e094c8","type":"ui_tab","z":"","name":"HDMI_TV_control","icon":"dashboard","order":7,"disabled":false,"hidden":false},{"id":"7afb0d90.fa2c74","type":"ui_base","theme":{"name":"theme-dark","lightTheme":{"default":"#0094CE","baseColor":"#0094CE","baseFont":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif","edited":true,"reset":false},"darkTheme":{"default":"#097479","baseColor":"#097479","baseFont":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif","edited":true,"reset":false},"customTheme":{"name":"Untitled Theme 1","default":"#4B7930","baseColor":"#4B7930","baseFont":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"},"themeState":{"base-color":{"default":"#097479","value":"#097479","edited":false},"page-titlebar-backgroundColor":{"value":"#097479","edited":false},"page-backgroundColor":{"value":"#111111","edited":false},"page-sidebar-backgroundColor":{"value":"#333333","edited":false},"group-textColor":{"value":"#0eb8c0","edited":false},"group-borderColor":{"value":"#555555","edited":false},"group-backgroundColor":{"value":"#333333","edited":false},"widget-textColor":{"value":"#eeeeee","edited":false},"widget-backgroundColor":{"value":"#097479","edited":false},"widget-borderColor":{"value":"#333333","edited":false},"base-font":{"value":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"}},"angularTheme":{"primary":"indigo","accents":"blue","warn":"red","background":"grey"}},"site":{"name":"Node-RED Dashboard","hideToolbar":"false","allowSwipe":"false","lockMenu":"false","allowTempTheme":"true","dateFormat":"DD/MM/YYYY","sizes":{"sx":48,"sy":48,"gx":6,"gy":6,"cx":6,"cy":6,"px":0,"py":0}}},{"id":"67e2f110.35bbe","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"CSS only ","order":7,"width":0,"height":0,"format":"<style>\n    :root {\n      --dashboard-unit-width: 48px;\n      --dashboard-unit-height: 48px;\n      --remote-button-background: black;\n      --remote-button-foreground: #cccccc;\n    }\n    .nr-dashboard-template {\n        padding: 0px;\n    }\n    \n    .remote-button.disabled{\n        cursor: not-allowed;\n        pointer-events: none;\n        color: #aaaaaa !important;\n    }\n    \n    .remote-button:not([disabled]):hover{\n         background-color: #232323 !important;\n    }\n\n    /*   This is the normal button definition  */\n    .remote-button{\n        background-color: var(--remote-button-background) !important;\n        color: var(--remote-button-foreground) !important;\n        height: var(--dashboard-unit-height);\n        width: 100%;\n        border-radius: 10px;\n        font-size:1.0em;\n        font-weight:normal;\n        margin: 0;\n        min-height: 36px;\n        min-width: unset;\n        line-height: unset;\n    }\n    /*  This is a sub-set which is invoked by */\n    /*  <md-button class=\"md-button remote-button bigger\"> */\n    /*  note the (space) \"bigger\" at the end.  */\n    .remote-button.bigger{\n        font-weight:bold;\n        font-size:1.5em;\n    }\n    /*  This is for buttons with a lot of text.  `font-size:0.7em` */\n    /*  makes the font 70% normal size  */\n    .remote-button.small{\n        font-size:0.7em;\n    }\n    /*  This is for buttons with just icons, to upsize the size */\n    /*  of the icon with the line: */\n    /*  <i class=\"fa fa-fw fa-plus remote-icon\"> in the other node  */\n    .remote-icon{\n        font-size:2.0em;\n    }\n    /*  This is the same as the other one, but it makes the icon smaller  */\n    .remote-iconS{\n        font-size:0.5em;\n    }\n\n    .remote-button.black{\n        background-color: var(--remote-button-background) !important;\n        color: var(--remote-button-foreground) !important;\n    }\n\n    .remote-button.red{\n        background-color: blue !important;\n        color: var(--remote-button-foreground) !important;\n    }\n    .remote-button.red:not([disabled]):hover{\n         background-color: orange !important;\n    }\n\n    .remote-button.blinking{\n    \tanimation:blinkingAnim1 0.8s infinite;\n    }\n    @keyframes blinkingAnim1{\n    \t0%{\t\tbackground-color: var(--remote-button-background); color:var(--remote-button-foreground);}\n    \t50%{\tbackground-color: var(--remote-button-foreground); color:var(--remote-button-background);}\n    \t100%{\tbackground-color: var(--remote-button-background);\tcolor:var(--remote-button-foreground);}\n    }    \n     \n    .remote-button.red.blinking{\n    \tanimation:blinkingAnim2 0.8s infinite;\n    }\n    @keyframes blinkingAnim2{\n    \t0%{\t\tbackground-color: red; color:#cccccc;}\n    \t50%{\tbackground-color: #cccccc; color:red;}\n    \t100%{\tbackground-color: red;\tcolor:#cccccc;}\n    }     \n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"global","x":160,"y":40,"wires":[[]]},{"id":"c3cd9eec.3dd3","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"script for all buttons with class remote-button","order":27,"width":"1","height":"1","format":"<div>\n<!--diliberately emtpy - only need the script below -->\n</div>\n\n<script>\n\n(function($scope) {\n//debugger\n\n    //cause a small delay while things load \n    //ideally this would be an init event or on all parts of document loaded\n    //(may not be necessary!)\n    setTimeout(function() {\n        //debugger\n        $scope.init();\n    },100);\n    \n    var BUTTON_CLASS = \".remote-button\";\n\n    /** \n     * Initialise all buttons with class BUTTON_CLASS\n    */\n    $scope.init = function () {\n        //debugger\n        console.log(\"$scope.init called. Adding event handlers to all buttons with class '\" + BUTTON_CLASS + \"'.\");\n        var clickButtons = $(BUTTON_CLASS + \":not([data-buttontype='repeater'])\") \n        clickButtons.click(function(e){\n            //debugger\n            var btn = $(this)\n            var type = btn.data(\"buttontype\");//get the button type from attribute data-buttontype=\"xxxxx\"\n            var topic = btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n            var payload = btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n            \n            if(type == \"toggle\"){\n                var newPayload = payload == 1 ? 0 : 1;\n                setButtonState(btn,newPayload)\n                $scope.send({\"topic\":topic,\"payload\":newPayload, \"event\": \"click\"})\n            } else {\n                $scope.send({\"topic\":topic,\"payload\":payload})\n            }\n        }); \n        \n\n        var repeatButtons = $(BUTTON_CLASS + \"[data-buttontype='repeater']\") \n        repeatButtons.on('mousedown touchstart',function(e) {\n            e.preventDefault();\n            e.stopPropagation();\n            console.log(e.type);//remove me after debugging\n            var btn = this;\n            var $btn = $(btn);\n            if(btn._intervalId) return; //already in operation\n            btn._topic = $btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n            btn._payload = $btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n            btn._interval = $btn.data(\"interval\");//get the desired repeat duration\n            if(isNumeric(btn._interval) == false || btn._interval < 1) btn._interval = 300;//prevent zero & non numeric timeout value\n            var max = $btn.data(\"max\");//get the max repeat count\n            if(isNumeric(max) && max > 0) \n                btn._max = parseInt(max);\n            else\n                btn._max = undefined;\n            btn._count = 1;\n            $scope.send({\"topic\":btn._topic,\"payload\":btn.payload,\"event\":\"down\", \"count\": btn._count})\n            btn._lastevent = \"down\";\n            //remove any existing timer\n            if(btn._intervalId){\n                clearInterval(btn._intervalId);\n                btn._intervalId = null;\n            }\n            //start the timer if conditions are ok\n            if(!btn._max || (btn._max && btn._count < btn._max)){\n                btn._intervalId = setInterval(function() {\n                    if(btn._max && btn._count >= btn._max){\n                        clearInterval(btn._intervalId);\n                        btn._intervalId = null;\n                        return;\n                    }                    \n                    btn._count = btn._count + 1;\n                    $scope.send({\"topic\":btn._topic,\"payload\":btn.payload,\"event\":\"down\", \"count\": btn._count})\n                },btn._interval); \n            }\n        }).on('mouseup blur focusout mouseout touchend',function(e) {\n            e.preventDefault();\n            e.stopPropagation();\n            console.log(e.type);//remove me after debugging\n            var btn = this;\n            var $btn = $(btn);\n            \n            //next, if data-fireonup == true, then fire up event\n            var sendUpEvent = $btn.data(\"fireonup\") == true;\n            if(sendUpEvent && btn._lastevent === \"down\"){\n                btn._lastevent = \"up\"\n                $scope.send({\"topic\":btn._topic,\"payload\":btn._payload,\"event\":\"up\"})\n            }\n            \n            //finally, if the timer id is still valid, clear it (stop repeater)\n            if(btn._intervalId){\n                clearInterval(btn._intervalId);//stop timer\n                btn._intervalId = null;//clear timer id\n            }\n        });\n           \n    };\n    \n    //watch for node-red msgs\n    $scope.$watch('msg', function(msg) {\n        //debugger\n        if(!msg){ //if no msg \n            console.log(\"$scope.$watch('msg', ...) - msg is empty\");\n            return;\n        }\n        if(!msg.topic){ //if no topic set found\n            console.log(\"msg.topic is empty - cannot match this to any button\")\n            return; //stop processing!\n        }\n\n        var buttonSelector =  BUTTON_CLASS + \"[data-topic='\" + msg.topic + \"']\" \n        var $btn = $(buttonSelector);//get the button\n        \n        if(!$btn.length){ //if no button found\n            console.log(buttonSelector + \" not found - cannot set state\")\n            return; //stop processing!\n        }\n        \n        if($btn.length > 1){ //if MORE than one button found\n            console.log(buttonSelector + \" found more than 1 button - is this intended? Do you have the same data-topic set on multiple buttons?\")\n        }\n        \n        //see if this is a command - if so, process the command\n        if(typeof msg.payload === \"object\" && msg.payload.command){\n            processCommand($btn, msg.payload)\n            return;\n        }        \n        \n        if($btn.data(\"buttontype\") === \"toggle\"){\n            if(msg.payload == \"1\"){\n                setButtonState($btn, 1);\n            } else if(msg.payload == \"0\"){\n                setButtonState($btn, 0);\n            } else {\n               console.log(\"Invalid toggle value in msg.payload, cannot set \" + buttonSelector + \". Ensure msg.payload is either 0 or 1\") \n            }\n        }\n\n    }); \n    \n    /** \n    * helper function to set the correct icon & update the \"data-payload\" memory \n    */\n    function setButtonState($btn, state){\n        \n        $btn.data(\"payload\", state);//set data-payload to new state value (used as a memory)\n        \n        //determine the opposite state\n        var oppositeState;\n        if(state == \"1\" || state === 1){\n            state = 1; //normalise to a number\n            oppositeState = 0;\n        } else {\n            state = 0; //normalise to a number\n            oppositeState = 1;\n        }\n        \n        var $icon = $btn.find(\"i\"); //get the <i> element\n        if(!$icon.length){\n            $icon = $btn.find(\"span\"); //get the <span> element instead!\n        }\n        if(!$icon.length){\n            console.log(\"<i> or <span> not found inside button - cant toggle the icon!\")\n            return;//exit this function - nothing to toggle!\n        }\n        \n        //get the old icon and new icon names\n        var oldIcon = $btn.data(\"icon\" + oppositeState); //get icon1 or icon2 depending on oppositeState\n        var newIcon = $btn.data(\"icon\" + state); //get icon1 or icon2 depending on newPayload\n       \n        //if we have newIcon and an actual DOM element ($icon) - update it...\n        if(newIcon && $icon.length){\n            if(newIcon.includes(\"fa-\")){ \n                $icon.removeClass(oldIcon).addClass(newIcon);  // fontawesome\n            } else { \n                $icon.text(newIcon); // MDI\n            }\n        }\n    }\n        \n    function processCommand($btn, payload){\n        //first check payload is correct format...\n        if(!payload || !payload.command || !payload.value){\n            console.log(\"Cannot process command. Expected a payload object with .command and .value. \")\n        }\n        var cmd = payload.command.trim();\n        switch(cmd){\n            case \"addClass\":\n                $btn.addClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n                break;\n            case \"toggleClass\":\n                $btn.toggleClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n                break;\n            case \"removeClass\":\n                $btn.removeClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n                break;\n            default:\n                console.log(\"command '\" + payload.command + \"' is not supported\")\n        }\n    }        \n        \n    /** \n    * helper function to determine a value is REALLY a number \n    */\n    function isNumeric(n){\n        if(n === \"\") return false;\n        if(n === true || n === false) return false;\n        return !isNaN(parseFloat(n)) && isFinite(n);\n    }\n   \n\n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":290,"y":500,"wires":[["8fc2df4d.a1bd3"]]},{"id":"8fc2df4d.a1bd3","type":"debug","z":"1dc2aa68.579f46","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":350,"y":440,"wires":[]},{"id":"f9050d6a.1bd0c","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"lock","order":23,"width":"1","height":"1","format":"<div>\n   <md-button class=\"md-button remote-button bigger\" \n              data-payload=\"0\" \n              data-buttontype=\"toggle\"\n              data-topic=\"lock\"\n              data-icon0=\"fa-unlock\"\n              data-icon1=\"fa-lock\"\n              aria-label=\"lock\"\n              >\n      <span class=\"fa fa-unlock\" aria-hidden=\"true\"> </span>\n   </md-button>\n</div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":290,"y":380,"wires":[[]]},{"id":"f8c597b1.8d5b88","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass blinking, on lock","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"lock","payload":"{\"command\":\"toggleClass\",\"value\":\"blinking\"}","payloadType":"json","x":180,"y":640,"wires":[["c3cd9eec.3dd3"]]},{"id":"49f3f717.1f2d38","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass red, on lock","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"lock","payload":"{\"command\":\"toggleClass\",\"value\":\"red\"}","payloadType":"json","x":160,"y":680,"wires":[["c3cd9eec.3dd3"]]}]

But still broken.

Well what can I see is that the remote-button is not meant to be blinking.
See the css

.remote-button{
        background-color: var(--remote-button-background) !important;
        ....

This does what it says. The background color can't be changed.
You can of course remove the !important from here but I can't really say what kind of side effects it may bring in :upside_down_face:

Thank you again. Found the blinking issue.

<style>
    :root {
      --dashboard-unit-width: 48px;
      --dashboard-unit-height: 48px;
      --remote-button-background: black;
      --remote-button-foreground: #cccccc;
    }
    .nr-dashboard-template {
        padding: 0px;
    }
    
    .remote-button.disabled{
        cursor: not-allowed;
        pointer-events: none;
        color: #aaaaaa !important;
    }
    
    .remote-button:not([disabled]):hover{
         background-color: #232323 !important;
    }

    /*   This is the normal button definition  */
    .remote-button{
        background-color: var(--remote-button-background) !important;
        color: var(--remote-button-foreground) !important;
        height: var(--dashboard-unit-height);
        width: 100%;
        border-radius: 10px;
        font-size:1.0em;
        font-weight:normal;
        margin: 0;
        min-height: 36px;
        min-width: unset;
        line-height: unset;
    }
    /*  This is a sub-set which is invoked by */
    /*  <md-button class="md-button remote-button bigger"> */
    /*  note the (space) "bigger" at the end.  */
    .remote-button.bigger{
        font-weight:bold;
        font-size:1.5em;
    }
    /*  This is for buttons with a lot of text.  `font-size:0.7em` */
    /*  makes the font 70% normal size  */
    .remote-button.small{
        font-size:0.7em;
    }
    /*  This is for buttons with just icons, to upsize the size */
    /*  of the icon with the line: */
    /*  <i class="fa fa-fw fa-plus remote-icon"> in the other node  */
    .remote-icon{
        font-size:2.0em;
    }
    /*  This is the same as the other one, but it makes the icon smaller  */
    .remote-iconS{
        font-size:0.5em;
    }

    .remote-button.black{
        background-color: var(--remote-button-background) !important;
        color: var(--remote-button-foreground) !important;
    }

    .remote-button.blinking {
        animation: blink-animation 1s steps(5, start) infinite;
        -webkit-animation: blink-animation 1s steps(5, start) infinite;
    }
    @keyframes blink-animation {
        to { visibility: hidden; }
    }
    @-webkit-keyframes blink-animation {
        to { visibility: hidden; }
    }
    
    .remote-button.red{
        background-color: blue !important;
        color: var(--remote-button-foreground) !important;
    }
    .remote-button.red:not([disabled]):hover{
         background-color: orange !important;
    }
</style>

I have CSS problem with 'disabled' and 'red' (actually outputs blue but that is so I can detect if the style has inadvertantly overriden until the bugs are out).

I expect disabled to have an effect no mater what other attributes are active (flashing, colour changes or whatever).

Well, it is not easy (specially for me) to explain how to set up the CSS for complex structure in logical manner.
It takes to know almost all about the elements on page and of course the design decisions.
To achieve the goal (may be first time in your life) via the "learning on the fly" takes patience and time.

First thing what I would try is to get rid of all !important statements and make all elements basic states still to look right. The !important should be used only if there is no more legal ways to achieve something and this situation looks far from it now (for me at least).

I thought the use of !important was essential in order to minimise any side effects ?

I do not understand all the semantics here by a long chalk. But I know it should be simple to specify styles that can all apply.

So a colour should be able to flash (which it does) so why would mixing in disabled be any different ?

I agree here in the context of publishing content on various devices of different shape and size. In this case the ui is an embedded control panel where the semantics of colour/flash/enabled/disabled etc are not to be reinterpreted.

That's why the css overrides become usefull. But must be done wisely. State interpretation for html elements like buttons is not limited anyhow. Add and/or remove classes from element is most common way to do it. And you have working examples in your hand already. Keep practicing and do experiments. Ask specific questions to get well targeted help.

Thank you.
Can this approach also use css colour change on payload. Like for a toggle button with 1 style for up and another for down ?
If so do you have a quick example ?