Instantly share code, notes, and snippets.
Last active
June 10, 2020 15:10
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save taylor8294/ad3551afa1539356193e9c3e56294146 to your computer and use it in GitHub Desktop.
Small utility to produce native-looking app install banners (like https://i.imgur.com/Qu3hoGh.png) in mobile browsers. Relies on jQuery being present in the page. Initiate like `new AppBanner({ios:{id:'1500000000',title:'My App',store:'gb'},android:{id:'com.example.app',title:'My App'},showAfter:30});`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* Native App Install Banner v1 ~ (c) 2020 Alex Taylor */ | |
| (function (window, document, $, undefined) { | |
| var AppBanner = function (options) { | |
| var OS = AppBanner.OS; | |
| // merge default options with user config | |
| this.options = $.extend(true, {}, AppBanner.defaults, options); | |
| if (this.options.debug) { | |
| AppBanner.isCompatible = true; | |
| AppBanner.OS = typeof this.options.debug == 'string' ? this.options.debug : AppBanner.OS == 'unsupported' ? 'android' : AppBanner.OS; | |
| AppBanner.OSVersion = AppBanner.OS == 'ios' ? '13' : '10'; | |
| } | |
| // the element the message will be appended to | |
| this.$container = this.options.container ? $(this.options.container) : $(document.body); | |
| // check the device is supported | |
| if (!AppBanner.isCompatible) { | |
| console.warn("App banner: device not supported"); | |
| return; | |
| } | |
| // check if the app is in standalone mode | |
| if (AppBanner.isStandalone) { | |
| console.warn("App banner: in standalone mode"); | |
| //return; | |
| } | |
| $.extend(true, this, AppBanner[OS], this.options[OS]); | |
| this.init(); | |
| }; | |
| //Device sniffing | |
| var _ua = window.navigator.userAgent; | |
| AppBanner.isRetina = window.devicePixelRatio && window.devicePixelRatio > 1; | |
| AppBanner.isIDevice = (/iphone|ipod|ipad/i).test(_ua); | |
| AppBanner.isMobileChrome = _ua.indexOf('Android') > -1 && (/Chrome\/[.0-9]*/).test(_ua) && _ua.indexOf("Version") == -1; | |
| AppBanner.isMobileIE = _ua.indexOf('Windows Phone') > -1; | |
| AppBanner.isMobileSafari = AppBanner.isIDevice && _ua.indexOf('Safari') > -1 && _ua.indexOf('CriOS') < 0; | |
| AppBanner.OS = AppBanner.isIDevice ? 'ios' : AppBanner.isMobileChrome ? 'android' : AppBanner.isMobileIE ? 'windows' : 'unsupported'; | |
| var OSv = _ua.match(/(OS|Android) (\d+[_\.]\d+)/); | |
| AppBanner.OSVersion = OSv && OSv[2] ? +OSv[2].replace('_', '.') : 0; | |
| AppBanner.isStandalone = 'standalone' in window.navigator && window.navigator.standalone, | |
| AppBanner.isTablet = (AppBanner.isMobileSafari && _ua.indexOf('iPad') > -1) || (AppBanner.isMobileChrome && _ua.indexOf('Mobile') < 0), | |
| AppBanner.isCompatible = (AppBanner.isMobileSafari && (AppBanner.OSVersion >= 6 || AppBanner.OSVersion == 0)) || AppBanner.isMobileChrome; // TODO: add winphone | |
| AppBanner.defaults = { | |
| showAfter: 30, | |
| ios: { | |
| id: '', | |
| url: 'https://apps.apple.com/\${store}/app/id\${id}', | |
| icon: '', | |
| title: '', | |
| developer: '', | |
| rating: 4.5, | |
| desc: 'GET - On the App Store', | |
| button: 'View', | |
| store: 'us' | |
| }, | |
| android: { | |
| id: '', | |
| url: 'https://play.google.com/store/apps/details?id=\${id}', | |
| icon: '', | |
| title: '', | |
| rating: 4.5, | |
| button: 'Install' | |
| }, | |
| //windows: {}, //TODO | |
| debug: false | |
| }; | |
| AppBanner.waitForDom = function(){ | |
| return new Promise(function (resolve, reject) { | |
| (function pollDom(){ | |
| if (window.jQuery.isReady) return resolve(); | |
| setTimeout(pollDom, 30); | |
| })(); | |
| }); | |
| } | |
| AppBanner.template = function(str,obj){ | |
| var keys = Object.keys(obj); | |
| var vals = Object.keys(obj).map(function(key){ return obj[key] }); | |
| return new Function(...keys, `return \`${str}\`;`)(...vals); | |
| } | |
| AppBanner.preventDefault = function(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| AppBanner.ios = { | |
| html: ` | |
| <div class="app-banner ios"> | |
| <div class="app-banner-close"> | |
| <a href="javascript:">✕</a> | |
| </div> | |
| <div class="app-banner-icon"> | |
| <img width="70" src="\${icon}" /> | |
| </div> | |
| <div class="app-banner-content"> | |
| <div class="app-banner-title"> | |
| \${title} | |
| </div> | |
| <div class="app-banner-developer"> | |
| \${developer} | |
| </div> | |
| <div class="app-banner-rating"> | |
| <img width="8" src="\${rating > 0 ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAARUlEQVQoU2NkQAP/pjP/BwkxZf5lRJZC4YAkiFIIUwQzCdlUFBPxKkSXRHczjA82kZBikBPgVuNSDHMnjRQiWwuzCl0MAG7zKqLf346TAAAAAElFTkSuQmCC' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAARUlEQVQoU2NkQAMnTpz4DxKysLBgRJZC4YAkiFIIUwQzCdlUFBPxKkSXRHczjA82kZBikBPgVuNSDHMnjRQiWwuzCl0MAKkHK2I7iNRuAAAAAElFTkSuQmCC' }" /> | |
| <img width="8" src="\${rating > 1 ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAARUlEQVQoU2NkQAP/pjP/BwkxZf5lRJZC4YAkiFIIUwQzCdlUFBPxKkSXRHczjA82kZBikBPgVuNSDHMnjRQiWwuzCl0MAG7zKqLf346TAAAAAElFTkSuQmCC' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAARUlEQVQoU2NkQAMnTpz4DxKysLBgRJZC4YAkiFIIUwQzCdlUFBPxKkSXRHczjA82kZBikBPgVuNSDHMnjRQiWwuzCl0MAKkHK2I7iNRuAAAAAElFTkSuQmCC' }" /> | |
| <img width="8" src="\${rating > 2 ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAARUlEQVQoU2NkQAP/pjP/BwkxZf5lRJZC4YAkiFIIUwQzCdlUFBPxKkSXRHczjA82kZBikBPgVuNSDHMnjRQiWwuzCl0MAG7zKqLf346TAAAAAElFTkSuQmCC' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAARUlEQVQoU2NkQAMnTpz4DxKysLBgRJZC4YAkiFIIUwQzCdlUFBPxKkSXRHczjA82kZBikBPgVuNSDHMnjRQiWwuzCl0MAKkHK2I7iNRuAAAAAElFTkSuQmCC' }" /> | |
| <img width="8" src="\${rating > 3 ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAARUlEQVQoU2NkQAP/pjP/BwkxZf5lRJZC4YAkiFIIUwQzCdlUFBPxKkSXRHczjA82kZBikBPgVuNSDHMnjRQiWwuzCl0MAG7zKqLf346TAAAAAElFTkSuQmCC' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAARUlEQVQoU2NkQAMnTpz4DxKysLBgRJZC4YAkiFIIUwQzCdlUFBPxKkSXRHczjA82kZBikBPgVuNSDHMnjRQiWwuzCl0MAKkHK2I7iNRuAAAAAElFTkSuQmCC' }" /> | |
| <img width="8" src="\${rating > 4 ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAARUlEQVQoU2NkQAP/pjP/BwkxZf5lRJZC4YAkiFIIUwQzCdlUFBPxKkSXRHczjA82kZBikBPgVuNSDHMnjRQiWwuzCl0MAG7zKqLf346TAAAAAElFTkSuQmCC' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAARUlEQVQoU2NkQAMnTpz4DxKysLBgRJZC4YAkiFIIUwQzCdlUFBPxKkSXRHczjA82kZBikBPgVuNSDHMnjRQiWwuzCl0MAKkHK2I7iNRuAAAAAElFTkSuQmCC' }" /> | |
| </div> | |
| <div class="app-banner-desc"> | |
| \${desc} | |
| </div> | |
| </div> | |
| <div class="app-banner-button"> | |
| <a href="\${url}" target="_blank">\${button}</a> | |
| </div> | |
| </div>`, | |
| css: ` | |
| body { | |
| position: relative; | |
| min-height: 100vh; | |
| } | |
| .app-banner { | |
| display: table; | |
| position: fixed; | |
| z-index: 9999999; | |
| left: 0; | |
| box-sizing: border-box; | |
| width: 100%; | |
| margin: 0; | |
| text-rendering: optimizeLegibility; | |
| -webkit-font-smoothing: antialiased; | |
| -moz-osx-font-smoothing: grayscale; | |
| } | |
| .app-banner.ios { | |
| table-layout: fixed; | |
| top: -90px; | |
| background-color: #f2f2f2; | |
| font-family: Arial, Helvetica, sans-serif; | |
| font-size: 12px; | |
| padding: 5px 0; | |
| } | |
| .app-banner.ios > div { | |
| display: table-cell; | |
| vertical-align: middle; | |
| text-align: center; | |
| padding: 0; | |
| margin: 0; | |
| } | |
| .app-banner.ios .app-banner-close { | |
| width: 30px; | |
| } | |
| .app-banner.ios .app-banner-close a, .app-banner.ios .app-banner-close a:hover, .app-banner.ios .app-banner-close a:active, .app-banner.ios .app-banner-close a:visited, .app-banner.ios .app-banner-close a:focus { | |
| color: #6f6f6f; | |
| text-decoration: none; | |
| } | |
| .app-banner.ios .app-banner-icon { | |
| text-align: left; | |
| width: 80px; | |
| } | |
| .app-banner.ios .app-banner-icon img { | |
| border-radius: 22.5%; | |
| } | |
| .app-banner.ios .app-banner-content { | |
| text-align: left; | |
| color: #0f0f0f; | |
| } | |
| .app-banner.ios .app-banner-content .app-banner-title { | |
| font-size: 14px; | |
| font-weight: 500; | |
| color: #010101; | |
| margin-bottom: 2px; | |
| } | |
| .app-banner.ios .app-banner-content .app-banner-desc { | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| .app-banner.ios .app-banner-button { | |
| width: 90px; | |
| } | |
| .app-banner.ios .app-banner-button a, .app-banner.ios .app-banner-button a:hover, .app-banner.ios .app-banner-button a:active, .app-banner.ios .app-banner-button a:visited, .app-banner.ios .app-banner-button a:focus { | |
| font-size: 16px; | |
| color: #0078ff; | |
| text-decoration: none; | |
| }` | |
| } | |
| AppBanner.android = { | |
| html: ` | |
| <div class="app-banner android"> | |
| <div class="app-banner-row"> | |
| <div class="app-banner-icon"> | |
| <img width="70" src="\${icon}" /> | |
| </div> | |
| <div class="app-banner-content" colspan="2"> | |
| <div class="app-banner-title"> | |
| \${title} | |
| </div> | |
| <div class="app-banner-rating"> | |
| <img width="16" src="\${rating > 0.5 ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfCAYAAAAfrhY5AAADqElEQVRYR8WVfUzMcRzH3+eOuuNqOosopVNLpDKLkMdj1i1Km9nRg4exmWebhpGx9TBGD39gniajUWkqFblqRSd6sNx1SUp5WMgzPxV3X/u1dXVc3X1/Z/P79/t+v1+fz/f3+X1+PPzHh2cN+47mEpFNjuCcwdlY0VJ6taj+9MpYeTrnDM7Gy6pEUtGUjag5exEwYTmnHE4m9lUlFm4iLe01cHH0wP7gK5xyOJlK67NIjjoFDMNALB6Bo+ElnHI4ma5VpZJidZphVpf6RiHUfwt1FrWBJcYXKkhr+1MD3GPsVOxeco46i9rAEnelLyJM1xcD3NHeFYfDMqmzqA3lT2+RjMo4dP9iDHAbGxHCA3ZirjSUKo9KzNIyaxNIcV029ERvtJ9mSkMQHXSQKo9KzNLiciNI2/sGI7CO6CAZ6YyE0ByqvB7xqdI9pLVDC/JHN3+uXvb8a+cH6PXGXbM6Pp8Psa3E7LZmdWPs3LFVlsQzVHpcuYM0vrhn1mytQOrkgzC/PZg42qsPzoYWazKI8slFvP/yxlqGkZ/H42Gsgxumu4ZglocM9kKnnqaN3lFnN5Pc1FG5Tam+Du3r+/+sAO/xgZD7RSukDtPS+4eaHBCGYWIK6s8mqJpv4BvT9z3TViMebocFnhEI9o02yRl0Osuf5ZMKbRbaPmqg0+ksZg8dKoCjeAJme8qx0Gv1gAyLPo1U5VaiffXA5JSbqijAPRiLvKPgOsp90HyL4Jk1iaRYfd0iONt1oHsoFIExZrPNCtjOjuVvJE1vay2+dieJG2JDMsxmmxU0vislp0pie/7dNI9i5gHM9Vpm3bVnVCaTovo0DOEN+YvNbquBBnGGVI61QYesgx/JXUnaOprA5/ENcKHQFlKJPzzGeaGupRYvPmvR3dVlVNxI8WjEh+dxh1e3KHckKTefGC6w6/t92tpiw7y9kNrPTxGJRNvZg7vN10j2wzP4/uOTQTdMIETKmjJucOYnE5NZk5RQor4CG74QAoEAbg5TsdhHAV+XeX+Fal6pSKHmPJrf1EGn04NdqYGe4YgcZOoHrSw+L4K0djT0gP1dZVgfdMTsgKZXxZF72nz80nXBRTIJ+0PS6JdM1fOyaRdU+6qH6QWQ+6+DzDvSLLj3zssa8khBQyqYTgbJq8rp4TcfnyQvPzViU9AJi6G9cKa7M1nTdneF6nmWs0TkhtWzTC+cAYPV7bcfTRmzxI/m2zalffyyiPFxXiwydUbdlbXF9Pf/BkFmRi9vqxubAAAAAElFTkSuQmCC' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfCAYAAAAfrhY5AAAEWElEQVRYR72WfUwbdRjHn1/vSs+7Ky1XaKVrSQgvcwvZNBp5SbbMP7YFp0t8ISEp6vxjxqhgdInTJUa3GWMWg6ZuvqNxAzYwGGaULXMi4NbChsMNXWGWt1HelJdee6MtPfiZY8IGtLZ3uv3+a+95vp/n9XeHQOHBGOfywnirjk3OQAj1KpFBSpwkH39wuvTKaLN9tbmgnE3Q7VKioxjeO+msvNh33rY5pyTEajjqtsExxty3Fz5t+9M/lvno+lLg9PoNCKEzcgNQlDl/bbDww5ZXG7TEnbBtdSmYUow1NE0X3xb4xYGf36zpeOsNM50DhRnPg9GYAlqtVnYish2k7Jq6jzdUO/cVrjNvmoezLAMGg6GYJMkaOdnLhgcxn3m09UDbT5fruPut2+bhajUpwc8wDLPhlsL/4odtn7W8Vukaa4XctIcW4RzHAcMwFEIoFG8AsjPvHGmx1zjfLx3xuuEey+ZFOE3TkJSUtIsgiPJbBnf0HW095jiUOxMOQY554yJcrVZLpe9NSEjIiBs+5r2KA6IQl30oGIIfug7Db57rK30zXPptMBiAJMm4tCQj5B51YWfPN9A9dg7EWTGmoz84AbOzsxHhBEEAQrE7KdlpNBpAGGObZ7zffqLrS+7X3lMwh68Lx3OWZx6Pj2TDcdwkwzBl82FK16UwE7S7h9ttDb9/BJ4Jt/RfTC05cKkiFEUBy7JVFEWVIYQml9RIFEXbEO+yN7pquTb3yf8VrtPp5rMlSbJqQXhFg6QqBINB+9me47bGPw7DJD8u1SZiIPFk/s8KVqlUqvlsbxaKOh0iFm2u4Tb79x1fcEO8C8Lh8IoAYsEZhpnU6XRLso0LvjALnomr9uYrVbZzA9/BTGhpANHg0s7r9frF3kbrX+y9AIDzA/X4iOOALHhqampM7ZgGPp/vrsoLb7su9f8oq+xGozGLoij3v01tTPiQ99Jue+PL7/A+Xhacpumy5OTkD/4T/GRntaOu/d18AhFLdIKz1yAvfTtsSd8JJKGOxDiRlpb2oGK4gAVTRdPu0c6+s0CqbgCkgUpNzIKt64vBSt8NwemVmyBBrVZrMkJoQtHAdQ92PHOw5cVPwuHAor90L2+/78ne/FUl+xITE7/CGD/N8/xen89nXQ7RaDSPm0ymOkXw013HauvbDxaJYghUKhWk6jJg01pbxb2ZD+ylET24IIoxTg8EAvv9fr8tFLrxLaHRaA6ZTKYXZMMxxgmfN+/p+aX/tIVQkbBmVcHQljVPvZJtXlcdTUwUxZ1er3f/9PS0SbJBCLktFstahFDEvkSd9hGvp+i9xmdrBf84bMx+rOLhvB2vMyhlJNaFjzG28DxfLghC0dzcnPQG28qy7KlIflHhbX315Y6+hicKrI88l5tV+HUs6PLngUBgx9TUlPRJ9bHZbN4jC355rOmI/o7sl8yJZunNouhgjI2CIJRrtdqSuOEY43yEkFMRMYITxjgPIdS6/NHfsBG1QLyoq3AAAAAASUVORK5CYII=' }" /> | |
| <img width="16" src="\${rating > 1.5 ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfCAYAAAAfrhY5AAADqElEQVRYR8WVfUzMcRzH3+eOuuNqOosopVNLpDKLkMdj1i1Km9nRg4exmWebhpGx9TBGD39gniajUWkqFblqRSd6sNx1SUp5WMgzPxV3X/u1dXVc3X1/Z/P79/t+v1+fz/f3+X1+PPzHh2cN+47mEpFNjuCcwdlY0VJ6taj+9MpYeTrnDM7Gy6pEUtGUjag5exEwYTmnHE4m9lUlFm4iLe01cHH0wP7gK5xyOJlK67NIjjoFDMNALB6Bo+ElnHI4ma5VpZJidZphVpf6RiHUfwt1FrWBJcYXKkhr+1MD3GPsVOxeco46i9rAEnelLyJM1xcD3NHeFYfDMqmzqA3lT2+RjMo4dP9iDHAbGxHCA3ZirjSUKo9KzNIyaxNIcV029ERvtJ9mSkMQHXSQKo9KzNLiciNI2/sGI7CO6CAZ6YyE0ByqvB7xqdI9pLVDC/JHN3+uXvb8a+cH6PXGXbM6Pp8Psa3E7LZmdWPs3LFVlsQzVHpcuYM0vrhn1mytQOrkgzC/PZg42qsPzoYWazKI8slFvP/yxlqGkZ/H42Gsgxumu4ZglocM9kKnnqaN3lFnN5Pc1FG5Tam+Du3r+/+sAO/xgZD7RSukDtPS+4eaHBCGYWIK6s8mqJpv4BvT9z3TViMebocFnhEI9o02yRl0Osuf5ZMKbRbaPmqg0+ksZg8dKoCjeAJme8qx0Gv1gAyLPo1U5VaiffXA5JSbqijAPRiLvKPgOsp90HyL4Jk1iaRYfd0iONt1oHsoFIExZrPNCtjOjuVvJE1vay2+dieJG2JDMsxmmxU0vislp0pie/7dNI9i5gHM9Vpm3bVnVCaTovo0DOEN+YvNbquBBnGGVI61QYesgx/JXUnaOprA5/ENcKHQFlKJPzzGeaGupRYvPmvR3dVlVNxI8WjEh+dxh1e3KHckKTefGC6w6/t92tpiw7y9kNrPTxGJRNvZg7vN10j2wzP4/uOTQTdMIETKmjJucOYnE5NZk5RQor4CG74QAoEAbg5TsdhHAV+XeX+Fal6pSKHmPJrf1EGn04NdqYGe4YgcZOoHrSw+L4K0djT0gP1dZVgfdMTsgKZXxZF72nz80nXBRTIJ+0PS6JdM1fOyaRdU+6qH6QWQ+6+DzDvSLLj3zssa8khBQyqYTgbJq8rp4TcfnyQvPzViU9AJi6G9cKa7M1nTdneF6nmWs0TkhtWzTC+cAYPV7bcfTRmzxI/m2zalffyyiPFxXiwydUbdlbXF9Pf/BkFmRi9vqxubAAAAAElFTkSuQmCC' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfCAYAAAAfrhY5AAAEWElEQVRYR72WfUwbdRjHn1/vSs+7Ky1XaKVrSQgvcwvZNBp5SbbMP7YFp0t8ISEp6vxjxqhgdInTJUa3GWMWg6ZuvqNxAzYwGGaULXMi4NbChsMNXWGWt1HelJdee6MtPfiZY8IGtLZ3uv3+a+95vp/n9XeHQOHBGOfywnirjk3OQAj1KpFBSpwkH39wuvTKaLN9tbmgnE3Q7VKioxjeO+msvNh33rY5pyTEajjqtsExxty3Fz5t+9M/lvno+lLg9PoNCKEzcgNQlDl/bbDww5ZXG7TEnbBtdSmYUow1NE0X3xb4xYGf36zpeOsNM50DhRnPg9GYAlqtVnYish2k7Jq6jzdUO/cVrjNvmoezLAMGg6GYJMkaOdnLhgcxn3m09UDbT5fruPut2+bhajUpwc8wDLPhlsL/4odtn7W8Vukaa4XctIcW4RzHAcMwFEIoFG8AsjPvHGmx1zjfLx3xuuEey+ZFOE3TkJSUtIsgiPJbBnf0HW095jiUOxMOQY554yJcrVZLpe9NSEjIiBs+5r2KA6IQl30oGIIfug7Db57rK30zXPptMBiAJMm4tCQj5B51YWfPN9A9dg7EWTGmoz84AbOzsxHhBEEAQrE7KdlpNBpAGGObZ7zffqLrS+7X3lMwh68Lx3OWZx6Pj2TDcdwkwzBl82FK16UwE7S7h9ttDb9/BJ4Jt/RfTC05cKkiFEUBy7JVFEWVIYQml9RIFEXbEO+yN7pquTb3yf8VrtPp5rMlSbJqQXhFg6QqBINB+9me47bGPw7DJD8u1SZiIPFk/s8KVqlUqvlsbxaKOh0iFm2u4Tb79x1fcEO8C8Lh8IoAYsEZhpnU6XRLso0LvjALnomr9uYrVbZzA9/BTGhpANHg0s7r9frF3kbrX+y9AIDzA/X4iOOALHhqampM7ZgGPp/vrsoLb7su9f8oq+xGozGLoij3v01tTPiQ99Jue+PL7/A+Xhacpumy5OTkD/4T/GRntaOu/d18AhFLdIKz1yAvfTtsSd8JJKGOxDiRlpb2oGK4gAVTRdPu0c6+s0CqbgCkgUpNzIKt64vBSt8NwemVmyBBrVZrMkJoQtHAdQ92PHOw5cVPwuHAor90L2+/78ne/FUl+xITE7/CGD/N8/xen89nXQ7RaDSPm0ymOkXw013HauvbDxaJYghUKhWk6jJg01pbxb2ZD+ylET24IIoxTg8EAvv9fr8tFLrxLaHRaA6ZTKYXZMMxxgmfN+/p+aX/tIVQkbBmVcHQljVPvZJtXlcdTUwUxZ1er3f/9PS0SbJBCLktFstahFDEvkSd9hGvp+i9xmdrBf84bMx+rOLhvB2vMyhlJNaFjzG28DxfLghC0dzcnPQG28qy7KlIflHhbX315Y6+hicKrI88l5tV+HUs6PLngUBgx9TUlPRJ9bHZbN4jC355rOmI/o7sl8yJZunNouhgjI2CIJRrtdqSuOEY43yEkFMRMYITxjgPIdS6/NHfsBG1QLyoq3AAAAAASUVORK5CYII=' }" /> | |
| <img width="16" src="\${rating > 2.5 ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfCAYAAAAfrhY5AAADqElEQVRYR8WVfUzMcRzH3+eOuuNqOosopVNLpDKLkMdj1i1Km9nRg4exmWebhpGx9TBGD39gniajUWkqFblqRSd6sNx1SUp5WMgzPxV3X/u1dXVc3X1/Z/P79/t+v1+fz/f3+X1+PPzHh2cN+47mEpFNjuCcwdlY0VJ6taj+9MpYeTrnDM7Gy6pEUtGUjag5exEwYTmnHE4m9lUlFm4iLe01cHH0wP7gK5xyOJlK67NIjjoFDMNALB6Bo+ElnHI4ma5VpZJidZphVpf6RiHUfwt1FrWBJcYXKkhr+1MD3GPsVOxeco46i9rAEnelLyJM1xcD3NHeFYfDMqmzqA3lT2+RjMo4dP9iDHAbGxHCA3ZirjSUKo9KzNIyaxNIcV029ERvtJ9mSkMQHXSQKo9KzNLiciNI2/sGI7CO6CAZ6YyE0ByqvB7xqdI9pLVDC/JHN3+uXvb8a+cH6PXGXbM6Pp8Psa3E7LZmdWPs3LFVlsQzVHpcuYM0vrhn1mytQOrkgzC/PZg42qsPzoYWazKI8slFvP/yxlqGkZ/H42Gsgxumu4ZglocM9kKnnqaN3lFnN5Pc1FG5Tam+Du3r+/+sAO/xgZD7RSukDtPS+4eaHBCGYWIK6s8mqJpv4BvT9z3TViMebocFnhEI9o02yRl0Osuf5ZMKbRbaPmqg0+ksZg8dKoCjeAJme8qx0Gv1gAyLPo1U5VaiffXA5JSbqijAPRiLvKPgOsp90HyL4Jk1iaRYfd0iONt1oHsoFIExZrPNCtjOjuVvJE1vay2+dieJG2JDMsxmmxU0vislp0pie/7dNI9i5gHM9Vpm3bVnVCaTovo0DOEN+YvNbquBBnGGVI61QYesgx/JXUnaOprA5/ENcKHQFlKJPzzGeaGupRYvPmvR3dVlVNxI8WjEh+dxh1e3KHckKTefGC6w6/t92tpiw7y9kNrPTxGJRNvZg7vN10j2wzP4/uOTQTdMIETKmjJucOYnE5NZk5RQor4CG74QAoEAbg5TsdhHAV+XeX+Fal6pSKHmPJrf1EGn04NdqYGe4YgcZOoHrSw+L4K0djT0gP1dZVgfdMTsgKZXxZF72nz80nXBRTIJ+0PS6JdM1fOyaRdU+6qH6QWQ+6+DzDvSLLj3zssa8khBQyqYTgbJq8rp4TcfnyQvPzViU9AJi6G9cKa7M1nTdneF6nmWs0TkhtWzTC+cAYPV7bcfTRmzxI/m2zalffyyiPFxXiwydUbdlbXF9Pf/BkFmRi9vqxubAAAAAElFTkSuQmCC' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfCAYAAAAfrhY5AAAEWElEQVRYR72WfUwbdRjHn1/vSs+7Ky1XaKVrSQgvcwvZNBp5SbbMP7YFp0t8ISEp6vxjxqhgdInTJUa3GWMWg6ZuvqNxAzYwGGaULXMi4NbChsMNXWGWt1HelJdee6MtPfiZY8IGtLZ3uv3+a+95vp/n9XeHQOHBGOfywnirjk3OQAj1KpFBSpwkH39wuvTKaLN9tbmgnE3Q7VKioxjeO+msvNh33rY5pyTEajjqtsExxty3Fz5t+9M/lvno+lLg9PoNCKEzcgNQlDl/bbDww5ZXG7TEnbBtdSmYUow1NE0X3xb4xYGf36zpeOsNM50DhRnPg9GYAlqtVnYish2k7Jq6jzdUO/cVrjNvmoezLAMGg6GYJMkaOdnLhgcxn3m09UDbT5fruPut2+bhajUpwc8wDLPhlsL/4odtn7W8Vukaa4XctIcW4RzHAcMwFEIoFG8AsjPvHGmx1zjfLx3xuuEey+ZFOE3TkJSUtIsgiPJbBnf0HW095jiUOxMOQY554yJcrVZLpe9NSEjIiBs+5r2KA6IQl30oGIIfug7Db57rK30zXPptMBiAJMm4tCQj5B51YWfPN9A9dg7EWTGmoz84AbOzsxHhBEEAQrE7KdlpNBpAGGObZ7zffqLrS+7X3lMwh68Lx3OWZx6Pj2TDcdwkwzBl82FK16UwE7S7h9ttDb9/BJ4Jt/RfTC05cKkiFEUBy7JVFEWVIYQml9RIFEXbEO+yN7pquTb3yf8VrtPp5rMlSbJqQXhFg6QqBINB+9me47bGPw7DJD8u1SZiIPFk/s8KVqlUqvlsbxaKOh0iFm2u4Tb79x1fcEO8C8Lh8IoAYsEZhpnU6XRLso0LvjALnomr9uYrVbZzA9/BTGhpANHg0s7r9frF3kbrX+y9AIDzA/X4iOOALHhqampM7ZgGPp/vrsoLb7su9f8oq+xGozGLoij3v01tTPiQ99Jue+PL7/A+Xhacpumy5OTkD/4T/GRntaOu/d18AhFLdIKz1yAvfTtsSd8JJKGOxDiRlpb2oGK4gAVTRdPu0c6+s0CqbgCkgUpNzIKt64vBSt8NwemVmyBBrVZrMkJoQtHAdQ92PHOw5cVPwuHAor90L2+/78ne/FUl+xITE7/CGD/N8/xen89nXQ7RaDSPm0ymOkXw013HauvbDxaJYghUKhWk6jJg01pbxb2ZD+ylET24IIoxTg8EAvv9fr8tFLrxLaHRaA6ZTKYXZMMxxgmfN+/p+aX/tIVQkbBmVcHQljVPvZJtXlcdTUwUxZ1er3f/9PS0SbJBCLktFstahFDEvkSd9hGvp+i9xmdrBf84bMx+rOLhvB2vMyhlJNaFjzG28DxfLghC0dzcnPQG28qy7KlIflHhbX315Y6+hicKrI88l5tV+HUs6PLngUBgx9TUlPRJ9bHZbN4jC355rOmI/o7sl8yJZunNouhgjI2CIJRrtdqSuOEY43yEkFMRMYITxjgPIdS6/NHfsBG1QLyoq3AAAAAASUVORK5CYII=' }" /> | |
| <img width="16" src="\${rating > 3.5 ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfCAYAAAAfrhY5AAADqElEQVRYR8WVfUzMcRzH3+eOuuNqOosopVNLpDKLkMdj1i1Km9nRg4exmWebhpGx9TBGD39gniajUWkqFblqRSd6sNx1SUp5WMgzPxV3X/u1dXVc3X1/Z/P79/t+v1+fz/f3+X1+PPzHh2cN+47mEpFNjuCcwdlY0VJ6taj+9MpYeTrnDM7Gy6pEUtGUjag5exEwYTmnHE4m9lUlFm4iLe01cHH0wP7gK5xyOJlK67NIjjoFDMNALB6Bo+ElnHI4ma5VpZJidZphVpf6RiHUfwt1FrWBJcYXKkhr+1MD3GPsVOxeco46i9rAEnelLyJM1xcD3NHeFYfDMqmzqA3lT2+RjMo4dP9iDHAbGxHCA3ZirjSUKo9KzNIyaxNIcV029ERvtJ9mSkMQHXSQKo9KzNLiciNI2/sGI7CO6CAZ6YyE0ByqvB7xqdI9pLVDC/JHN3+uXvb8a+cH6PXGXbM6Pp8Psa3E7LZmdWPs3LFVlsQzVHpcuYM0vrhn1mytQOrkgzC/PZg42qsPzoYWazKI8slFvP/yxlqGkZ/H42Gsgxumu4ZglocM9kKnnqaN3lFnN5Pc1FG5Tam+Du3r+/+sAO/xgZD7RSukDtPS+4eaHBCGYWIK6s8mqJpv4BvT9z3TViMebocFnhEI9o02yRl0Osuf5ZMKbRbaPmqg0+ksZg8dKoCjeAJme8qx0Gv1gAyLPo1U5VaiffXA5JSbqijAPRiLvKPgOsp90HyL4Jk1iaRYfd0iONt1oHsoFIExZrPNCtjOjuVvJE1vay2+dieJG2JDMsxmmxU0vislp0pie/7dNI9i5gHM9Vpm3bVnVCaTovo0DOEN+YvNbquBBnGGVI61QYesgx/JXUnaOprA5/ENcKHQFlKJPzzGeaGupRYvPmvR3dVlVNxI8WjEh+dxh1e3KHckKTefGC6w6/t92tpiw7y9kNrPTxGJRNvZg7vN10j2wzP4/uOTQTdMIETKmjJucOYnE5NZk5RQor4CG74QAoEAbg5TsdhHAV+XeX+Fal6pSKHmPJrf1EGn04NdqYGe4YgcZOoHrSw+L4K0djT0gP1dZVgfdMTsgKZXxZF72nz80nXBRTIJ+0PS6JdM1fOyaRdU+6qH6QWQ+6+DzDvSLLj3zssa8khBQyqYTgbJq8rp4TcfnyQvPzViU9AJi6G9cKa7M1nTdneF6nmWs0TkhtWzTC+cAYPV7bcfTRmzxI/m2zalffyyiPFxXiwydUbdlbXF9Pf/BkFmRi9vqxubAAAAAElFTkSuQmCC' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfCAYAAAAfrhY5AAAEWElEQVRYR72WfUwbdRjHn1/vSs+7Ky1XaKVrSQgvcwvZNBp5SbbMP7YFp0t8ISEp6vxjxqhgdInTJUa3GWMWg6ZuvqNxAzYwGGaULXMi4NbChsMNXWGWt1HelJdee6MtPfiZY8IGtLZ3uv3+a+95vp/n9XeHQOHBGOfywnirjk3OQAj1KpFBSpwkH39wuvTKaLN9tbmgnE3Q7VKioxjeO+msvNh33rY5pyTEajjqtsExxty3Fz5t+9M/lvno+lLg9PoNCKEzcgNQlDl/bbDww5ZXG7TEnbBtdSmYUow1NE0X3xb4xYGf36zpeOsNM50DhRnPg9GYAlqtVnYish2k7Jq6jzdUO/cVrjNvmoezLAMGg6GYJMkaOdnLhgcxn3m09UDbT5fruPut2+bhajUpwc8wDLPhlsL/4odtn7W8Vukaa4XctIcW4RzHAcMwFEIoFG8AsjPvHGmx1zjfLx3xuuEey+ZFOE3TkJSUtIsgiPJbBnf0HW095jiUOxMOQY554yJcrVZLpe9NSEjIiBs+5r2KA6IQl30oGIIfug7Db57rK30zXPptMBiAJMm4tCQj5B51YWfPN9A9dg7EWTGmoz84AbOzsxHhBEEAQrE7KdlpNBpAGGObZ7zffqLrS+7X3lMwh68Lx3OWZx6Pj2TDcdwkwzBl82FK16UwE7S7h9ttDb9/BJ4Jt/RfTC05cKkiFEUBy7JVFEWVIYQml9RIFEXbEO+yN7pquTb3yf8VrtPp5rMlSbJqQXhFg6QqBINB+9me47bGPw7DJD8u1SZiIPFk/s8KVqlUqvlsbxaKOh0iFm2u4Tb79x1fcEO8C8Lh8IoAYsEZhpnU6XRLso0LvjALnomr9uYrVbZzA9/BTGhpANHg0s7r9frF3kbrX+y9AIDzA/X4iOOALHhqampM7ZgGPp/vrsoLb7su9f8oq+xGozGLoij3v01tTPiQ99Jue+PL7/A+Xhacpumy5OTkD/4T/GRntaOu/d18AhFLdIKz1yAvfTtsSd8JJKGOxDiRlpb2oGK4gAVTRdPu0c6+s0CqbgCkgUpNzIKt64vBSt8NwemVmyBBrVZrMkJoQtHAdQ92PHOw5cVPwuHAor90L2+/78ne/FUl+xITE7/CGD/N8/xen89nXQ7RaDSPm0ymOkXw013HauvbDxaJYghUKhWk6jJg01pbxb2ZD+ylET24IIoxTg8EAvv9fr8tFLrxLaHRaA6ZTKYXZMMxxgmfN+/p+aX/tIVQkbBmVcHQljVPvZJtXlcdTUwUxZ1er3f/9PS0SbJBCLktFstahFDEvkSd9hGvp+i9xmdrBf84bMx+rOLhvB2vMyhlJNaFjzG28DxfLghC0dzcnPQG28qy7KlIflHhbX315Y6+hicKrI88l5tV+HUs6PLngUBgx9TUlPRJ9bHZbN4jC355rOmI/o7sl8yJZunNouhgjI2CIJRrtdqSuOEY43yEkFMRMYITxjgPIdS6/NHfsBG1QLyoq3AAAAAASUVORK5CYII=' }" /> | |
| <img width="16" src="\${rating > 4.5 ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfCAYAAAAfrhY5AAADqElEQVRYR8WVfUzMcRzH3+eOuuNqOosopVNLpDKLkMdj1i1Km9nRg4exmWebhpGx9TBGD39gniajUWkqFblqRSd6sNx1SUp5WMgzPxV3X/u1dXVc3X1/Z/P79/t+v1+fz/f3+X1+PPzHh2cN+47mEpFNjuCcwdlY0VJ6taj+9MpYeTrnDM7Gy6pEUtGUjag5exEwYTmnHE4m9lUlFm4iLe01cHH0wP7gK5xyOJlK67NIjjoFDMNALB6Bo+ElnHI4ma5VpZJidZphVpf6RiHUfwt1FrWBJcYXKkhr+1MD3GPsVOxeco46i9rAEnelLyJM1xcD3NHeFYfDMqmzqA3lT2+RjMo4dP9iDHAbGxHCA3ZirjSUKo9KzNIyaxNIcV029ERvtJ9mSkMQHXSQKo9KzNLiciNI2/sGI7CO6CAZ6YyE0ByqvB7xqdI9pLVDC/JHN3+uXvb8a+cH6PXGXbM6Pp8Psa3E7LZmdWPs3LFVlsQzVHpcuYM0vrhn1mytQOrkgzC/PZg42qsPzoYWazKI8slFvP/yxlqGkZ/H42Gsgxumu4ZglocM9kKnnqaN3lFnN5Pc1FG5Tam+Du3r+/+sAO/xgZD7RSukDtPS+4eaHBCGYWIK6s8mqJpv4BvT9z3TViMebocFnhEI9o02yRl0Osuf5ZMKbRbaPmqg0+ksZg8dKoCjeAJme8qx0Gv1gAyLPo1U5VaiffXA5JSbqijAPRiLvKPgOsp90HyL4Jk1iaRYfd0iONt1oHsoFIExZrPNCtjOjuVvJE1vay2+dieJG2JDMsxmmxU0vislp0pie/7dNI9i5gHM9Vpm3bVnVCaTovo0DOEN+YvNbquBBnGGVI61QYesgx/JXUnaOprA5/ENcKHQFlKJPzzGeaGupRYvPmvR3dVlVNxI8WjEh+dxh1e3KHckKTefGC6w6/t92tpiw7y9kNrPTxGJRNvZg7vN10j2wzP4/uOTQTdMIETKmjJucOYnE5NZk5RQor4CG74QAoEAbg5TsdhHAV+XeX+Fal6pSKHmPJrf1EGn04NdqYGe4YgcZOoHrSw+L4K0djT0gP1dZVgfdMTsgKZXxZF72nz80nXBRTIJ+0PS6JdM1fOyaRdU+6qH6QWQ+6+DzDvSLLj3zssa8khBQyqYTgbJq8rp4TcfnyQvPzViU9AJi6G9cKa7M1nTdneF6nmWs0TkhtWzTC+cAYPV7bcfTRmzxI/m2zalffyyiPFxXiwydUbdlbXF9Pf/BkFmRi9vqxubAAAAAElFTkSuQmCC' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAfCAYAAAAfrhY5AAAEWElEQVRYR72WfUwbdRjHn1/vSs+7Ky1XaKVrSQgvcwvZNBp5SbbMP7YFp0t8ISEp6vxjxqhgdInTJUa3GWMWg6ZuvqNxAzYwGGaULXMi4NbChsMNXWGWt1HelJdee6MtPfiZY8IGtLZ3uv3+a+95vp/n9XeHQOHBGOfywnirjk3OQAj1KpFBSpwkH39wuvTKaLN9tbmgnE3Q7VKioxjeO+msvNh33rY5pyTEajjqtsExxty3Fz5t+9M/lvno+lLg9PoNCKEzcgNQlDl/bbDww5ZXG7TEnbBtdSmYUow1NE0X3xb4xYGf36zpeOsNM50DhRnPg9GYAlqtVnYish2k7Jq6jzdUO/cVrjNvmoezLAMGg6GYJMkaOdnLhgcxn3m09UDbT5fruPut2+bhajUpwc8wDLPhlsL/4odtn7W8Vukaa4XctIcW4RzHAcMwFEIoFG8AsjPvHGmx1zjfLx3xuuEey+ZFOE3TkJSUtIsgiPJbBnf0HW095jiUOxMOQY554yJcrVZLpe9NSEjIiBs+5r2KA6IQl30oGIIfug7Db57rK30zXPptMBiAJMm4tCQj5B51YWfPN9A9dg7EWTGmoz84AbOzsxHhBEEAQrE7KdlpNBpAGGObZ7zffqLrS+7X3lMwh68Lx3OWZx6Pj2TDcdwkwzBl82FK16UwE7S7h9ttDb9/BJ4Jt/RfTC05cKkiFEUBy7JVFEWVIYQml9RIFEXbEO+yN7pquTb3yf8VrtPp5rMlSbJqQXhFg6QqBINB+9me47bGPw7DJD8u1SZiIPFk/s8KVqlUqvlsbxaKOh0iFm2u4Tb79x1fcEO8C8Lh8IoAYsEZhpnU6XRLso0LvjALnomr9uYrVbZzA9/BTGhpANHg0s7r9frF3kbrX+y9AIDzA/X4iOOALHhqampM7ZgGPp/vrsoLb7su9f8oq+xGozGLoij3v01tTPiQ99Jue+PL7/A+Xhacpumy5OTkD/4T/GRntaOu/d18AhFLdIKz1yAvfTtsSd8JJKGOxDiRlpb2oGK4gAVTRdPu0c6+s0CqbgCkgUpNzIKt64vBSt8NwemVmyBBrVZrMkJoQtHAdQ92PHOw5cVPwuHAor90L2+/78ne/FUl+xITE7/CGD/N8/xen89nXQ7RaDSPm0ymOkXw013HauvbDxaJYghUKhWk6jJg01pbxb2ZD+ylET24IIoxTg8EAvv9fr8tFLrxLaHRaA6ZTKYXZMMxxgmfN+/p+aX/tIVQkbBmVcHQljVPvZJtXlcdTUwUxZ1er3f/9PS0SbJBCLktFstahFDEvkSd9hGvp+i9xmdrBf84bMx+rOLhvB2vMyhlJNaFjzG28DxfLghC0dzcnPQG28qy7KlIflHhbX315Y6+hicKrI88l5tV+HUs6PLngUBgx9TUlPRJ9bHZbN4jC355rOmI/o7sl8yJZunNouhgjI2CIJRrtdqSuOEY43yEkFMRMYITxjgPIdS6/NHfsBG1QLyoq3AAAAAASUVORK5CYII=' }" /> | |
| </div> | |
| </div> | |
| <div class="app-banner-close"> | |
| <a href="javascript:">✕</a> | |
| </div> | |
| </div> | |
| <div class="app-banner-row"> | |
| <div class="app-banner-spacer"> </div> | |
| <div class="app-banner-play"> | |
| <img height="24" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJkAAAAkCAYAAACaCJgRAAALi0lEQVR4Xu3ciXMUVRcF8BdFERXFBdxQNlFcEAERF1BBNhdU/lDjrqggRNBCg0spiID7CoKKAipLjPV79d2pTts9M0km9QmZW5WqZOb16/fuO/fcc2839AwODg6mlNLPP/+cLr/88tTT0+PPrnU90DEP9PT39w9eccUV6eqrr059fX3pzjvvzL93reuBTnmg55lnnhn8+++/M7iuvfba9Nprr6ULLrggLVmyJF122WWduk93nnHsgZ6nn346p0u2YMGCdN1112WgnT59Ol155ZXprrvuShdeeOE4dlF366P1wBCQmeyOO+5I119/fXr11Vcz0BiGW7x4cTrvvPNGe7/u9ePQA/8CGR/Mnz8/3XDDDUOA5vM5c+bk784555xx6KrulkfqgUqQmez2229PM2fOzEA7depUY/5zzz033XLLLWnu3LndSnSkXh9n19WCjB9uvfXWNHv27KzRTp48OcQ1EyZMSAsXLsyptWtdDzTzQFOQufCmm27KP2VGi0knTZqU9dq0adO6nu56oNIDLUEWQLv55psz0MqMFrNeeuml6e67706TJ0/uurrrgSEeaAtkIfqlz9dffz399ddflW708ACj6bHptY2F/f777+nLL79MBw8ebGhFbKpQoSGl8TPBjhw5kr766qvcHpIpOmHvvvtu5dmcf/75uTXlp1i07d+/Px0/fjxLHg35sbK2QWYB9JmCYNOmTemPP/5ouqbp06dnzdaptgdgb9++PQFZ2P+eiDUKEI/EZs2alRvL/3X79ttvU39/fz7cBx98sCPL3bhxY9NzUbTdf//9uf/Jtm7dmn755Zcsd2bMmNGRNVRNMiyQmcAh6qUBmihoZqJGmlWNjsZ+++23tGXLluTJBLvmmmty9F988cUZYAD4+eefZ2YINl22bNlobjnm1wbIPC9+6KGHOnK/AJmm+iWXXNKY89ixY2n37t0NqbN27dp00UUX/XdBZuVS06JFi9LmzZvT0aNHax0EAKJUBH333XcZbMPtsdGAL7/8cgaYFOxA6p5AYNc333wzjxmrdN0RNKSUxhJkK1euTFOmTBmy1IGBgSx1+Gjq1Klp+fLl/22QWb08jmbfeOONISksdgZMNuupgYMHEmDDgrRTu297vPXWW1l/SbuPPPJIS82FydqZu91xdam5GdjsNeavC6pWIHN9zNHOfqwnmKwKZL4X6HSb+TZs2NASZMP1UZ1Php0uixMRkkS+tzd+/fXXxlfAtHr16vTnn39mgIV2igETJ07MAG31toc0iMWY9DfaNglWlDZ++OGHDH7rNCedWcWOwKLI+Oyzz9KJEyfy4aieFUBVa9G0doiHDh1q7FnaUnVj/A8++CCLb5qxDmTmMO7AgQMJ+8Q9XdNKnLcC2U8//ZR1LeA/9dRTlSDjo48++ijf37kJbmueN29eQ1/73tpIpzJjOiu++uSTT/K5IZVRgSz00dKlS/PiDx8+nJlmzZo1iY7CQs2MLrj33nuH6Ifi+H379qVdu3blza1fv35UmSoO1SScDOgcylkM0IpVHr1JBxrjoI0HumjhEM9STrCMNKRpHbpRRcdiPGH99ddfJwUR0FWBTMXpnhGUgiBY0VzA7bDrrBXIQujTtPxeFv5l7Rt7C0YT6FItAH366acZYFizbB9++GH64osv8ls8K1asGD3IikBTLYk4kSyi2zEbefTRR/Mhli1SZSy2ar441Lp7ARThCwDMIfnxOef9+OOPaceOHfm7e+65J78MYM6XXnopt0iAyedAYzzwYWcsa6zvfG48QGHEBx54IGmrMOMxvehmdSBzL3O494033phuu+22xhox6ccff5wBXZcKzR0go4P1LcOK7OizKuFPZ/OR9SpG7Etw25t7Y3Q+E+zW+MILL+TpSZjYq79999xzz+XraOP8ImzxVZ92QFE3JpwHYNu2bRvWVHWpUFRLwzF31aS9vb21GsyhPPnkkzlizYOpMFbZaD6AxhxPPPFEwqDSKqbFymVNJNUCBBZct25dkobef//9PO6xxx7LgCwa8L344otNQRbRH8Atr/Gdd95J33//fYMdqnwRICuvN5gRSO67775Gqi8zmXHxhnRZS+omaB9hYeexc+fO9M0332R9rQgM85nvBBvfsI6AzOGsWrUqazDI5RAM0Y5hFWmgyt5+++2sDZr1kp599tlKkAGA1P344483IksU1vXtaD/sxDGc73d6s+7ZrDQOjNYusPw0CwZsSQvWMRkQAqO0WgYp3/At4c7oqaqCIkBm30UdrNVDV2HIYrO6qk+G9TCntfqdvwAfE0uBcV7W88orr+R1CMxYT6yBhHLPjoAsNJhFiUZ5Gk22AhpdQPxXOTQAp++FIYwBliorFxXGcA46F01SB2dYJ1arMylNFFs7fYmtyqmgeG1ELBDSopzerKmJGffu3VsJMun1+eefb+i5ZsFpv3xRJS9aabLyvGWQ0YlYKHwaAckX8Zm+p1TOyAZ713RXBChutEnKGnpUTGYy+Z3DVRxhNJTDpcuAr2hYCUO087ZtMc0Q2URnOxbgROWcwvnYFsjq2gGcw0mqYmmaY7Fa3TpFuz17x46jiWZpwz2rzFjXVDEZXwGZexLK0nQzqwKY8aMBmaCPlI5NNXT5jMkKKl4gLILMnqVRPUkyITJPuYgaMchslF4hCKWOsmE0zhMZtITSH7iqSt5mDn3vvfdyVdZun4xD6KUACQdIqcx6pY6yuQbzEa1SqggHOBHLqVUWelGVJlUCkCIBK1VZAKAuXXr5gOj2Uqh39crWTs9qNCADKKTAz5iyHIyKujLIrNG6FVZIQAZwnfQZADVmRCBTTdBgHLtnz55ajKhwRCbR3arHUzcJANgIjQQwgFsX6XSDtGfTGEY0Fmkd0B9++OEhesbhSe0YN0ryaC/QGcaX3ywRNK4JPRJPJdyrqEViT5Fa/V0HMoEkoMwpOxQrNteRInSuPdFIzYDcrAItXldMl+4HJKFji5oPU/MrABWZzFw0MwZzncAuFwIjApkDBjB9EhqjzizIA/VOvK5NY3mEFQ/lOVm0ByvRQwAPHEBDJ2HNMCDAbr6j76wJ62Ar5Xk8GovS3nVRTdkHwcx5nGjPIQGK6dEbDeZixgI5i2eq7msdZZBFUWNt9qiCc8AEtn3YOz2nAmbttDBGAjIiHZtbB7lDZ1mHfQkAv2P6MsiK7Zs6vTgsJtO9xkzSI+fVWeT0Tr52YwNYU0VX7I0V0wiq5xwHWTapSORGv6r4vXWi++I/AXQPbQ3tiSodV9Yd5sNYtEs0eH0WoBcY0lGzZqzrrJHWKZv0gyWbPSUZSbpU7PgXac4sHjuV7y0jua8AK4PM2GjOxjPR8vVtgyyqRrRNWFdZOxVjLTLb/MKhoW+pI6Le2jgBSFo950Pv1i/9YhfOBYC666RepbtXYoyxR5VUXSsEOK0PO9KtV111VR5LuwKgawUChgJ84CmnYxrPGjE3iWBvWK3VywXACahAUdREda61N+ysuCk+oUAg9mv97msPfG28tZbXK0sIXhmu+PZH3LctkEW1qJ0gWsvmOZ7U0U7F2CaWzrhhDotWq3svK/RPPFU44zZYs+BoZJMuCqsqawky2kVHXnXBiUXDILTPeH/lGntoQWDZ6IgX/RTCv65yO5MBF1WxlyEx3rBBhqE8hig2VjkSaumD4bYjzmRntlp7pEPj+IWQll4VCVIPo2fPpv/6wb4wdLNmuX3XMhkdoAekPI3KRmWpqx2v77Zy/Hj7HqAwflH484FDkA3OtqBUgdOBNKZOQp1VgozYU3F40E3EEoA0F9HbteYeiIfMhD+hTgifTew1kvP/F8gIV8iMpqbmH9C1qtpGcvPuNePDAz29vb3x/+DlJqL3wXR+lfUail1wjQ8gjOUue/r6+galRF1tnXC6Qlps1ZMZy0V15z67PNAzMDAwqPlXV36eXdvt7ub/4YF/AAieu/WXVsVBAAAAAElFTkSuQmCC" alt="" /> | |
| </div> | |
| <div class="app-banner-button" colspan="2"> | |
| <a href="\${url}" target="_blank">\${button}</a> | |
| </div> | |
| </div> | |
| </div>`, | |
| css: ` | |
| body { | |
| position: relative; | |
| min-height: 100vh; | |
| } | |
| .app-banner { | |
| display: table; | |
| position: fixed; | |
| z-index: 9999999; | |
| left: 0; | |
| box-sizing: border-box; | |
| width: 100%; | |
| margin: 0; | |
| text-rendering: optimizeLegibility; | |
| -webkit-font-smoothing: antialiased; | |
| -moz-osx-font-smoothing: grayscale; | |
| } | |
| .app-banner.android { | |
| bottom: -170px; | |
| background-color: #ffffff; | |
| box-shadow: rgba(0, 0, 0, 0.33) 0px 0px 14px 0px; | |
| font-family: Roboto, RobotoDraft, Helvetica, Arial, sans-serif; | |
| font-size: 16px; | |
| padding: 20px; | |
| } | |
| .app-banner.android .app-banner-row { | |
| display: table-row; | |
| } | |
| .app-banner.android .app-banner-row > div { | |
| display: table-cell; | |
| vertical-align: middle; | |
| text-align: left; | |
| padding: 0; | |
| margin: 0; | |
| } | |
| .app-banner.android .app-banner-row .app-banner-icon { | |
| text-align: center; | |
| width: 16.6666%; | |
| } | |
| .app-banner.android .app-banner-row .app-banner-icon img { | |
| max-width: 100%; | |
| } | |
| .app-banner.android .app-banner-row .app-banner-content { | |
| padding-left: 10px; | |
| } | |
| .app-banner.android .app-banner-row .app-banner-content .app-banner-title { | |
| font-weight: 600; | |
| color: #30302e; | |
| margin-bottom: 2px; | |
| } | |
| .app-banner.android .app-banner-row .app-banner-close { | |
| width: 16.6666%; | |
| text-align: right; | |
| vertical-align: top; | |
| font-size: 24px; | |
| } | |
| .app-banner.android .app-banner-row .app-banner-close a, .app-banner.android .app-banner-row .app-banner-close a:hover, .app-banner.android .app-banner-row .app-banner-close a:active, .app-banner.android .app-banner-row .app-banner-close a:visited, .app-banner.android .app-banner-row .app-banner-close a:focus { | |
| color: #666; | |
| font-weight: 600; | |
| text-decoration: none; | |
| } | |
| .app-banner.android .app-banner-row .app-banner-spacer { | |
| width: 16.6666%; | |
| } | |
| .app-banner.android .app-banner-row .app-banner-play { | |
| padding-left: 10px; | |
| } | |
| .app-banner.android .app-banner-row .app-banner-button { | |
| width: 33.3333%; | |
| text-align: right; | |
| } | |
| .app-banner.android .app-banner-row .app-banner-button a, .app-banner.android .app-banner-row .app-banner-button a:hover, .app-banner.android .app-banner-row .app-banner-button a:active, .app-banner.android .app-banner-row .app-banner-button a:visited, .app-banner.android .app-banner-row .app-banner-button a:focus { | |
| background-color: #689f38; | |
| box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 0px 0px; | |
| border-radius: 4px; | |
| color: white; | |
| cursor: pointer; | |
| font-weight: 600; | |
| height: auto; | |
| line-height: 54px; | |
| padding: 12px 16px; | |
| text-align: center; | |
| text-decoration: none; | |
| text-transform: uppercase; | |
| }` | |
| } | |
| AppBanner.prototype = { | |
| init: async function() { | |
| var OS = AppBanner.OS; | |
| // set URL | |
| this.url = this.url.replace('${store}',encodeURI(this.store)).replace('${id}',encodeURI(this.id)); | |
| if(!(this.options.ajax === false)){ | |
| // use AJAX to lookup from ID | |
| var that = this; | |
| $.ajax({ | |
| dataType: OS == 'ios' ? 'json' : '', | |
| url: OS == 'ios' ? | |
| //'http://ax.itunes.apple.com/WebObjects/MZStoreServices.woa/wa/wsLookup?country=gb&id='+this.id : | |
| 'https://itunes.apple.com/lookup?id='+this.id : | |
| //'https://play.google.com/store/apps/details?id='+this.id, | |
| //'https://cors-anywhere.herokuapp.com/https://play.google.com/store/apps/details?id='+this.id, | |
| //'https://crossorigin.me/https://play.google.com/store/apps/details?id='+this.id, | |
| //'http://www.whateverorigin.org/get?url=' + encodeURIComponent('https://play.google.com/store/apps/details?id='+this.id) + '&callback=?', | |
| 'https://api.allorigins.win/get?url=' + encodeURIComponent('https://play.google.com/store/apps/details?id='+this.id), | |
| success: function(data, textStatus, jqXHR){ | |
| if(OS == 'ios'){ | |
| if(data && data.results && data.results.length){ | |
| that.icon = that.icon || data.results[0].artworkUrl512.replace(/source\/.*/,'source/100x100-75.png'); | |
| that.rating = that.rating || data.results[0].averageUserRating; | |
| that.title = that.title || data.results[0].trackName; | |
| } | |
| } else { | |
| var $doc = $($.parseHTML(data.contents)); //$($.parseHTML(data)); | |
| that.icon = that.icon || $doc.find('img[alt="Cover art"]').attr('src') | |
| if(!that.icon) that.icon = $doc.find('[itemprop=image]').eq(0).attr('src') | |
| that.title = that.title || $doc.find('[itemprop=name]').eq(0).text() | |
| that.rating = parseFloat($doc.find('[href^="https://play.google.com/about/comment-posting-policy"]').parent().parent().find('[aria-label^=Rated]').eq(0).text()) | |
| } | |
| }, | |
| error: function(){ | |
| console.error('App banner: AJAX error',arguments); | |
| }, | |
| complete: function(){ | |
| that.init2() | |
| } | |
| }); | |
| } else { | |
| this.init2() | |
| } | |
| }, | |
| init2: async function(){ | |
| var OS = AppBanner.OS; | |
| // Check we have icon from AJAX | |
| if(!this.icon){ | |
| if (this.options.icon) { | |
| if(typeof this.options.icon == 'string') | |
| this.icon = this.options.icon; | |
| else if(typeof this.options.icon == 'object' && this.options.icon[OS]) | |
| this.icon = this.options.icon[OS] | |
| } | |
| } | |
| if(!this.icon){ | |
| // try to get the highest resolution application icon from tags in head of current page | |
| if (OS == 'ios') { | |
| var appleLinks = $('head link[rel^=apple-touch-icon]').map(function(i,link){ | |
| return {n:link, s: $(link).attr('sizes') ? parseInt($(link).attr('sizes').split('x')[0]) : -1} | |
| }).sort(function(l1, l2) { return l1.s > l2.s ? -1 : l1.s < l2.s ? 1 : 0; }); | |
| if(appleLinks.length) this.icon = appleLinks[0].n.href; | |
| } else { | |
| var androidLinks = $('head link[rel^="shortcut icon"], head link[rel^=apple-touch-icon]').map(function(i,link){ | |
| return {n:link, s: $(link).attr('sizes') ? parseInt($(link).attr('sizes').split('x')[0]) : -1} | |
| }).sort(function(l1, l2) { return l1.s > l2.s ? -1 : l1.s < l2.s ? 1 : 0; }); | |
| if(androidLinks.length) this.icon = androidLinks[0].n.href; | |
| } | |
| } | |
| if (this.options.onInit) { | |
| this.options.onInit.call(this); | |
| } | |
| var that = this; | |
| this.addToDom().then(function(){ | |
| if (that.options.showAfter == 0) { | |
| console.log("App banner: displaying banner immediately"); | |
| that.show(); | |
| } else if (that.options.showAfter > 0) { | |
| setTimeout(that.show.bind(that), that.options.showAfter * 1000); | |
| } | |
| }); | |
| }, | |
| getHtml: function(){ | |
| return AppBanner.template(this.html, this); | |
| }, | |
| isInDom: function(){ | |
| return this.$el.parents('body').length > 0 | |
| }, | |
| isShowing: function(){ | |
| return this.isInDom() && parseFloat(this.$el.css(AppBanner.OS == 'ios' ? 'top' : 'bottom')) > -80 | |
| }, | |
| addToDom: function () { | |
| var that = this; | |
| return new Promise(function (resolve, reject) { | |
| AppBanner.waitForDom().then(function(){ | |
| // add css | |
| if(!$('#app-banner-style').length) | |
| $('head').append('<style type="text/css" id="app-banner-css">'+that.css+'</style>'); | |
| if(!that.$el){ | |
| // create the element | |
| that.$el = $(that.getHtml()); | |
| } | |
| if(!that.isInDom()){ | |
| // attach all elements to the DOM | |
| that.$container.append(that.$el); | |
| var eventsNeedAdding = true, keys = Object.keys(that.$el[0]).filter(function(k){return k.substr(0,6) == 'jQuery'}) | |
| if(keys.length){ | |
| keys.forEach(function(k){ | |
| if(that.$el[0][k] && that.$el[0][k].events && Object.keys(that.$el[0][k].events).length > 0) eventsNeedAdding = false; | |
| }) | |
| } | |
| if(eventsNeedAdding) that.addEventListeners(); | |
| } else console.warn("App banner: banner already in DOM"); | |
| that.waitForIcon().then(function(){ | |
| that.isReady = true; | |
| resolve(); | |
| }) | |
| }) | |
| }) | |
| }, | |
| addEventListeners: function () { | |
| this.$el.on('touchmove', AppBanner.preventDefault); | |
| this.$el.on('click', '.app-banner-close a', this.hide.bind(this)); | |
| this.$el.find('.app-banner-icon img').on('load', this.setIconLoaded.bind(this)); | |
| }, | |
| removeEventListeners: function () { | |
| this.$el.off('touchmove', AppBanner.preventDefault); | |
| this.$el.off('click', '.app-banner-close a'); | |
| this.$el.find('.app-banner-icon img').off('load'); | |
| }, | |
| setIconLoaded: function(){ | |
| this.iconLoaded = true; | |
| }, | |
| waitForIcon: function(){ | |
| var that = this; | |
| return new Promise(function (resolve, reject) { | |
| if(that.iconLoaded || $('.app-banner-icon img')[0].complete){ | |
| that.iconLoaded = true; | |
| resolve() | |
| } else { | |
| (function pollIcon(){ | |
| if (that.iconLoaded) return resolve(); | |
| setTimeout(pollIcon, 30); | |
| })(); | |
| } | |
| }); | |
| }, | |
| waitForReady: function(){ | |
| var that = this; | |
| return new Promise(function (resolve, reject) { | |
| (function pollReady(){ | |
| if (that.isReady) return resolve(); | |
| setTimeout(pollReady, 30); | |
| })(); | |
| }); | |
| }, | |
| show: function(){ | |
| var that = this, OS = AppBanner.OS; | |
| if(this.isShowing()){ | |
| console.warn('App banner: already showing'); | |
| } else if(this.isAnimating){ | |
| console.warn('App banner: already animating'); | |
| } else if(!this.isInDom()){ | |
| this.addToDom().then(function(){ | |
| that.show(); | |
| }); | |
| } else if(!this.isReady){ | |
| console.warn('App banner: not ready, waiting before show'); | |
| this.waitForReady().then(function(){ that.show() }) | |
| } else { | |
| this.isAnimating = true; | |
| if(OS == 'ios'){ | |
| this.$el.animate({ | |
| top: '0px' | |
| }, 1000, function(){ that.isAnimating = false; console.log('App banner: showing'); }) | |
| $('body').animate({ | |
| paddingTop: '+=90px' | |
| }, 1000) | |
| } else { | |
| this.$el.animate({ | |
| bottom: '0px' | |
| }, 1000, function(){ that.isAnimating = false; console.log('App banner: showing'); }) | |
| } | |
| } | |
| }, | |
| hide: function(){ | |
| var that = this, doRemove = this.options.removeOnHide !== false; | |
| if(!this.isShowing()){ | |
| console.warn('App banner: already hidden'); | |
| return; | |
| } | |
| if(this.isAnimating){ | |
| console.warn('App banner: already animating'); | |
| return; | |
| } | |
| // fire the custom onRemove event | |
| if (this.options.onHide) this.options.onHide.call(this); | |
| this.isAnimating = true; | |
| if(AppBanner.OS == 'ios'){ | |
| this.$el.animate({ | |
| top: '-90px' | |
| }, 1000, function(){ that.isAnimating = false; if(doRemove) that.remove(); else console.log('App banner: hidden'); }) | |
| $('body').animate({ | |
| paddingTop: '-=90px' | |
| }, 1000) | |
| } else { | |
| this.$el.animate({ | |
| bottom: '-170px' | |
| }, 1000, function(){ that.isAnimating = false; if(doRemove) that.remove(); else console.log('App banner: hidden'); }) | |
| } | |
| }, | |
| remove: function () { | |
| var that = this, removeOnHide = this.options.removeOnHide !== false; | |
| if(this.isShowing()) { | |
| if(removeOnHide) this.hide() | |
| else this.hide().then(function(){ that.remove(); }) | |
| return; | |
| } | |
| if(!this.isInDom()) { | |
| console.warn('App banner: already removed'); | |
| return; | |
| } | |
| this.removeEventListeners(); | |
| this.$el.remove(); | |
| this.isReady = this.iconLoaded = false; | |
| // fire the custom onRemove event | |
| if (this.options.onRemove) this.options.onRemove.call(this); | |
| console.log('App banner: removed'); | |
| } | |
| }; | |
| // expose to the world | |
| window.AppBanner = AppBanner; | |
| })(this, this.document, this.jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment