diff --git a/jumpapp/assets/css/src/_greeting.scss b/jumpapp/assets/css/src/_greeting.scss
index 3e1ab3d..f0580b9 100644
--- a/jumpapp/assets/css/src/_greeting.scss
+++ b/jumpapp/assets/css/src/_greeting.scss
@@ -4,7 +4,6 @@
font-weight: 400;
text-transform: capitalize;
text-shadow: 1px 1px 2px #000000a0;
- margin-top: -50px;
margin-bottom: 15px;
.tagname {
diff --git a/jumpapp/assets/css/src/_header-bar.scss b/jumpapp/assets/css/src/_header-bar.scss
index 2dc15cc..d37a62a 100644
--- a/jumpapp/assets/css/src/_header-bar.scss
+++ b/jumpapp/assets/css/src/_header-bar.scss
@@ -4,7 +4,6 @@
right: 0;
left: 0;
padding: 15px 15px 0 15px;
- overflow: hidden;
text-align: right;
z-index: 100;
diff --git a/jumpapp/assets/css/src/_search.scss b/jumpapp/assets/css/src/_search.scss
new file mode 100644
index 0000000..a67cc66
--- /dev/null
+++ b/jumpapp/assets/css/src/_search.scss
@@ -0,0 +1,179 @@
+.search {
+ position:absolute;
+ top:15px;
+ height: 55px;
+ width: 55px;
+ display: block;
+ background-position: top 13px left 13px;
+ background-repeat: no-repeat;
+ background-image: url(../images/search.svg);
+ background-size: 24px;
+ background-color: #ffffff15;
+ border-radius: 50%;
+ cursor: pointer;
+ border: 2px solid #ffffff20;
+ overflow: hidden;
+ padding:0;
+ font-family: 'Quicksand', sans-serif;
+
+ &:hover {
+ background-color: #fff;
+ box-shadow: 0 1px 5px rgba(0,0,0,.3);
+ background-image: url(../images/search-dark.svg);
+ border: 2px solid #cecece;
+ transition: background-color, background-image .1s;
+ }
+
+ &.open {
+ @extend :hover;
+ width: calc(100% - 30px);
+ max-width: 450px;
+ cursor: auto;
+ border-radius: 5px;
+ border: .2em solid #cecece;
+ box-shadow: 0 1px 5px rgba(0,0,0,.3);
+
+ .close {
+ position: absolute;
+ top: 0;
+ right: 0;
+ height: 48px;
+ width: 48px;
+ display: inline-block;
+ background-position: top 50% left 50%;
+ background-repeat: no-repeat;
+ background-image: url(../images/close-dark.svg);
+ background-size: 30px;
+ cursor: pointer;
+ border: 5px solid #fff;
+ border-radius: 50%;
+ z-index: 1;
+
+ &:hover {
+ background-color: #f3f3f3;
+ }
+ }
+
+ .search-form {
+ display: inline-block;
+ }
+ }
+
+ &.suggestions {
+ height: auto;
+
+ .suggestion-list {
+ display: block;
+ padding-top: 20px;
+ margin-top: 25px;
+ padding-bottom:10px;
+ border-top: 1px solid #ddd;
+ text-align: left;
+ color: #202124;
+ font-size: 15px;
+
+ .selected {
+ border: 1px solid red;
+ }
+
+ ul {
+ padding: 10px 0 0 0;
+ margin: 0;
+ list-style-type: none;
+
+ li a {
+ display: block;
+ padding: 5px 0 5px 15px;
+ text-decoration: none;
+ color: inherit;
+
+ &:focus,
+ &:hover {
+ outline: none;
+ background-color: #eee;
+ }
+ }
+ }
+
+ .searchproviders {
+ margin-bottom: 20px;
+
+ li a {
+ display: block;
+ background-position: top 50% left 15px;
+ background-repeat: no-repeat;
+ background-image: url(../images/search-dark.svg);
+ background-size: 17px;
+ padding-left: 50px;
+
+ span {
+ color: #999;
+ }
+ }
+ }
+
+ .suggestiontitle {
+ margin: 0 0 0 15px;
+ display: block;
+ color: #888;
+ font-size: 15px;
+ text-transform: uppercase;
+ }
+
+ ul.suggestions {
+ padding-bottom: 15px;
+ }
+
+ .icon {
+ width: 20px;
+ vertical-align: middle;
+ margin-right: 15px;
+ }
+
+ .name {
+ vertical-align: middle;
+ }
+ }
+ }
+
+ .search-form {
+ display: none;
+ position: relative;
+ top: 14px;
+ overflow: hidden;
+ width: calc(100% - 35px);
+ text-align: left;
+ color: #202124;
+ padding-right: 48px;
+
+ input {
+ display: block;
+ width: 100%;
+ background: transparent;
+ border: none;
+ color: #202124;
+ font-size: 17px;
+ font-family: 'Quicksand', sans-serif;
+ padding: 0 0 0 15px;
+ margin: 0;
+ outline: none;
+ position: relative;
+ }
+
+ // Remove the 'x' icon from Internet Explorer
+ input[type=search]::-ms-clear,
+ input[type=search]::-ms-reveal {
+ display: none;
+ width: 0;
+ height: 0;
+ }
+
+ // Remove the 'x' icon from chrome
+ input[type="search"]::-webkit-search-decoration,
+ input[type="search"]::-webkit-search-cancel-button,
+ input[type="search"]::-webkit-search-results-button,
+ input[type="search"]::-webkit-search-results-decoration {
+ display: none;
+ }
+ }
+}
diff --git a/jumpapp/assets/css/src/index.scss b/jumpapp/assets/css/src/index.scss
index 7f48ddd..06f792b 100644
--- a/jumpapp/assets/css/src/index.scss
+++ b/jumpapp/assets/css/src/index.scss
@@ -11,6 +11,7 @@
@use 'footer-bar';
@use 'greeting';
@use 'header-bar';
+@use 'search';
@use 'sites';
@use 'tags';
@@ -71,5 +72,5 @@ body {
justify-content:center;
width: 100%;
max-width: 650px;
- margin:0 auto;
+ margin:-50px auto 0 auto;
}
diff --git a/jumpapp/assets/css/styles.2596a987cbdacf25a231.min.css b/jumpapp/assets/css/styles.2596a987cbdacf25a231.min.css
new file mode 100644
index 0000000..3f51f06
--- /dev/null
+++ b/jumpapp/assets/css/styles.2596a987cbdacf25a231.min.css
@@ -0,0 +1 @@
+.time-weather{bottom:10px;display:block;font-family:Quicksand,sans-serif;font-weight:400;position:absolute;right:15px;text-shadow:1px 1px 2px rgba(0,0,0,.627);z-index:100}.time-weather .time{font-size:2.4em;vertical-align:middle}.time-weather .time span{font-size:.5em;padding-left:5px}.time-weather .weather{color:inherit;text-decoration:none}.time-weather .weather .weather-icon{display:inline-block;font-size:1.9em;height:48px;line-height:48px!important;vertical-align:middle}.time-weather .weather .weather-icon:before{position:relative}.time-weather .weather .weather-info{display:inline-flex;flex-direction:column;font-size:14px;font-weight:600;line-height:normal;margin-right:10px;text-align:right;text-shadow:1px 1px 1px rgba(0,0,0,.627);vertical-align:middle}.widget{display:inline-block;height:58px;padding:5px 10px;user-select:none;z-index:1000}.widget.clickable{border-radius:6px;cursor:pointer}.widget.clickable:hover{background-color:hsla(0,0%,100%,.082);transition:background-color .1s}.useclientlocation{background-image:url(../images/map-pin.svg);background-position:top 50% left 5px;background-repeat:no-repeat;background-size:37px;bottom:10px;display:none;font-size:14px;left:15px;line-height:58px;padding:0 10px 0 45px;position:absolute;text-shadow:1px 1px 1px rgba(0,0,0,.627)}.greeting{font-family:Quicksand,sans-serif;font-size:2.3em;font-weight:400;margin-bottom:15px;text-shadow:1px 1px 2px rgba(0,0,0,.627);text-transform:capitalize}.greeting .tagname{text-transform:lowercase}.greeting .tagname .greeting .tagname span{margin-right:5px;opacity:.5}.header-bar{left:0;padding:15px 15px 0;position:absolute;right:0;text-align:right;top:0;z-index:100}.header-bar .show-tags{background-color:hsla(0,0%,100%,.082);background-image:url(../images/tags.svg);background-position:top 50% left 50%;background-repeat:no-repeat;background-size:35px;border:2px solid hsla(0,0%,100%,.125);border-radius:50%;cursor:pointer;display:inline-block;height:55px;width:55px}.header-bar .show-tags:hover{background-color:#fff;background-image:url(../images/tags-dark.svg);border:2px solid #cecece;box-shadow:0 1px 5px rgba(0,0,0,.3);transition:background-color,background-image .1s}.search{background-color:hsla(0,0%,100%,.082);background-image:url(../images/search.svg);background-position:top 13px left 13px;background-repeat:no-repeat;background-size:24px;border:2px solid hsla(0,0%,100%,.125);border-radius:50%;cursor:pointer;display:block;font-family:Quicksand,sans-serif;height:55px;overflow:hidden;padding:0;position:absolute;top:15px;width:55px}.search.open,.search:hover{background-color:#fff;background-image:url(../images/search-dark.svg);border:2px solid #cecece;box-shadow:0 1px 5px rgba(0,0,0,.3);transition:background-color,background-image .1s}.search.open{border:.2em solid #cecece;border-radius:5px;box-shadow:0 1px 5px rgba(0,0,0,.3);cursor:auto;max-width:450px;width:calc(100% - 30px)}.search.open .close{background-image:url(../images/close-dark.svg);background-position:top 50% left 50%;background-repeat:no-repeat;background-size:30px;border:5px solid #fff;border-radius:50%;cursor:pointer;display:inline-block;height:48px;position:absolute;right:0;top:0;width:48px;z-index:1}.search.open .close.search.open,.search.open .close:hover{background-color:#f3f3f3}.search.open .search-form{display:inline-block}.search.suggestions{height:auto}.search.suggestions .suggestion-list{border-top:1px solid #ddd;color:#202124;display:block;font-size:15px;margin-top:25px;padding-bottom:10px;padding-top:20px;text-align:left}.search.suggestions .suggestion-list .selected{border:1px solid red}.search.suggestions .suggestion-list ul{list-style-type:none;margin:0;padding:10px 0 0}.search.suggestions .suggestion-list ul li a{color:inherit;display:block;padding:5px 0 5px 15px;text-decoration:none}.search.suggestions .suggestion-list ul li a.search.open,.search.suggestions .suggestion-list ul li a:focus,.search.suggestions .suggestion-list ul li a:hover{background-color:#eee;outline:none}.search.suggestions .suggestion-list .searchproviders{margin-bottom:20px}.search.suggestions .suggestion-list .searchproviders li a{background-image:url(../images/search-dark.svg);background-position:top 50% left 15px;background-repeat:no-repeat;background-size:17px;display:block;padding-left:50px}.search.suggestions .suggestion-list .searchproviders li a span{color:#999}.search.suggestions .suggestion-list .suggestiontitle{color:#888;display:block;font-size:15px;margin:0 0 0 15px;text-transform:uppercase}.search.suggestions .suggestion-list ul.suggestions{padding-bottom:15px}.search.suggestions .suggestion-list .icon{margin-right:15px;vertical-align:middle;width:20px}.search.suggestions .suggestion-list .name{vertical-align:middle}.search .search-form{color:#202124;display:none;overflow:hidden;padding-right:48px;position:relative;text-align:left;top:14px;width:calc(100% - 35px)}.search .search-form input{background:transparent;border:none;color:#202124;display:block;font-family:Quicksand,sans-serif;font-size:17px;margin:0;outline:none;padding:0 0 0 15px;position:relative;width:100%}.search .search-form input[type=search]::-ms-clear,.search .search-form input[type=search]::-ms-reveal{display:none;height:0;width:0}.search .search-form input[type=search]::-webkit-search-cancel-button,.search .search-form input[type=search]::-webkit-search-decoration,.search .search-form input[type=search]::-webkit-search-results-button,.search .search-form input[type=search]::-webkit-search-results-decoration{display:none}.sites,.sites li{font-size:14px;list-style-type:none;margin:0;padding:0;user-select:none}.sites li,.sites li a{display:inline-block}.sites li a{border-radius:6px;color:inherit;padding:13px;text-decoration:none}.sites li a:hover{background-color:hsla(0,0%,100%,.082);transition:background-color .1s}.sites .icon{background-color:#fff;background-image:url(../images/loading.svg);background-position:50%;background-repeat:no-repeat;background-size:20px;border:.2em solid #fff;border-radius:6px;box-shadow:0 1px 5px rgba(0,0,0,.3);display:block;height:80px;margin-bottom:8px;padding:15px;width:80px}.sites .icon img{background:#fff;width:100%}.sites .name{word-wrap:break-word;display:block;max-height:3.3em;overflow:hidden;text-overflow:ellipsis;text-shadow:1px 1px 1px rgba(0,0,0,.627);white-space:nowrap;width:80px}.sites.alternate{margin-top:20px;width:100%}.sites.alternate li{float:left;margin-bottom:10px;padding:0 5px;text-align:left;width:50%}.sites.alternate li a{background-color:hsla(0,0%,100%,.8);box-shadow:0 1px 5px rgba(0,0,0,.3);overflow:hidden;padding:8px;position:relative;transition:background-color .1s,box-shadow .1s;width:100%}.sites.alternate li a:hover{background-color:#fff;box-shadow:0 1px 5px rgba(0,0,0,.6)}.sites.alternate .icon{border:none;border-radius:6px;box-shadow:none;display:inline-block;height:35px;margin:0 8px 0 0;overflow:hidden;padding:0;vertical-align:middle;width:35px}.sites.alternate .name{color:#202124;display:inline-block;height:14px;line-height:14px;margin-top:-7px;position:absolute;text-shadow:none;top:50%;vertical-align:middle;width:auto}@media(max-width:500px){.sites.alternate li{width:100%}}.tags{background-color:#fff;border:.2em solid #cecece;border-radius:6px;box-shadow:0 1px 5px rgba(0,0,0,.3);color:#202124;display:none;font-family:Quicksand,sans-serif;font-weight:400;min-width:250px;padding:15px;position:fixed;right:15px;text-align:left;top:15px;z-index:100}.tags:target{display:block}.tags .header{border-bottom:1px solid #ddd;display:block;font-size:20px;height:35px;line-height:18px;margin-bottom:20px}.tags .header .close{background-image:url(../images/close-dark.svg);background-position:top 50% left 50%;background-repeat:no-repeat;background-size:30px;border:5px solid #fff;border-radius:50%;cursor:pointer;display:inline-block;height:48px;position:absolute;right:0;top:0;width:48px}.tags .header .close:hover{background-color:#f3f3f3}.tags ul{list-style-position:inside;margin:0;padding:0}.tags ul li{margin-bottom:3px;text-transform:lowercase}.tags ul li::marker{color:#bbb;content:"#"}.tags ul li a{border-radius:4px;color:inherit;display:inline-block;margin-left:1px;padding:3px 5px;text-decoration:dotted}.tags ul li a:hover{background-color:#f3f3f3;transition:background-color .1s}@font-face{font-family:Quicksand;font-style:normal;font-weight:400;src:local(""),url(../font/quicksand-v28-latin-regular.woff2) format("woff2"),url(../font/quicksand-v28-latin-regular.woff) format("woff")}*{box-sizing:border-box}body{background:#000;color:#fff;font-family:sans-serif;margin:0;padding:0;text-align:center}.fixed{bottom:0;left:0;position:fixed;right:0;top:0}.hidden{opacity:0}.enable{display:block!important}.background{background-position:50%;background-repeat:no-repeat;background-size:cover;filter:brightness(.85) blur(10px);transform:scale(1.07);z-index:1}.content{display:flex;flex-direction:column;justify-content:center;margin:-50px auto 0;max-width:650px;width:100%;z-index:100}
\ No newline at end of file
diff --git a/jumpapp/assets/css/styles.d5cd9fa42e523236402f.min.css b/jumpapp/assets/css/styles.d5cd9fa42e523236402f.min.css
deleted file mode 100644
index e8b1b4f..0000000
--- a/jumpapp/assets/css/styles.d5cd9fa42e523236402f.min.css
+++ /dev/null
@@ -1 +0,0 @@
-.time-weather{bottom:10px;display:block;font-family:Quicksand,sans-serif;font-weight:400;position:absolute;right:15px;text-shadow:1px 1px 2px rgba(0,0,0,.627);z-index:100}.time-weather .time{font-size:2.4em;vertical-align:middle}.time-weather .time span{font-size:.5em;padding-left:5px}.time-weather .weather{color:inherit;text-decoration:none}.time-weather .weather .weather-icon{display:inline-block;font-size:1.9em;height:48px;line-height:48px!important;vertical-align:middle}.time-weather .weather .weather-icon:before{position:relative}.time-weather .weather .weather-info{display:inline-flex;flex-direction:column;font-size:14px;font-weight:600;line-height:normal;margin-right:10px;text-align:right;text-shadow:1px 1px 1px rgba(0,0,0,.627);vertical-align:middle}.widget{display:inline-block;height:58px;padding:5px 10px;user-select:none;z-index:1000}.widget .clickable{border-radius:6px;cursor:pointer}.widget .clickable:hover{background-color:hsla(0,0%,100%,.082);transition:background-color .1s}.useclientlocation{background-image:url(../images/map-pin.svg);background-position:top 50% left 5px;background-repeat:no-repeat;background-size:37px;bottom:10px;display:none;font-size:14px;left:15px;line-height:58px;padding:0 10px 0 45px;position:absolute;text-shadow:1px 1px 1px rgba(0,0,0,.627)}.greeting{font-family:Quicksand,sans-serif;font-size:2.3em;font-weight:400;margin-bottom:15px;margin-top:-50px;text-shadow:1px 1px 2px rgba(0,0,0,.627);text-transform:capitalize}.greeting .tagname{text-transform:lowercase}.greeting .tagname .greeting .tagname span{margin-right:5px;opacity:.5}.header-bar{left:0;overflow:hidden;padding:15px 15px 0;position:absolute;right:0;text-align:right;top:0;z-index:100}.header-bar .show-tags{background-color:hsla(0,0%,100%,.082);background-image:url(../images/tags.svg);background-position:top 50% left 50%;background-repeat:no-repeat;background-size:35px;border:2px solid hsla(0,0%,100%,.125);border-radius:50%;cursor:pointer;display:inline-block;height:55px;width:55px}.header-bar .show-tags:hover{background-color:#fff;background-image:url(../images/tags-dark.svg);border:2px solid #cecece;box-shadow:0 1px 5px rgba(0,0,0,.3);transition:background-color,background-image .1s}.sites,.sites li{font-size:14px;list-style-type:none;margin:0;padding:0;user-select:none}.sites li,.sites li a{display:inline-block}.sites li a{border-radius:6px;color:inherit;padding:13px;text-decoration:none}.sites li a:hover{background-color:hsla(0,0%,100%,.082);transition:background-color .1s}.sites .icon{background-color:#fff;background-image:url(../images/loading.svg);background-position:50%;background-repeat:no-repeat;background-size:20px;border:.2em solid #fff;border-radius:6px;box-shadow:0 1px 5px rgba(0,0,0,.3);display:block;height:80px;margin-bottom:8px;padding:15px;width:80px}.sites .icon img{background:#fff;width:100%}.sites .name{word-wrap:break-word;display:block;max-height:3.3em;overflow:hidden;text-overflow:ellipsis;text-shadow:1px 1px 1px rgba(0,0,0,.627);white-space:nowrap;width:80px}.sites.alternate{margin-top:20px;width:100%}.sites.alternate li{float:left;margin-bottom:10px;padding:0 5px;text-align:left;width:50%}.sites.alternate li a{background-color:hsla(0,0%,100%,.8);box-shadow:0 1px 5px rgba(0,0,0,.3);overflow:hidden;padding:8px;position:relative;transition:background-color .1s,box-shadow .1s;width:100%}.sites.alternate li a:hover{background-color:#fff;box-shadow:0 1px 5px rgba(0,0,0,.6)}.sites.alternate .icon{border:none;border-radius:6px;box-shadow:none;display:inline-block;height:35px;margin:0 8px 0 0;overflow:hidden;padding:0;vertical-align:middle;width:35px}.sites.alternate .name{color:#202124;display:inline-block;height:14px;line-height:14px;margin-top:-7px;position:absolute;text-shadow:none;top:50%;vertical-align:middle;width:auto}@media(max-width:500px){.sites.alternate li{width:100%}}.tags{background-color:#fff;border:.2em solid #cecece;border-radius:6px;box-shadow:0 1px 5px rgba(0,0,0,.3);color:#202124;display:none;font-family:Quicksand,sans-serif;font-weight:400;min-width:250px;padding:15px;position:fixed;right:15px;text-align:left;top:15px;z-index:100}.tags:target{display:block}.tags .header{border-bottom:1px solid #ddd;display:block;font-size:20px;height:35px;line-height:18px;margin-bottom:20px}.tags .header .close{background-image:url(../images/close-dark.svg);background-position:top 50% left 50%;background-repeat:no-repeat;background-size:30px;border:5px solid #fff;border-radius:50%;cursor:pointer;display:inline-block;height:48px;position:absolute;right:0;top:0;width:48px}.tags .header .close:hover{background-color:#f3f3f3}.tags ul{list-style-position:inside;margin:0;padding:0}.tags ul li{margin-bottom:3px;text-transform:lowercase}.tags ul li::marker{color:#bbb;content:"#"}.tags ul li a{border-radius:4px;color:inherit;display:inline-block;margin-left:1px;padding:3px 5px;text-decoration:dotted}.tags ul li a:hover{background-color:#f3f3f3;transition:background-color .1s}@font-face{font-family:Quicksand;font-style:normal;font-weight:400;src:local(""),url(../font/quicksand-v28-latin-regular.woff2) format("woff2"),url(../font/quicksand-v28-latin-regular.woff) format("woff")}*{box-sizing:border-box}body{background:#000;color:#fff;font-family:sans-serif;margin:0;padding:0;text-align:center}.fixed{bottom:0;left:0;position:fixed;right:0;top:0}.hidden{opacity:0}.enable{display:block!important}.background{background-position:50%;background-repeat:no-repeat;background-size:cover;filter:brightness(.85) blur(10px);transform:scale(1.07);z-index:1}.content{display:flex;flex-direction:column;justify-content:center;margin:0 auto;max-width:650px;width:100%;z-index:100}
\ No newline at end of file
diff --git a/jumpapp/assets/images/search-dark.svg b/jumpapp/assets/images/search-dark.svg
new file mode 100644
index 0000000..1a984c2
--- /dev/null
+++ b/jumpapp/assets/images/search-dark.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/jumpapp/assets/images/search.svg b/jumpapp/assets/images/search.svg
new file mode 100644
index 0000000..6aaaf6a
--- /dev/null
+++ b/jumpapp/assets/images/search.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/jumpapp/assets/js/index.1fd54d01d088096b9a90.min.js b/jumpapp/assets/js/index.1fd54d01d088096b9a90.min.js
new file mode 100644
index 0000000..428a9f2
--- /dev/null
+++ b/jumpapp/assets/js/index.1fd54d01d088096b9a90.min.js
@@ -0,0 +1 @@
+(()=>{"use strict";var e={729:e=>{var t=Object.prototype.hasOwnProperty,s="~";function n(){}function i(e,t,s){this.fn=e,this.context=t,this.once=s||!1}function r(e,t,n,r,o){if("function"!=typeof n)throw new TypeError("The listener must be a function");var c=new i(n,r||e,o),h=s?s+t:t;return e._events[h]?e._events[h].fn?e._events[h]=[e._events[h],c]:e._events[h].push(c):(e._events[h]=c,e._eventsCount++),e}function o(e,t){0==--e._eventsCount?e._events=new n:delete e._events[t]}function c(){this._events=new n,this._eventsCount=0}Object.create&&(n.prototype=Object.create(null),(new n).__proto__||(s=!1)),c.prototype.eventNames=function(){var e,n,i=[];if(0===this._eventsCount)return i;for(n in e=this._events)t.call(e,n)&&i.push(s?n.slice(1):n);return Object.getOwnPropertySymbols?i.concat(Object.getOwnPropertySymbols(e)):i},c.prototype.listeners=function(e){var t=s?s+e:e,n=this._events[t];if(!n)return[];if(n.fn)return[n.fn];for(var i=0,r=n.length,o=new Array(r);i{var t=e&&e.__esModule?()=>e.default:()=>e;return s.d(t,{a:t}),t},s.d=(e,t)=>{for(var n in t)s.o(t,n)&&!s.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},s.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{class e{constructor(e,t=!1,s=!1){this.set_utc_shift(),this.contentintervalid=null,this.eventemitter=e,this.ampm=t,this.forcelocaltime=s}set_utc_shift(e=0){this.utcshift=e,this.shiftedtimestamp=(new Date).getTime()+this.utcshift,this.shifteddate=new Date(this.shiftedtimestamp)}get_formatted_time(){let e=this.shifteddate.getUTCHours(),t=String(this.shifteddate.getUTCMinutes()).padStart(2,"0");if(this.forcelocaltime&&(e=(new Date).getHours(),t=String((new Date).getMinutes()).padStart(2,"0")),!this.ampm)return String(e).padStart(2,"0")+":"+t;const s=e<=12?"AM":"PM";return e=(e+11)%12+1,e+":"+t+""+s+""}get_hour(){return this.forcelocaltime?(new Date).getHours():this.shifteddate.getUTCHours()}update_time(){this.set_utc_shift(this.utcshift),this.eventemitter.emit("clock-updated",{formatted_time:this.get_formatted_time(),hour:this.get_hour(),utcshift:this.utcshift})}run(e){this.contentintervalid&&clearInterval(this.contentintervalid),this.update_time(),this.contentintervalid=setInterval((()=>{this.update_time()}),e)}}var t=s(729),n=s.n(t);function i(e){return Array.isArray?Array.isArray(e):"[object Array]"===u(e)}function r(e){return"string"==typeof e}function o(e){return"number"==typeof e}function c(e){return!0===e||!1===e||function(e){return h(e)&&null!==e}(e)&&"[object Boolean]"==u(e)}function h(e){return"object"==typeof e}function a(e){return null!=e}function l(e){return!e.trim().length}function u(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":Object.prototype.toString.call(e)}const d=Object.prototype.hasOwnProperty;class g{constructor(e){this._keys=[],this._keyMap={};let t=0;e.forEach((e=>{let s=f(e);t+=s.weight,this._keys.push(s),this._keyMap[s.id]=s,t+=s.weight})),this._keys.forEach((e=>{e.weight/=t}))}get(e){return this._keyMap[e]}keys(){return this._keys}toJSON(){return JSON.stringify(this._keys)}}function f(e){let t=null,s=null,n=null,o=1,c=null;if(r(e)||i(e))n=e,t=m(e),s=p(e);else{if(!d.call(e,"name"))throw new Error((e=>`Missing ${e} property in key`)("name"));const i=e.name;if(n=i,d.call(e,"weight")&&(o=e.weight,o<=0))throw new Error((e=>`Property 'weight' in key '${e}' must be a positive integer`)(i));t=m(i),s=p(i),c=e.getFn}return{path:t,id:s,weight:o,src:n,getFn:c}}function m(e){return i(e)?e:e.split(".")}function p(e){return i(e)?e.join("."):e}var v={isCaseSensitive:!1,includeScore:!1,keys:[],shouldSort:!0,sortFn:(e,t)=>e.score===t.score?e.idx{if(a(e))if(t[l]){const u=e[t[l]];if(!a(u))return;if(l===t.length-1&&(r(u)||o(u)||c(u)))s.push(function(e){return null==e?"":function(e){if("string"==typeof e)return e;let t=e+"";return"0"==t&&1/e==-1/0?"-0":t}(e)}(u));else if(i(u)){n=!0;for(let e=0,s=u.length;e{this._keysMap[e.id]=t}))}create(){!this.isCreated&&this.docs.length&&(this.isCreated=!0,r(this.docs[0])?this.docs.forEach(((e,t)=>{this._addString(e,t)})):this.docs.forEach(((e,t)=>{this._addObject(e,t)})),this.norm.clear())}add(e){const t=this.size();r(e)?this._addString(e,t):this._addObject(e,t)}removeAt(e){this.records.splice(e,1);for(let t=e,s=this.size();t{let o=t.getFn?t.getFn(e):this.getFn(e,t.path);if(a(o))if(i(o)){let e=[];const t=[{nestedArrIndex:-1,value:o}];for(;t.length;){const{nestedArrIndex:s,value:n}=t.pop();if(a(n))if(r(n)&&!l(n)){let t={v:n,i:s,n:this.norm.get(n)};e.push(t)}else i(n)&&n.forEach(((e,s)=>{t.push({nestedArrIndex:s,value:e})}))}s.$[n]=e}else if(r(o)&&!l(o)){let e={v:o,n:this.norm.get(o)};s.$[n]=e}})),this.records.push(s)}toJSON(){return{keys:this.keys,records:this.records}}}function w(e,t,{getFn:s=v.getFn,fieldNormWeight:n=v.fieldNormWeight}={}){const i=new _({getFn:s,fieldNormWeight:n});return i.setKeys(e.map(f)),i.setSources(t),i.create(),i}function M(e,{errors:t=0,currentLocation:s=0,expectedLocation:n=0,distance:i=v.distance,ignoreLocation:r=v.ignoreLocation}={}){const o=t/e.length;if(r)return o;const c=Math.abs(n-s);return i?o+c/i:c?1:o}const x=32;function L(e,t,s,{location:n=v.location,distance:i=v.distance,threshold:r=v.threshold,findAllMatches:o=v.findAllMatches,minMatchCharLength:c=v.minMatchCharLength,includeMatches:h=v.includeMatches,ignoreLocation:a=v.ignoreLocation}={}){if(t.length>x)throw new Error(`Pattern length exceeds max of ${x}.`);const l=t.length,u=e.length,d=Math.max(0,Math.min(n,u));let g=r,f=d;const m=c>1||h,p=m?Array(u):[];let y;for(;(y=e.indexOf(t,f))>-1;){let e=M(t,{currentLocation:y,expectedLocation:d,distance:i,ignoreLocation:a});if(g=Math.min(e,g),f=y+l,m){let e=0;for(;e=h;r-=1){let o=r-1,c=s[e.charAt(o)];if(m&&(p[o]=+!!c),y[r]=(y[r+1]<<1|1)&c,n&&(y[r]|=(_[r+1]|_[r])<<1|1|_[r+1]),y[r]&b&&(w=M(t,{errors:n,currentLocation:o,expectedLocation:d,distance:i,ignoreLocation:a}),w<=g)){if(g=w,f=o,f<=d)break;h=Math.max(1,2*d-f)}}if(M(t,{errors:n+1,currentLocation:d,expectedLocation:d,distance:i,ignoreLocation:a})>g)break;_=y}const k={isMatch:f>=0,score:Math.max(.001,w)};if(m){const e=function(e=[],t=v.minMatchCharLength){let s=[],n=-1,i=-1,r=0;for(let o=e.length;r=t&&s.push([n,i]),n=-1)}return e[r-1]&&r-n>=t&&s.push([n,r-1]),s}(p,c);e.length?h&&(k.indices=e):k.isMatch=!1}return k}function b(e){let t={};for(let s=0,n=e.length;s{this.chunks.push({pattern:e,alphabet:b(e),startIndex:t})},l=this.pattern.length;if(l>x){let e=0;const t=l%x,s=l-t;for(;e{const{isMatch:f,score:m,indices:p}=L(e,t,d,{location:n+g,distance:i,threshold:r,findAllMatches:o,minMatchCharLength:c,includeMatches:s,ignoreLocation:h});f&&(u=!0),l+=m,f&&p&&(a=[...a,...p])}));let d={isMatch:u,score:u?l/this.chunks.length:1};return u&&s&&(d.indices=a),d}}class S{constructor(e){this.pattern=e}static isMultiMatch(e){return C(e,this.multiRegex)}static isSingleMatch(e){return C(e,this.singleRegex)}search(){}}function C(e,t){const s=e.match(t);return s?s[1]:null}class E extends S{constructor(e,{location:t=v.location,threshold:s=v.threshold,distance:n=v.distance,includeMatches:i=v.includeMatches,findAllMatches:r=v.findAllMatches,minMatchCharLength:o=v.minMatchCharLength,isCaseSensitive:c=v.isCaseSensitive,ignoreLocation:h=v.ignoreLocation}={}){super(e),this._bitapSearch=new k(e,{location:t,threshold:s,distance:n,includeMatches:i,findAllMatches:r,minMatchCharLength:o,isCaseSensitive:c,ignoreLocation:h})}static get type(){return"fuzzy"}static get multiRegex(){return/^"(.*)"$/}static get singleRegex(){return/^(.*)$/}search(e){return this._bitapSearch.searchIn(e)}}class A extends S{constructor(e){super(e)}static get type(){return"include"}static get multiRegex(){return/^'"(.*)"$/}static get singleRegex(){return/^'(.*)$/}search(e){let t,s=0;const n=[],i=this.pattern.length;for(;(t=e.indexOf(this.pattern,s))>-1;)s=t+i,n.push([t,s-1]);const r=!!n.length;return{isMatch:r,score:r?0:1,indices:n}}}const I=[class extends S{constructor(e){super(e)}static get type(){return"exact"}static get multiRegex(){return/^="(.*)"$/}static get singleRegex(){return/^=(.*)$/}search(e){const t=e===this.pattern;return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}},A,class extends S{constructor(e){super(e)}static get type(){return"prefix-exact"}static get multiRegex(){return/^\^"(.*)"$/}static get singleRegex(){return/^\^(.*)$/}search(e){const t=e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}},class extends S{constructor(e){super(e)}static get type(){return"inverse-prefix-exact"}static get multiRegex(){return/^!\^"(.*)"$/}static get singleRegex(){return/^!\^(.*)$/}search(e){const t=!e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},class extends S{constructor(e){super(e)}static get type(){return"inverse-suffix-exact"}static get multiRegex(){return/^!"(.*)"\$$/}static get singleRegex(){return/^!(.*)\$$/}search(e){const t=!e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},class extends S{constructor(e){super(e)}static get type(){return"suffix-exact"}static get multiRegex(){return/^"(.*)"\$$/}static get singleRegex(){return/^(.*)\$$/}search(e){const t=e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[e.length-this.pattern.length,e.length-1]}}},class extends S{constructor(e){super(e)}static get type(){return"inverse-exact"}static get multiRegex(){return/^!"(.*)"$/}static get singleRegex(){return/^!(.*)$/}search(e){const t=-1===e.indexOf(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},E],N=I.length,O=/ +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/;const q=new Set([E.type,A.type]);class ${constructor(e,{isCaseSensitive:t=v.isCaseSensitive,includeMatches:s=v.includeMatches,minMatchCharLength:n=v.minMatchCharLength,ignoreLocation:i=v.ignoreLocation,findAllMatches:r=v.findAllMatches,location:o=v.location,threshold:c=v.threshold,distance:h=v.distance}={}){this.query=null,this.options={isCaseSensitive:t,includeMatches:s,minMatchCharLength:n,findAllMatches:r,ignoreLocation:i,location:o,threshold:c,distance:h},this.pattern=t?e:e.toLowerCase(),this.query=function(e,t={}){return e.split("|").map((e=>{let s=e.trim().split(O).filter((e=>e&&!!e.trim())),n=[];for(let e=0,i=s.length;e!(!e[j]&&!e[P]),W=e=>({[j]:Object.keys(e).map((t=>({[t]:e[t]})))});function H(e,t,{auto:s=!0}={}){const n=e=>{let o=Object.keys(e);const c=(e=>!!e[J])(e);if(!c&&o.length>1&&!U(e))return n(W(e));if((e=>!i(e)&&h(e)&&!U(e))(e)){const n=c?e[J]:o[0],i=c?e[T]:e[n];if(!r(i))throw new Error((e=>`Invalid value for key ${e}`)(n));const h={keyId:p(n),pattern:i};return s&&(h.searcher=R(i,t)),h}let a={children:[],operator:o[0]};return o.forEach((t=>{const s=e[t];i(s)&&s.forEach((e=>{a.children.push(n(e))}))})),a};return U(e)||(e=W(e)),n(e)}function z(e,t){const s=e.matches;t.matches=[],a(s)&&s.forEach((e=>{if(!a(e.indices)||!e.indices.length)return;const{indices:s,value:n}=e;let i={indices:s,value:n};e.key&&(i.key=e.key.src),e.idx>-1&&(i.refIndex=e.idx),t.matches.push(i)}))}function D(e,t){t.score=e.score}class K{constructor(e,t={},s){this.options={...v,...t},this.options.useExtendedSearch,this._keyStore=new g(this.options.keys),this.setCollection(e,s)}setCollection(e,t){if(this._docs=e,t&&!(t instanceof _))throw new Error("Incorrect 'index' type");this._myIndex=t||w(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}add(e){a(e)&&(this._docs.push(e),this._myIndex.add(e))}remove(e=(()=>!1)){const t=[];for(let s=0,n=this._docs.length;s{let s=1;e.matches.forEach((({key:e,norm:n,score:i})=>{const r=e?e.weight:null;s*=Math.pow(0===i&&r?Number.EPSILON:i,(r||1)*(t?1:n))})),e.score=s}))}(a,{ignoreFieldNorm:h}),i&&a.sort(c),o(t)&&t>-1&&(a=a.slice(0,t)),function(e,t,{includeMatches:s=v.includeMatches,includeScore:n=v.includeScore}={}){const i=[];return s&&i.push(z),n&&i.push(D),e.map((e=>{const{idx:s}=e,n={item:t[s],refIndex:s};return i.length&&i.forEach((t=>{t(e,n)})),n}))}(a,this._docs,{includeMatches:s,includeScore:n})}_searchStringList(e){const t=R(e,this.options),{records:s}=this._myIndex,n=[];return s.forEach((({v:e,i:s,n:i})=>{if(!a(e))return;const{isMatch:r,score:o,indices:c}=t.searchIn(e);r&&n.push({item:e,idx:s,matches:[{score:o,value:e,norm:i,indices:c}]})})),n}_searchLogical(e){const t=H(e,this.options),s=(e,t,n)=>{if(!e.children){const{keyId:s,searcher:i}=e,r=this._findMatches({key:this._keyStore.get(s),value:this._myIndex.getValueForItemAtKeyId(t,s),searcher:i});return r&&r.length?[{idx:n,item:t,matches:r}]:[]}const i=[];for(let r=0,o=e.children.length;r{if(a(e)){let o=s(t,e,n);o.length&&(i[n]||(i[n]={idx:n,item:e,matches:[]},r.push(i[n])),o.forEach((({matches:e})=>{i[n].matches.push(...e)})))}})),r}_searchObjectList(e){const t=R(e,this.options),{keys:s,records:n}=this._myIndex,i=[];return n.forEach((({$:e,i:n})=>{if(!a(e))return;let r=[];s.forEach(((s,n)=>{r.push(...this._findMatches({key:s,value:e[n],searcher:t}))})),r.length&&i.push({idx:n,item:e,matches:r})})),i}_findMatches({key:e,value:t,searcher:s}){if(!a(t))return[];let n=[];if(i(t))t.forEach((({v:t,i,n:r})=>{if(!a(t))return;const{isMatch:o,score:c,indices:h}=s.searchIn(t);o&&n.push({score:c,key:e,value:t,idx:i,norm:r,indices:h})}));else{const{v:i,n:r}=t,{isMatch:o,score:c,indices:h}=s.searchIn(i);o&&n.push({score:c,key:e,value:i,norm:r,indices:h})}return n}}K.version="6.6.2",K.createIndex=w,K.parseIndex=function(e,{getFn:t=v.getFn,fieldNormWeight:s=v.fieldNormWeight}={}){const{keys:n,records:i}=e,r=new _({getFn:t,fieldNormWeight:s});return r.setKeys(n),r.setIndexRecords(i),r},K.config=v,K.parseQuery=H,function(...e){F.push(...e)}($);class V{constructor(e){this.hour=e,this.greetings={0:"morning",12:"afternoon",16:"evening",19:"night"}}get_greeting(){let e=Object.keys(this.greetings).reverse();for(let t of e)if(this.hour>=t)return this.greetings[t]}}class B{constructor(e,t,s,n){this.containerelm=s,this.eventemitter=n,this.inputelm=t,this.suggestionslistelm=s.querySelector(".suggestion-list"),this.searchproviderlist=null,this.searchengines=e}build_searchprovider_list_elm(e){const t=document.createElement("ul");return t.classList.add("searchproviders"),t.setAttribute("tabindex",-1),this.searchengines.forEach((s=>{const n=document.createElement("li");n.setAttribute("tabindex",-1),n.innerHTML='Search on '+s.name+"",t.appendChild(n)})),t.addEventListener("keyup",(e=>{switch(e.code){case"ArrowUp":if(document.activeElement==e.target.parentNode.parentNode.firstChild.firstChild){this.inputelm.focus();break}document.activeElement.parentNode.previousSibling.firstChild.focus();break;case"ArrowDown":if(document.activeElement==e.target.parentNode.parentNode.lastChild.firstChild){const t=document.querySelector(".suggestionholder .suggestions");t?t.firstChild.firstChild.focus():e.target.parentNode.parentNode.firstChild.firstChild.focus();break}document.activeElement.parentNode.nextSibling.firstChild.focus()}})),t}build_suggestion_list_elm(e){const t=document.createElement("ul");return t.classList.add("suggestions"),t.setAttribute("tabindex",-1),e.forEach((e=>{const s=document.createElement("li");s.setAttribute("tabindex",-1),s.innerHTML='
'+e.name+"",t.appendChild(s)})),t.addEventListener("keyup",(e=>{switch(e.code){case"ArrowUp":if(document.activeElement==e.target.parentNode.parentNode.firstChild.firstChild){this.searchproviderlist.lastChild.firstChild.focus();break}document.activeElement.parentNode.previousSibling.firstChild.focus();break;case"ArrowDown":if(document.activeElement==e.target.parentNode.parentNode.lastChild.firstChild){this.searchproviderlist.firstChild.firstChild.focus();break}document.activeElement.parentNode.nextSibling.firstChild.focus()}})),t}replace(e){const t=this.build_suggestion_list_elm(e),s=document.createElement("span");if(s.classList.add("suggestionholder"),""!==this.inputelm.value){const e=document.createElement("span");e.classList.add("suggestiontitle"),e.innerHTML="Search",s.appendChild(e),this.searchproviderlist=this.build_searchprovider_list_elm(this.inputelm.value),s.appendChild(this.searchproviderlist)}if(t.childNodes.length>0){const e=document.createElement("span");e.classList.add("suggestiontitle"),e.innerHTML="Sites",s.appendChild(e),s.appendChild(t)}if(s.childNodes.length>0)this.containerelm.classList.add("suggestions"),this.suggestionslistelm.replaceChildren(s);else{this.containerelm.classList.remove("suggestions");let e=this.containerelm.querySelector(".suggestionholder");e&&e.remove()}}}class Q{constructor(e){this.eventemitter=e}fetch_owm_data(e){let t="/api/weatherdata.php?token="+JUMP.token;e.length&&(t+="&lat="+e[0]+"&lon="+e[1]),fetch(t).then((e=>e.json())).then((e=>{if(e.error)console.error("JUMP ERROR: There was an issue with the OWM API... "+e.error);else if(401!==e.cod){var t="night";e.dt>e.sys.sunrise&&e.dt{this.weather.fetch_owm_data(this.latlong)}),this.weatherfrequency)):this.eventemitter.emit("show-content")}add_event_listeners(){if(this.eventemitter.on("weather-loaded",(e=>{this.timezoneshift=e.timezoneshift,this.weatherelm.href="https://openweathermap.org/city/"+e.locationcode,this.weathericonelm.classList.add(e.iconclass),this.clientlocationelm.innerHTML=e.locationname,this.tempelm.innerHTML=e.temp,this.weatherdescelm.innerHTML=e.description,this.clientlocationelm.classList.add("enable"),this.eventemitter.emit("show-content")})),this.eventemitter.on("clock-updated",(e=>{if(null!=this.timeelm&&(this.timeelm.innerHTML=e.formatted_time),null!=this.greetingelm){let t=new V(e.hour);this.greetingelm.innerHTML=t.get_greeting()}})),this.eventemitter.on("show-content",(()=>{this.set_clock(),this.show_content()})),this.clientlocationelm.addEventListener("click",(e=>{navigator.geolocation.getCurrentPosition((e=>{this.latlong=[e.coords.latitude,e.coords.longitude],this.storage.setItem("lastrequestedlocation",JSON.stringify(this.latlong)),this.weather.fetch_owm_data(this.latlong)}),(e=>{console.error(e.message)}),{enableHighAccuracy:!0})})),this.showtagsbuttonelm&&this.showtagsbuttonelm.addEventListener("click",(e=>{this.tagselectorelm.classList.add("enable"),e.preventDefault()})),this.tagsselectorclosebuttonelm&&this.tagsselectorclosebuttonelm.addEventListener("click",(e=>{this.tagselectorelm.classList.remove("enable")})),this.showsearchbuttonelm){const e=document.querySelector(".search-form input");this.searchsuggestions=new B(JSON.parse(JUMP.searchengines),e,this.showsearchbuttonelm,this.eventemitter),this.showsearchbuttonelm.addEventListener("click",(t=>{t.target.classList.contains("open")||(this.showsearchbuttonelm.classList.add("open"),e.focus())})),document.addEventListener("keyup",(t=>{t.ctrlKey&&"Slash"==t.code&&(this.showsearchbuttonelm.classList.contains("open")?this.search_close():(this.showsearchbuttonelm.classList.add("open"),e.focus()))})),this.searchclosebuttonelm.addEventListener("click",(e=>{e.stopPropagation(),this.search_close()})),e.addEventListener("keyup",(t=>{let s=document.querySelector(".suggestion-list .searchproviders");if("ArrowDown"===t.code)return void(s&&s.childNodes.length&&s.firstChild.firstChild.focus());let n=[],i=this.fuse.search(e.value);i.length>0&&i.forEach((e=>{n.push(e.item)})),this.searchsuggestions.replace(n)})),document.querySelector(".search-form").addEventListener("submit",(t=>{t.preventDefault(),""!=e.value&&document.querySelector(".searchproviders li a").click()}))}}search_close(){let e=this.showsearchbuttonelm.querySelector(".suggestionholder");e&&e.remove(),this.showsearchbuttonelm.classList.remove("suggestions"),document.querySelector(".search").classList.remove("open"),document.querySelector(".search-form input").value=""}show_content(){document.querySelectorAll(".hidden").forEach((function(e){e.classList.remove("hidden")}))}set_clock(){this.clock.set_utc_shift(this.timezoneshift),this.clock.run(this.clockfrequency)}}).init()})()})();
\ No newline at end of file
diff --git a/jumpapp/assets/js/index.52c1dcb71f05502fb292.min.js b/jumpapp/assets/js/index.52c1dcb71f05502fb292.min.js
deleted file mode 100644
index 34c8221..0000000
--- a/jumpapp/assets/js/index.52c1dcb71f05502fb292.min.js
+++ /dev/null
@@ -1 +0,0 @@
-(()=>{"use strict";var e={729:e=>{var t=Object.prototype.hasOwnProperty,n="~";function i(){}function s(e,t,n){this.fn=e,this.context=t,this.once=n||!1}function r(e,t,i,r,o){if("function"!=typeof i)throw new TypeError("The listener must be a function");var h=new s(i,r||e,o),c=n?n+t:t;return e._events[c]?e._events[c].fn?e._events[c]=[e._events[c],h]:e._events[c].push(h):(e._events[c]=h,e._eventsCount++),e}function o(e,t){0==--e._eventsCount?e._events=new i:delete e._events[t]}function h(){this._events=new i,this._eventsCount=0}Object.create&&(i.prototype=Object.create(null),(new i).__proto__||(n=!1)),h.prototype.eventNames=function(){var e,i,s=[];if(0===this._eventsCount)return s;for(i in e=this._events)t.call(e,i)&&s.push(n?i.slice(1):i);return Object.getOwnPropertySymbols?s.concat(Object.getOwnPropertySymbols(e)):s},h.prototype.listeners=function(e){var t=n?n+e:e,i=this._events[t];if(!i)return[];if(i.fn)return[i.fn];for(var s=0,r=i.length,o=new Array(r);s{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var i in t)n.o(t,i)&&!n.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{class e{constructor(e,t=!1,n=!1){this.set_utc_shift(),this.contentintervalid=null,this.eventemitter=e,this.ampm=t,this.forcelocaltime=n}set_utc_shift(e=0){this.utcshift=e,this.shiftedtimestamp=(new Date).getTime()+this.utcshift,this.shifteddate=new Date(this.shiftedtimestamp)}get_formatted_time(){let e=this.shifteddate.getUTCHours(),t=String(this.shifteddate.getUTCMinutes()).padStart(2,"0");if(this.forcelocaltime&&(e=(new Date).getHours(),t=String((new Date).getMinutes()).padStart(2,"0")),!this.ampm)return String(e).padStart(2,"0")+":"+t;const n=e<=12?"AM":"PM";return e=(e+11)%12+1,e+":"+t+""+n+""}get_hour(){return this.forcelocaltime?(new Date).getHours():this.shifteddate.getUTCHours()}update_time(){this.set_utc_shift(this.utcshift),this.eventemitter.emit("clock-updated",{formatted_time:this.get_formatted_time(),hour:this.get_hour(),utcshift:this.utcshift})}run(e){this.contentintervalid&&clearInterval(this.contentintervalid),this.update_time(),this.contentintervalid=setInterval((()=>{this.update_time()}),e)}}var t=n(729),i=n.n(t);class s{constructor(e){this.hour=e,this.greetings={0:"morning",12:"afternoon",16:"evening",19:"night"}}get_greeting(){let e=Object.keys(this.greetings).reverse();for(let t of e)if(this.hour>=t)return this.greetings[t]}}class r{constructor(e){this.eventemitter=e}fetch_owm_data(e){let t="/api/weatherdata.php?token="+JUMP.token;e.length&&(t+="&lat="+e[0]+"&lon="+e[1]),fetch(t).then((e=>e.json())).then((e=>{if(e.error)console.error("JUMP ERROR: There was an issue with the OWM API... "+e.error);else if(401!==e.cod){var t="night";e.dt>e.sys.sunrise&&e.dt{this.weather.fetch_owm_data(this.latlong)}),this.weatherfrequency)):this.eventemitter.emit("show-content")}add_event_listeners(){this.eventemitter.on("weather-loaded",(e=>{this.timezoneshift=e.timezoneshift,this.weatherelm.href="https://openweathermap.org/city/"+e.locationcode,this.weathericonelm.classList.add(e.iconclass),this.clientlocationelm.innerHTML=e.locationname,this.tempelm.innerHTML=e.temp,this.weatherdescelm.innerHTML=e.description,this.clientlocationelm.classList.add("enable"),this.eventemitter.emit("show-content")})),this.eventemitter.on("clock-updated",(e=>{if(null!=this.timeelm&&(this.timeelm.innerHTML=e.formatted_time),null!=this.greetingelm){let t=new s(e.hour);this.greetingelm.innerHTML=t.get_greeting()}})),this.eventemitter.on("show-content",(()=>{this.set_clock(),this.show_content()})),this.clientlocationelm.addEventListener("click",(e=>{navigator.geolocation.getCurrentPosition((e=>{this.latlong=[e.coords.latitude,e.coords.longitude],this.storage.setItem("lastrequestedlocation",JSON.stringify(this.latlong)),this.weather.fetch_owm_data(this.latlong)}),(e=>{console.error(e.message)}),{enableHighAccuracy:!0})})),this.showtagsbuttonelm&&this.showtagsbuttonelm.addEventListener("click",(e=>{this.tagselectorelm.classList.add("enable"),e.preventDefault()})),this.tagsselectorclosebuttonelm&&this.tagsselectorclosebuttonelm.addEventListener("click",(e=>{this.tagselectorelm.classList.remove("enable")}))}show_content(){document.querySelectorAll(".hidden").forEach((function(e){e.classList.remove("hidden")}))}set_clock(){this.clock.set_utc_shift(this.timezoneshift),this.clock.run(this.clockfrequency)}}).init()})()})();
\ No newline at end of file
diff --git a/jumpapp/assets/js/src/classes/Main.js b/jumpapp/assets/js/src/classes/Main.js
index 7d587d5..25c85d5 100644
--- a/jumpapp/assets/js/src/classes/Main.js
+++ b/jumpapp/assets/js/src/classes/Main.js
@@ -1,6 +1,8 @@
import Clock from './Clock';
import EventEmitter from 'eventemitter3';
+import Fuse from 'fuse.js';
import Greeting from './Greeting';
+import SearchSuggestions from './SearchSuggestions';
import Weather from './Weather';
export default class Main {
@@ -23,7 +25,8 @@ export default class Main {
this.clientlocationelm = document.querySelector('.useclientlocation');
this.showtagsbuttonelm = document.querySelector('.show-tags');
this.tagselectorelm = document.querySelector('.tags');
- this.tagsselectorclosebuttonelm = document.querySelector('.tags .close')
+ this.tagsselectorclosebuttonelm = document.querySelector('.tags .close');
+ this.showsearchbuttonelm = document.querySelector('.search');
// If the user has previously asked for geolocation we will have stored the latlong.
if (this.lastrequestedlocation = this.storage.getItem('lastrequestedlocation')){
this.latlong = JSON.parse(this.lastrequestedlocation);
@@ -32,6 +35,14 @@ export default class Main {
this.eventemitter = new EventEmitter();
this.clock = new Clock(this.eventemitter, !!JUMP.ampmclock, !JUMP.owmapikey);
this.weather = new Weather(this.eventemitter);
+
+ if (this.showsearchbuttonelm) {
+ this.searchclosebuttonelm = this.showsearchbuttonelm.querySelector('.close');
+ this.fuse = new Fuse(JSON.parse(JUMP.search), {
+ threshold: 0.3,
+ keys: ['name', 'tags']
+ });
+ }
}
/**
@@ -115,6 +126,76 @@ export default class Main {
});
}
+ if (this.showsearchbuttonelm) {
+ const searchinput = document.querySelector('.search-form input');
+ this.searchsuggestions = new SearchSuggestions(JSON.parse(JUMP.searchengines), searchinput, this.showsearchbuttonelm, this.eventemitter);
+
+ // When the search icon is licked, show the search bar and focus on it.
+ this.showsearchbuttonelm.addEventListener('click', e => {
+ if (!e.target.classList.contains('open')) {
+ this.showsearchbuttonelm.classList.add('open');
+ searchinput.focus();
+ }
+ });
+
+ // Listen for CTRL+/ key combo and open search bar.
+ document.addEventListener('keyup', e => {
+ if (e.ctrlKey && (e.code == 'Slash')) {
+ if (!this.showsearchbuttonelm.classList.contains('open')) {
+ this.showsearchbuttonelm.classList.add('open');
+ searchinput.focus();
+ } else {
+ this.search_close();
+ }
+ }
+ });
+
+ // Handle the close button.
+ this.searchclosebuttonelm.addEventListener('click', e => {
+ e.stopPropagation();
+ this.search_close();
+ });
+
+ // Listen for key events triggered by the searh bar and do stuff.
+ searchinput.addEventListener('keyup', e => {
+ // On arrow down, focus on the first search suggestion.
+ let suggestionslist = document.querySelector('.suggestion-list .searchproviders');
+ if (e.code === 'ArrowDown') {
+ if (suggestionslist && suggestionslist.childNodes.length) {
+ suggestionslist.firstChild.firstChild.focus();
+ }
+ return;
+ }
+ // Perform search and display suggestions on the page.
+ let results = [];
+ let siteresults = this.fuse.search(searchinput.value);
+ if (siteresults.length > 0) {
+ siteresults.forEach((result) => {
+ results.push(result.item);
+ });
+ }
+ this.searchsuggestions.replace(results);
+ });
+
+ // If someone presses enter then open up the first link, this is the default seach engine
+ // purely because it is at the top of the list.
+ document.querySelector('.search-form').addEventListener('submit', e => {
+ e.preventDefault();
+ if (searchinput.value != '') {
+ document.querySelector('.searchproviders li a').click();
+ }
+ });
+ }
+ }
+
+ search_close() {
+ let suggestions = this.showsearchbuttonelm.querySelector('.suggestionholder');
+ if (suggestions) {
+ suggestions.remove();
+ }
+ this.showsearchbuttonelm.classList.remove('suggestions');
+ document.querySelector('.search').classList.remove('open');
+ document.querySelector('.search-form input').value = '';
}
/**
diff --git a/jumpapp/assets/js/src/classes/SearchSuggestions.js b/jumpapp/assets/js/src/classes/SearchSuggestions.js
new file mode 100644
index 0000000..86825ea
--- /dev/null
+++ b/jumpapp/assets/js/src/classes/SearchSuggestions.js
@@ -0,0 +1,122 @@
+/**
+ * Generate search suggestions.
+ *
+ * @author Dale Davies
+ * @license MIT
+ */
+
+export default class SearchSuggestions {
+
+ constructor(searchengines, inputelm, containerelm, eventemitter) {
+ this.containerelm = containerelm;
+ this.eventemitter = eventemitter;
+ this.inputelm = inputelm;
+ this.suggestionslistelm = containerelm.querySelector('.suggestion-list');
+ this.searchproviderlist = null;
+ this.searchengines = searchengines;
+ }
+
+ build_searchprovider_list_elm(query) {
+ const searchproviderlist = document.createElement('ul');
+ searchproviderlist.classList.add('searchproviders');
+ searchproviderlist.setAttribute('tabindex', -1);
+ this.searchengines.forEach((provider) => {
+ const searchprovider = document.createElement('li');
+ searchprovider.setAttribute('tabindex', -1);
+ searchprovider.innerHTML = 'Search on '+provider.name+'';
+ searchproviderlist.appendChild(searchprovider);
+ });
+ searchproviderlist.addEventListener('keyup', e => {
+ switch (e.code) {
+ case 'ArrowUp':
+ if (document.activeElement == e.target.parentNode.parentNode.firstChild.firstChild) {
+ this.inputelm.focus();
+ break;
+ }
+ document.activeElement.parentNode.previousSibling.firstChild.focus();
+ break;
+ case 'ArrowDown':
+ if (document.activeElement == e.target.parentNode.parentNode.lastChild.firstChild) {
+ const suggestionselm = document.querySelector('.suggestionholder .suggestions');
+ if (suggestionselm) {
+ suggestionselm.firstChild.firstChild.focus();
+ } else {
+ e.target.parentNode.parentNode.firstChild.firstChild.focus();
+ }
+ break;
+ }
+ document.activeElement.parentNode.nextSibling.firstChild.focus();
+ break;
+ }
+ });
+ return searchproviderlist;
+ }
+
+ build_suggestion_list_elm(siteresults) {
+ const suggestionslist = document.createElement('ul');
+ suggestionslist.classList.add('suggestions');
+ suggestionslist.setAttribute('tabindex', -1);
+ siteresults.forEach((result) => {
+ const resultitem = document.createElement('li');
+ resultitem.setAttribute('tabindex', -1);
+ resultitem.innerHTML = '\
+
'+result.name+'';
+ suggestionslist.appendChild(resultitem);
+ });
+ suggestionslist.addEventListener('keyup', e => {
+ switch (e.code) {
+ case 'ArrowUp':
+ if (document.activeElement == e.target.parentNode.parentNode.firstChild.firstChild) {
+ this.searchproviderlist.lastChild.firstChild.focus();
+ break;
+ }
+ document.activeElement.parentNode.previousSibling.firstChild.focus();
+ break;
+ case 'ArrowDown':
+ if (document.activeElement == e.target.parentNode.parentNode.lastChild.firstChild) {
+ this.searchproviderlist.firstChild.firstChild.focus();
+ break;
+ }
+ document.activeElement.parentNode.nextSibling.firstChild.focus();
+ break;
+ }
+ });
+ return suggestionslist;
+ }
+
+ replace(siteresults) {
+ const newsuggestionslist = this.build_suggestion_list_elm(siteresults);
+
+ const suggestionholder = document.createElement('span');
+ suggestionholder.classList.add('suggestionholder');
+
+ if (this.inputelm.value !== '') {
+ const searchtitle = document.createElement('span');
+ searchtitle.classList.add('suggestiontitle');
+ searchtitle.innerHTML = 'Search';
+ suggestionholder.appendChild(searchtitle);
+ this.searchproviderlist = this.build_searchprovider_list_elm(this.inputelm.value);
+ suggestionholder.appendChild(this.searchproviderlist);
+ }
+
+ if (newsuggestionslist.childNodes.length > 0) {
+ const suggestiontitle = document.createElement('span');
+ suggestiontitle.classList.add('suggestiontitle');
+ suggestiontitle.innerHTML = 'Sites';
+ suggestionholder.appendChild(suggestiontitle);
+ suggestionholder.appendChild(newsuggestionslist)
+ }
+
+ if (suggestionholder.childNodes.length > 0) {
+ this.containerelm.classList.add('suggestions');
+ this.suggestionslistelm.replaceChildren(suggestionholder);
+ } else {
+ this.containerelm.classList.remove('suggestions');
+ let suggestions = this.containerelm.querySelector('.suggestionholder');
+ if (suggestions) {
+ suggestions.remove();
+ }
+ }
+ }
+}
diff --git a/jumpapp/classes/Cache.php b/jumpapp/classes/Cache.php
index 2d5318e..8428486 100644
--- a/jumpapp/classes/Cache.php
+++ b/jumpapp/classes/Cache.php
@@ -29,6 +29,11 @@ class Cache {
public function __construct(private Config $config) {
// Define the various caches used throughout the app.
$this->caches = [
+ 'searchengines' => [
+ 'cache' => null,
+ 'expirationtype' => Caching\Cache::FILES,
+ 'expirationparams' => $config->get('searchenginesfile')
+ ],
'sites' => [
'cache' => null,
'expirationtype' => Caching\Cache::FILES,
diff --git a/jumpapp/classes/Config.php b/jumpapp/classes/Config.php
index 10d9846..533346b 100644
--- a/jumpapp/classes/Config.php
+++ b/jumpapp/classes/Config.php
@@ -24,6 +24,7 @@ class Config {
private const BASE_APPLICATION_PATHS = [
'backgroundsdir' => '/assets/backgrounds',
'defaulticonpath' => '/assets/images/default-icon.png',
+ 'searchenginesfile' => '/search/searchengines.json',
'sitesdir' => '/sites',
'sitesfile' => '/sites/sites.json',
'templatedir' => '/templates',
diff --git a/jumpapp/classes/Pages/HomePage.php b/jumpapp/classes/Pages/HomePage.php
index d05896a..d944c79 100644
--- a/jumpapp/classes/Pages/HomePage.php
+++ b/jumpapp/classes/Pages/HomePage.php
@@ -11,7 +11,7 @@ class HomePage extends AbstractPage {
$greeting = 'home';
}
$csrfsection = $this->session->getSection('csrf');
- return $template->render([
+ $templatecontext = [
'csrftoken' => $csrfsection->get('token'),
'greeting' => $greeting,
'noindex' => $this->config->parse_bool($this->config->get('noindex')),
@@ -19,7 +19,13 @@ class HomePage extends AbstractPage {
'owmapikey' => !!$this->config->get('owmapikey', false),
'metrictemp' => $this->config->parse_bool($this->config->get('metrictemp')),
'ampmclock' => $this->config->parse_bool($this->config->get('ampmclock', false)),
- ]);
+ ];
+ if ($this->config->parse_bool($this->config->get('showsearch', false))) {
+ $templatecontext = array_merge($templatecontext,
+ ['searchengines' => json_encode((new \Jump\SearchEngines($this->config, $this->cache))->get_search_engines()),
+ 'searchjson' => json_encode((new \Jump\Sites($this->config, $this->cache))->get_sites_for_search()),]);
+ }
+ return $template->render($templatecontext);
}
protected function render_content(): string {
@@ -42,7 +48,8 @@ class HomePage extends AbstractPage {
return $template->render([
'hastags' => !empty($tags),
'tags' => $tags,
- 'showclock' => $this->config->parse_bool($this->config->get('showclock'))
+ 'showclock' => $this->config->parse_bool($this->config->get('showclock')),
+ 'showsearch' => $this->config->parse_bool($this->config->get('showsearch', false)),
]);
});
}
diff --git a/jumpapp/classes/Pages/TagPage.php b/jumpapp/classes/Pages/TagPage.php
index 670908d..822d23b 100644
--- a/jumpapp/classes/Pages/TagPage.php
+++ b/jumpapp/classes/Pages/TagPage.php
@@ -11,14 +11,20 @@ class TagPage extends AbstractPage {
$greeting = $this->param;
$title = 'Tag: '.$this->param;
$csrfsection = $this->session->getSection('csrf');
- return $template->render([
+ $templatecontext = [
'csrftoken' => $csrfsection->get('token'),
'greeting' => $greeting,
'noindex' => $this->config->parse_bool($this->config->get('noindex')),
'title' => $title,
'owmapikey' => !!$this->config->get('owmapikey', false),
'metrictemp' => $this->config->parse_bool($this->config->get('metrictemp')),
- ]);
+ ];
+ if ($this->config->parse_bool($this->config->get('showsearch', false))) {
+ $templatecontext = array_merge($templatecontext,
+ ['searchengines' => json_encode((new \Jump\SearchEngines($this->config, $this->cache))->get_search_engines()),
+ 'searchjson' => json_encode((new \Jump\Sites($this->config, $this->cache))->get_sites_for_search()),]);
+ }
+ return $template->render($templatecontext);
}
protected function render_content(): string {
@@ -48,7 +54,8 @@ class TagPage extends AbstractPage {
return $template->render([
'hastags' => !empty($tags),
'tags' => $tags,
- 'showclock' => $this->config->parse_bool($this->config->get('showclock'))
+ 'showclock' => $this->config->parse_bool($this->config->get('showclock')),
+ 'showsearch' => $this->config->parse_bool($this->config->get('showsearch', false)),
]);
});
}
diff --git a/jumpapp/classes/SearchEngines.php b/jumpapp/classes/SearchEngines.php
new file mode 100644
index 0000000..4ab94ff
--- /dev/null
+++ b/jumpapp/classes/SearchEngines.php
@@ -0,0 +1,82 @@
+
+ * @license MIT
+ */
+class SearchEngines {
+ private array $default;
+ private string $searchfilelocation;
+ private array $loadedsearchengines;
+
+ /**
+ * Automatically load searchengines.json on instantiation.
+ */
+ public function __construct(private Config $config, private Cache $cache) {
+ $this->config = $config;
+ $this->loadedsearchengines = [];
+ $this->searchfilelocation = $this->config->get('searchenginesfile');
+ $this->cache = $cache;
+
+ // Retrieve search engines from cache. Load from json file if not cached or
+ // the cache has expired.
+ $this->loadedsearchengines = $this->cache->load(cachename: 'searchengines', callback: function() {
+ return $this->load_search_engines_from_json();
+ });
+
+ }
+ /**
+ * Try to load and validate the list of search engines from searchengines.json.
+ *
+ * Throws an exception if the file cannot be loaded, is empty, or cannot
+ * be decoded to an array.
+ *
+ * @return array AArray of parsed/validated search engine information from searchengines.json
+ * @throws Exception If searchengines.json cannot be found.
+ */
+ private function load_search_engines_from_json(): array {
+ $searchengines = [];
+ $rawjson = file_get_contents($this->searchfilelocation);
+ if ($rawjson === false) {
+ throw new Exception('There was a problem loading the searchengines.json file');
+ }
+ if ($rawjson === '') {
+ throw new Exception('The searchengines.json file is empty');
+ }
+ // Do some checks to see if the JSON decodes into something
+ // like what we expect to see...
+ $decodedjson = json_decode($rawjson);
+
+ if (!is_array($decodedjson)) {
+ throw new Exception('The searchengines.json file is invalid');
+ }
+
+ // Build a new array using the values we need...
+ foreach ($decodedjson as $item) {
+ if (!isset($item->name, $item->url)) {
+ throw new Exception('The searchengines.json does not contain the "name" or "url" properties');
+ }
+ $searchengine = new \stdClass();
+ $searchengine->name = $item->name;
+ $searchengine->url = $item->url;
+ $searchengines[] = $searchengine;
+ }
+
+ return $searchengines;
+ }
+
+ /**
+ * Get the list of loaded search engines.
+ *
+ * @return array Array of parsed/validated search engine information from searchengines.json
+ */
+ public function get_search_engines() {
+ return $this->loadedsearchengines;
+ }
+}
diff --git a/jumpapp/classes/Sites.php b/jumpapp/classes/Sites.php
index 261ee20..a8dd121 100644
--- a/jumpapp/classes/Sites.php
+++ b/jumpapp/classes/Sites.php
@@ -156,4 +156,17 @@ class Sites {
return $found;
}
+ public function get_sites_for_search(): array {
+ $searchlist = [];
+ foreach ($this->loadedsites as $loadedsite) {
+ $site = new \stdClass();
+ $site->name = $loadedsite->name;
+ $site->url = $loadedsite->url;
+ $site->tags = $loadedsite->tags;
+ $site->iconurl = '/api/icon.php?siteurl='.urlencode($loadedsite->url);
+ $searchlist[] = $site;
+ }
+ return $searchlist;
+ }
+
}
diff --git a/jumpapp/config.php b/jumpapp/config.php
index 1872cea..38c8f1a 100644
--- a/jumpapp/config.php
+++ b/jumpapp/config.php
@@ -30,7 +30,9 @@ return [
// Background brightness percentage.
'bgbright' => getenv('BGBRIGHT') ?: '85',
// Display alternative layout of sites list.
- 'altlayout' => getenv('ALTLAYOUT') ?: false,
+ 'altlayout' => getenv('ALTLAYOUT') ?: false,
+ // Show the search bar, requires /search/searchengines.json etc.
+ 'showsearch' => getenv('SHOWSEARCH') ?: true,
// Open Weather Map API key.
'owmapikey' => getenv('OWMAPIKEY') ?: '',
diff --git a/jumpapp/search/searchengines.json b/jumpapp/search/searchengines.json
new file mode 100644
index 0000000..5b121b7
--- /dev/null
+++ b/jumpapp/search/searchengines.json
@@ -0,0 +1,14 @@
+[
+ {
+ "name": "Google",
+ "url": "https://www.google.co.uk/search?q="
+ },
+ {
+ "name": "DuckDuckGo",
+ "url": "https://duckduckgo.com/?q="
+ },
+ {
+ "name": "Bing",
+ "url": "https://www.bing.com/search?q="
+ }
+]
\ No newline at end of file
diff --git a/jumpapp/templates/footer.mustache b/jumpapp/templates/footer.mustache
index a1e2603..9aac940 100644
--- a/jumpapp/templates/footer.mustache
+++ b/jumpapp/templates/footer.mustache
@@ -11,6 +11,15 @@
{{# hastags}}
@@ -23,6 +32,6 @@
{{/ hastags}}
-
+